Compare commits

..

2 Commits

109 changed files with 600 additions and 5128 deletions

View File

@@ -87,7 +87,7 @@ docker-compose up -d
- 第一步: 先安装 docker 环境, 以及docker-compose - 第一步: 先安装 docker 环境, 以及docker-compose
- 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载` - 第二步: 直接使用项目根目录下的install.sh 文件进行 `安装`、`启动`、`暂停`、`查状态`、`删除`、`卸载`
```shell ```shell
curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/master/install.sh curl -so install.sh https://raw.githubusercontent.com/veops/cmdb/deploy_on_kylin_docker/install.sh
sh install.sh install sh install.sh install
``` ```

View File

@@ -62,7 +62,6 @@ alembic = "==1.7.7"
hvac = "==2.0.0" hvac = "==2.0.0"
colorama = ">=0.4.6" colorama = ">=0.4.6"
pycryptodomex = ">=3.19.0" pycryptodomex = ">=3.19.0"
lz4 = ">=4.3.2"
[dev-packages] [dev-packages]
# Testing # Testing

View File

@@ -19,8 +19,7 @@ from flask.json.provider import DefaultJSONProvider
import api.views.entry import api.views.entry
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd) from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
from api.extensions import inner_secrets from api.extensions import inner_secrets
from api.lib.perm.authentication.cas import CAS from api.flask_cas import CAS
from api.lib.perm.authentication.oauth2 import OAuth2
from api.lib.secrets.secrets import InnerKVManger from api.lib.secrets.secrets import InnerKVManger
from api.models.acl import User from api.models.acl import User
@@ -97,7 +96,6 @@ def create_app(config_object="settings"):
register_shell_context(app) register_shell_context(app)
register_commands(app) register_commands(app)
CAS(app) CAS(app)
OAuth2(app)
app.wsgi_app = ReverseProxy(app.wsgi_app) app.wsgi_app = ReverseProxy(app.wsgi_app)
configure_upload_dir(app) configure_upload_dir(app)
@@ -194,11 +192,10 @@ def configure_logger(app):
app.logger.addHandler(handler) app.logger.addHandler(handler)
log_file = app.config['LOG_PATH'] log_file = app.config['LOG_PATH']
if log_file and log_file != "/dev/stdout": file_handler = RotatingFileHandler(log_file,
file_handler = RotatingFileHandler(log_file, maxBytes=2 ** 30,
maxBytes=2 ** 30, backupCount=7)
backupCount=7) file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL']))
file_handler.setLevel(getattr(logging, app.config['LOG_LEVEL'])) file_handler.setFormatter(formatter)
file_handler.setFormatter(formatter) app.logger.addHandler(file_handler)
app.logger.addHandler(file_handler)
app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL'])) app.logger.setLevel(getattr(logging, app.config['LOG_LEVEL']))

View File

@@ -117,15 +117,7 @@ def cmdb_init_acl():
# 1. add resource type # 1. add resource type
for resource_type in ResourceTypeEnum.all(): for resource_type in ResourceTypeEnum.all():
try: try:
perms = PermEnum.all() ResourceTypeCRUD.add(app_id, resource_type, '', 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: except AbortException:
pass pass

View File

@@ -299,20 +299,3 @@ def common_check_new_columns():
except Exception as e: except Exception as e:
current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:") current_app.logger.error(f"add new column [{column.name}] in table [{table_name}] err:")
current_app.logger.error(e) 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)

View File

@@ -15,7 +15,7 @@ try:
except ImportError: except ImportError:
from flask import _request_ctx_stack as stack from flask import _request_ctx_stack as stack
from . import routing from api.flask_cas import routing
class CAS(object): class CAS(object):

View File

@@ -119,4 +119,4 @@ def create_cas_validate_url(cas_url, cas_route, service, ticket,
('service', service), ('service', service),
('ticket', ticket), ('ticket', ticket),
('renew', renew), ('renew', renew),
) )

View File

@@ -1,24 +1,14 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import datetime
import uuid import json
import bs4 import bs4
from flask import Blueprint from flask import Blueprint
from flask import current_app from flask import current_app, session, request, url_for, redirect
from flask import redirect from flask_login import login_user, logout_user
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 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.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_login_url
from .cas_urls import create_cas_logout_url from .cas_urls import create_cas_logout_url
from .cas_urls import create_cas_validate_url from .cas_urls import create_cas_validate_url
@@ -26,7 +16,6 @@ from .cas_urls import create_cas_validate_url
blueprint = Blueprint('cas', __name__) blueprint = Blueprint('cas', __name__)
@blueprint.route('/api/cas/login')
@blueprint.route('/api/sso/login') @blueprint.route('/api/sso/login')
def login(): def login():
""" """
@@ -40,20 +29,16 @@ def login():
If validation was successful the logged in username is saved in If validation was successful the logged in username is saved in
the user's session under the key `CAS_USERNAME_SESSION_KEY`. 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'] cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
if request.values.get("next"): if request.values.get("next"):
session["next"] = request.values.get("next") session["next"] = request.values.get("next")
# _service = url_for('cas.login', _external=True) _service = url_for('cas.login', _external=True, next=session["next"]) \
_service = "{}://{}{}".format(urlparse(request.referrer).scheme, if session.get("next") else url_for('cas.login', _external=True)
urlparse(request.referrer).netloc,
url_for('cas.login'))
redirect_url = create_cas_login_url( redirect_url = create_cas_login_url(
config['cas_server'], current_app.config['CAS_SERVER'],
config['cas_login_route'], current_app.config['CAS_LOGIN_ROUTE'],
_service) _service)
if 'ticket' in request.args: if 'ticket' in request.args:
@@ -62,38 +47,30 @@ def login():
if request.args.get('ticket'): if request.args.get('ticket'):
if validate(request.args['ticket']): if validate(request.args['ticket']):
redirect_url = session.get("next") or config.get("cas_after_login") or "/" redirect_url = session.get("next") or \
current_app.config.get("CAS_AFTER_LOGIN")
username = session.get("CAS_USERNAME") username = session.get("CAS_USERNAME")
user = UserCache.get(username) user = UserCache.get(username)
login_user(user) login_user(user)
session.permanent = True session.permanent = True
_id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
session['LOGIN_ID'] = _id
else: else:
del session[cas_token_session_key] del session[cas_token_session_key]
redirect_url = create_cas_login_url( redirect_url = create_cas_login_url(
config['cas_server'], current_app.config['CAS_SERVER'],
config['cas_login_route'], current_app.config['CAS_LOGIN_ROUTE'],
url_for('cas.login', _external=True), url_for('cas.login', _external=True),
renew=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)) current_app.logger.info("redirect to: {0}".format(redirect_url))
return redirect(redirect_url) return redirect(redirect_url)
@blueprint.route('/api/cas/logout')
@blueprint.route('/api/sso/logout') @blueprint.route('/api/sso/logout')
def logout(): def logout():
""" """
When the user accesses this route they are logged out. 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_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY'] cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
@@ -105,14 +82,12 @@ def logout():
"next" in session and session.pop("next") "next" in session and session.pop("next")
redirect_url = create_cas_logout_url( redirect_url = create_cas_logout_url(
config['cas_server'], current_app.config['CAS_SERVER'],
config['cas_logout_route'], current_app.config['CAS_LOGOUT_ROUTE'],
url_for('cas.login', _external=True, next=request.referrer)) url_for('cas.login', _external=True, next=request.referrer))
logout_user() 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)) current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
return redirect(redirect_url) return redirect(redirect_url)
@@ -125,15 +100,14 @@ def validate(ticket):
and the validated username is saved in the session under the and the validated username is saved in the session under the
key `CAS_USERNAME_SESSION_KEY`. key `CAS_USERNAME_SESSION_KEY`.
""" """
config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY'] cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
current_app.logger.debug("validating token {0}".format(ticket)) current_app.logger.debug("validating token {0}".format(ticket))
cas_validate_url = create_cas_validate_url( cas_validate_url = create_cas_validate_url(
config['cas_validate_server'], current_app.config['CAS_VALIDATE_SERVER'],
config['cas_validate_route'], current_app.config['CAS_VALIDATE_ROUTE'],
url_for('cas.login', _external=True), url_for('cas.login', _external=True),
ticket) ticket)
@@ -141,35 +115,23 @@ def validate(ticket):
try: try:
response = urlopen(cas_validate_url).read() response = urlopen(cas_validate_url).read()
ticket_id = _parse_tag(response, "cas:user") ticketid = _parse_tag(response, "cas:user")
strs = [s.strip() for s in ticket_id.split('|') if s.strip()] strs = [s.strip() for s in ticketid.split('|') if s.strip()]
username, is_valid = None, False username, is_valid = None, False
if len(strs) == 1: if len(strs) == 1:
username = strs[0] username = strs[0]
is_valid = True is_valid = True
user_info = json.loads(_parse_tag(response, "cas:other"))
current_app.logger.info(user_info)
except ValueError: except ValueError:
current_app.logger.error("CAS returned unexpected result") current_app.logger.error("CAS returned unexpected result")
is_valid = False is_valid = False
return is_valid return is_valid
if is_valid: if is_valid:
current_app.logger.debug("{}: {}".format(cas_username_session_key, username)) current_app.logger.debug("valid")
session[cas_username_session_key] = username session[cas_username_session_key] = username
user = UserCache.get(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 from api.lib.perm.acl.acl import ACLManager
user_info = ACLManager.get_user_info(username) user_info = ACLManager.get_user_info(username)
@@ -202,5 +164,4 @@ def _parse_tag(string, tag):
if soup.find(tag) is None: if soup.find(tag) is None:
return '' return ''
return soup.find(tag).string.strip() return soup.find(tag).string.strip()

View File

@@ -189,8 +189,7 @@ class AttributeManager(object):
return attr return attr
def get_attribute(self, key, choice_web_hook_parse=True, choice_other_parse=True): def get_attribute(self, key, choice_web_hook_parse=True, choice_other_parse=True):
attr = AttributeCache.get(key) or dict() attr = AttributeCache.get(key).to_dict()
attr = attr and attr.to_dict()
if attr.get("is_choice"): if attr.get("is_choice"):
attr["choice_value"] = self.get_choice_values( attr["choice_value"] = self.get_choice_values(
attr["id"], attr["id"],

View File

@@ -3,6 +3,11 @@ import datetime
import json import json
import os 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.extensions import db
from api.lib.cmdb.auto_discovery.const import ClOUD_MAP from api.lib.cmdb.auto_discovery.const import ClOUD_MAP
from api.lib.cmdb.cache import CITypeAttributeCache from api.lib.cmdb.cache import CITypeAttributeCache
@@ -23,10 +28,6 @@ from api.lib.utils import AESCrypto
from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryRule 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__)) PWD = os.path.abspath(os.path.dirname(__file__))
@@ -250,17 +251,20 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
current_app.logger.warning(e) current_app.logger.warning(e)
return abort(400, str(e)) return abort(400, str(e))
@staticmethod def _can_add(self, **kwargs):
def _can_add(**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'))
if kwargs.get('adr_id'): if kwargs.get('adr_id'):
AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort( adr = AutoDiscoveryRule.get_by_id(kwargs['adr_id']) or abort(
404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id']))) 404, ErrFormat.adr_not_found.format("id={}".format(kwargs['adr_id'])))
# if not adr.is_plugin: if not adr.is_plugin:
# other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False) other = self.cls.get_by(adr_id=adr.id, first=True, to_dict=False)
# if other: if other:
# ci_type = CITypeCache.get(other.type_id) ci_type = CITypeCache.get(other.type_id)
# return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias)) return abort(400, ErrFormat.adr_default_ref_once.format(ci_type.alias))
if kwargs.get('is_plugin') and kwargs.get('plugin_script'): if kwargs.get('is_plugin') and kwargs.get('plugin_script'):
kwargs = check_plugin_script(**kwargs) kwargs = check_plugin_script(**kwargs)

View File

@@ -395,9 +395,8 @@ class CIManager(object):
k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT): k not in ci_type_attrs_alias and _no_attribute_policy == ExistPolicy.REJECT):
return abort(400, ErrFormat.attribute_not_found.format(k)) return abort(400, ErrFormat.attribute_not_found.format(k))
_attr_name = ((ci_type_attrs_name.get(k) and ci_type_attrs_name[k].name) or if limit_attrs and ci_type_attrs_name.get(k) not in limit_attrs and (
(ci_type_attrs_alias.get(k) and ci_type_attrs_alias[k].name)) ci_type_attrs_alias.get(k) not in limit_attrs):
if limit_attrs and _attr_name not in limit_attrs:
return abort(403, ErrFormat.ci_filter_perm_attr_no_permission.format(k)) 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} 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}
@@ -515,9 +514,9 @@ class CIManager(object):
ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE) ci_delete_trigger.apply_async(args=(trigger, OperateType.DELETE, ci_dict), queue=CMDB_QUEUE)
attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False) attrs = CITypeAttribute.get_by(type_id=ci.type_id, to_dict=False)
attrs = [AttributeCache.get(attr.attr_id) for attr in attrs] attr_names = set([AttributeCache.get(attr.attr_id).name for attr in attrs])
for attr in attrs: for attr_name in attr_names:
value_table = TableMap(attr=attr).table value_table = TableMap(attr_name=attr_name).table
for item in value_table.get_by(ci_id=ci_id, to_dict=False): for item in value_table.get_by(ci_id=ci_id, to_dict=False):
item.delete(commit=False) item.delete(commit=False)

View File

@@ -5,10 +5,8 @@ import copy
import toposort import toposort
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask import session
from flask_login import current_user from flask_login import current_user
from toposort import toposort_flatten from toposort import toposort_flatten
from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.attribute import AttributeManager
@@ -24,7 +22,6 @@ from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.history import CITypeHistoryManager 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.relation_type import RelationTypeManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.value import AttributeValueManager from api.lib.cmdb.value import AttributeValueManager
@@ -78,13 +75,12 @@ class CITypeManager(object):
def get_ci_types(type_name=None): def get_ci_types(type_name=None):
resources = None resources = None
if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'):
resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)]) resources = set([i.get('name') for i in ACLManager().get_resources("CIType")])
ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name) ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name)
res = list() res = list()
for type_dict in ci_types: for type_dict in ci_types:
attr = AttributeCache.get(type_dict["unique_id"]) type_dict["unique_key"] = AttributeCache.get(type_dict["unique_id"]).name
type_dict["unique_key"] = attr and attr.name
if resources is None or type_dict['name'] in resources: if resources is None or type_dict['name'] in resources:
res.append(type_dict) res.append(type_dict)
@@ -117,9 +113,6 @@ class CITypeManager(object):
@classmethod @classmethod
@kwargs_required("name") @kwargs_required("name")
def add(cls, **kwargs): 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 = kwargs.pop("unique_key", None) or kwargs.pop("unique_id", None)
unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define) unique_key = AttributeCache.get(unique_key) or abort(404, ErrFormat.unique_key_not_define)
@@ -138,11 +131,7 @@ class CITypeManager(object):
CITypeCache.clean(ci_type.name) CITypeCache.clean(ci_type.name)
if current_app.config.get("USE_ACL"): if current_app.config.get("USE_ACL"):
try: ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
except BadRequest:
pass
ACLManager().grant_resource_to_role(ci_type.name, ACLManager().grant_resource_to_role(ci_type.name,
RoleEnum.CMDB_READ_ALL, RoleEnum.CMDB_READ_ALL,
ResourceTypeEnum.CI, ResourceTypeEnum.CI,
@@ -254,6 +243,7 @@ class CITypeGroupManager(object):
else: else:
resources = {i['name']: i['permissions'] for i in resources if PermEnum.READ in i.get("permissions")} 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) groups = sorted(CITypeGroup.get_by(), key=lambda x: x['order'] or 0)
group_types = set() group_types = set()
for group in groups: for group in groups:
@@ -293,10 +283,7 @@ class CITypeGroupManager(object):
""" """
existed = CITypeGroup.get_by_id(gid) or abort( existed = CITypeGroup.get_by_id(gid) or abort(
404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid))) 404, ErrFormat.ci_type_group_not_found.format("id={}".format(gid)))
if name is not None and name != existed.name: if name is not None:
if RoleEnum.CONFIG not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin("cmdb"):
return abort(403, ErrFormat.role_required.format(RoleEnum.CONFIG))
existed.update(name=name) existed.update(name=name)
max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0]) max_order = max([i.order or 0 for i in CITypeGroupItem.get_by(group_id=gid, to_dict=False)] or [0])
@@ -589,11 +576,6 @@ class CITypeRelationManager(object):
ci_type_dict = CITypeCache.get(type_id).to_dict() ci_type_dict = CITypeCache.get(type_id).to_dict()
ci_type_dict["ctr_id"] = relation_inst.id ci_type_dict["ctr_id"] = relation_inst.id
ci_type_dict["attributes"] = CITypeAttributeManager.get_attributes_by_type_id(ci_type_dict["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["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint ci_type_dict["constraint"] = relation_inst.constraint
@@ -743,7 +725,7 @@ class CITypeAttributeGroupManager(object):
grouped = list() grouped = list()
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id) attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
id2attr = {i.get('id'): i for i in attributes} id2attr = {i['id']: i for i in attributes}
for group in groups: for group in groups:
items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False) items = CITypeAttributeGroupItem.get_by(group_id=group["id"], to_dict=False)
@@ -909,58 +891,97 @@ class CITypeAttributeGroupManager(object):
class CITypeTemplateManager(object): class CITypeTemplateManager(object):
@staticmethod @staticmethod
def __import(cls, data, unique_key='name'): def __import(cls, data):
id2obj_dicts = {i[unique_key]: i for i in data} id2obj_dicts = {i['id']: i for i in data}
existed = cls.get_by(to_dict=False) existed = cls.get_by(deleted=None, to_dict=False)
id2existed = {getattr(i, unique_key): i for i in existed} id2existed = {i.id: i for i in existed}
existed_ids = [getattr(i, unique_key) 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]
id_map = dict()
# add # add
for added_id in set(id2obj_dicts.keys()) - set(existed_ids): 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: if cls == CIType:
__id = CITypeManager.add(**id2obj_dicts[added_id]) CITypeManager.add(**id2obj_dicts[added_id])
CITypeCache.clean(__id)
elif cls == CITypeRelation: elif cls == CITypeRelation:
__id = CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'), CITypeRelationManager.add(id2obj_dicts[added_id].get('parent_id'),
id2obj_dicts[added_id].get('child_id'), id2obj_dicts[added_id].get('child_id'),
id2obj_dicts[added_id].get('relation_type_id'), id2obj_dicts[added_id].get('relation_type_id'),
id2obj_dicts[added_id].get('constraint'), id2obj_dicts[added_id].get('constraint'),
) )
else: else:
obj = cls.create(flush=True, **id2obj_dicts[added_id]) cls.create(flush=True, **id2obj_dicts[added_id])
if cls == Attribute:
AttributeCache.clean(obj)
__id = obj.id
id_map[_id] = __id
# update # update
for updated_id in set(id2obj_dicts.keys()) & set(existed_ids): 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: if cls == CIType:
CITypeCache.clean(id2existed[updated_id].id) 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)
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: try:
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
raise Exception(str(e)) raise Exception(str(e))
return id_map 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)
def _import_attributes(self, type2attributes): def _import_attributes(self, type2attributes):
attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]] attributes = [attr for type_id in type2attributes for attr in type2attributes[type_id]]
@@ -969,262 +990,122 @@ class CITypeTemplateManager(object):
i.pop('default_show', None) i.pop('default_show', None)
i.pop('is_required', None) i.pop('is_required', None)
i.pop('order', 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) choice_value = i.pop('choice_value', None)
if not choice_value:
i['is_choice'] = False
attrs.append((i, choice_value)) attrs.append((i, choice_value))
attr_id_map = self.__import(Attribute, [i[0] for i in copy.deepcopy(attrs)]) self.__import(Attribute, [i[0] for i in attrs])
for i, choice_value in attrs: for i, choice_value in attrs:
if choice_value and not i.get('choice_web_hook') and not i.get('choice_other'): if choice_value:
AttributeManager.add_choice_values(attr_id_map.get(i['id'], i['id']), i['value_type'], choice_value) AttributeManager.add_choice_values(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 @staticmethod
def _import_ci_type_relations(ci_type_relations, type_id_map, relation_type_id_map): def _import_type_attributes(type2attributes):
for i in ci_type_relations: # add type attribute
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: for type_id in type2attributes:
existed = CITypeAttributesCache.get2(type_id_map.get(int(type_id), type_id)) existed = CITypeAttribute.get_by(type_id=type_id, to_dict=False)
existed_attr_names = {attr.name: ta for ta, attr in existed} existed_attr_ids = {i.attr_id: i for i in existed}
new_attr_ids = {i['id']: i for i in type2attributes[type_id]}
handled = set()
for attr in type2attributes[type_id]: for attr in type2attributes[type_id]:
payload = dict(type_id=type_id_map.get(int(type_id), type_id), payload = dict(type_id=type_id,
attr_id=attr_id_map.get(attr['id'], attr['id']), attr_id=attr['id'],
default_show=attr['default_show'], default_show=attr['default_show'],
is_required=attr['is_required'], is_required=attr['is_required'],
order=attr['order']) order=attr['order'])
if attr['name'] not in handled: if attr['id'] not in existed_attr_ids: # new
if attr['name'] not in existed_attr_names: # new CITypeAttribute.create(flush=True, **payload)
CITypeAttribute.create(flush=True, **payload) else: # update
else: # update existed_attr_ids[attr['id']].update(**payload)
existed_attr_names[attr['name']].update(flush=True, **payload)
handled.add(attr['name']) # delete
for i in existed:
try: if i.attr_id not in new_attr_ids:
db.session.commit() i.soft_delete()
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 @staticmethod
def _import_attribute_group(type2attribute_group, type_id_map, attr_id_map): def _import_attribute_group(type2attribute_group):
for type_id in type2attribute_group: 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 []: for group in type2attribute_group[type_id] or []:
_group = copy.deepcopy(group) _group = copy.deepcopy(group)
_group.pop('attributes', None) _group.pop('attributes', None)
_group.pop('id', None) _group.pop('id', None)
existed = CITypeAttributeGroup.get_by(name=_group['name'], new = CITypeAttributeGroup.create(**_group)
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 = CITypeAttributeGroup.create(flush=True, **_group) existed = CITypeAttributeGroupItem.get_by(group_id=new.id, to_dict=False)
for i in existed:
i.soft_delete()
for order, attr in enumerate(group['attributes'] or []): for order, attr in enumerate(group['attributes'] or []):
item_existed = CITypeAttributeGroupItem.get_by(group_id=existed.id, CITypeAttributeGroupItem.create(group_id=new.id, attr_id=attr['id'], order=order)
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 @staticmethod
def _import_auto_discovery_rules(rules): 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 AutoDiscoveryRuleCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
for rule in rules: for rule in rules:
ci_type = CITypeCache.get(rule.pop('type_name', None)) ci_type = CITypeCache.get(rule.pop('type_name', None))
adr = rule.pop('adr', {}) or {}
if ci_type: if ci_type:
rule['type_id'] = ci_type.id rule['type_id'] = ci_type.id
if rule.get('adr_name'): if rule.get('adr_name'):
ad_rule = AutoDiscoveryRuleCRUD.get_by_name(rule.pop("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: if ad_rule:
rule['adr_id'] = ad_rule.id 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("id", None)
rule.pop("created_at", None) rule.pop("created_at", None)
rule.pop("updated_at", None) rule.pop("updated_at", None)
rule['uid'] = current_user.uid rule['uid'] = current_user.uid
try:
existed = False AutoDiscoveryCITypeCRUD.add(**rule)
for i in AutoDiscoveryCIType.get_by(type_id=ci_type.id, adr_id=rule['adr_id'], to_dict=False): except Exception as e:
if ((i.extra_option or {}).get('alias') or None) == ( current_app.logger.warning("import auto discovery rules failed: {}".format(e))
(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): def import_template(self, tpt):
import time import time
s = time.time() s = time.time()
attr_id_map = self._import_attributes(tpt.get('type2attributes') or {}) self._import_attributes(tpt.get('type2attributes') or {})
current_app.logger.info('import attributes cost: {}'.format(time.time() - s)) current_app.logger.info('import attributes cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
ci_type_id_map = self._import_ci_types(tpt.get('ci_types') or [], attr_id_map) self._import_ci_types(tpt.get('ci_types') or [])
current_app.logger.info('import ci_types cost: {}'.format(time.time() - s)) current_app.logger.info('import ci_types cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_ci_type_groups(tpt.get('ci_type_groups') or [], ci_type_id_map) self._import_ci_type_groups(tpt.get('ci_type_groups') or [])
current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s)) current_app.logger.info('import ci_type_groups cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
relation_type_id_map = self._import_relation_types(tpt.get('relation_types') or []) self._import_relation_types(tpt.get('relation_types') or [])
current_app.logger.info('import relation_types cost: {}'.format(time.time() - s)) current_app.logger.info('import relation_types cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_ci_type_relations(tpt.get('ci_type_relations') or [], ci_type_id_map, relation_type_id_map) self._import_ci_type_relations(tpt.get('ci_type_relations') or [])
current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s)) current_app.logger.info('import ci_type_relations cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_type_attributes(tpt.get('type2attributes') or {}, ci_type_id_map, attr_id_map) self._import_type_attributes(tpt.get('type2attributes') or {})
current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s)) current_app.logger.info('import type2attributes cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_attribute_group(tpt.get('type2attribute_group') or {}, ci_type_id_map, attr_id_map) self._import_attribute_group(tpt.get('type2attribute_group') or {})
current_app.logger.info('import type2attribute_group cost: {}'.format(time.time() - s)) current_app.logger.info('import type2attribute_group cost: {}'.format(time.time() - s))
s = time.time() s = time.time()
self._import_auto_discovery_rules(tpt.get('ci_type_auto_discovery_rules') or []) 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)) 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 @staticmethod
def export_template(): def export_template():
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryCITypeCRUD
from api.lib.cmdb.auto_discovery.auto_discovery import AutoDiscoveryRuleCRUD 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() ad_rules = AutoDiscoveryCITypeCRUD.get_all()
rules = [] rules = []
@@ -1235,91 +1116,23 @@ class CITypeTemplateManager(object):
if r.get('adr_id'): if r.get('adr_id'):
adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id')) adr = AutoDiscoveryRuleCRUD.get_by_id(r.pop('adr_id'))
r['adr_name'] = adr and adr.name 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) 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
@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( tpt = dict(
ci_types=CITypeManager.get_ci_types(type_name=ci_type.name), ci_types=CITypeManager.get_ci_types(),
ci_type_auto_discovery_rules=list(), 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(), type2attributes=dict(),
type2attribute_group=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']: 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( tpt['type2attributes'][ci_type['id']] = CITypeAttributeManager.get_attributes_by_type_id(
ci_type['id'], choice_web_hook_parse=False, choice_other_parse=False) 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']) tpt['type2attribute_group'][ci_type['id']] = CITypeAttributeGroupManager.get_by_type_id(ci_type['id'])
return tpt return tpt

View File

@@ -69,7 +69,6 @@ class ResourceTypeEnum(BaseEnum):
CI_TYPE_RELATION = "CITypeRelation" # create/delete/grant CI_TYPE_RELATION = "CITypeRelation" # create/delete/grant
RELATION_VIEW = "RelationView" # read/update/delete/grant RELATION_VIEW = "RelationView" # read/update/delete/grant
CI_FILTER = "CIFilter" # read CI_FILTER = "CIFilter" # read
PAGE = "page" # read
class PermEnum(BaseEnum): class PermEnum(BaseEnum):

View File

@@ -135,7 +135,7 @@ class AttributeHistoryManger(object):
from api.lib.cmdb.ci import CIManager from api.lib.cmdb.ci import CIManager
cis = CIManager().get_cis_by_ids(list(ci_ids), cis = CIManager().get_cis_by_ids(list(ci_ids),
unique_required=True) unique_required=True)
cis = {i['_id']: i for i in cis if i} cis = {i['_id']: i for i in cis}
return total, res, cis return total, res, cis

View File

@@ -143,14 +143,11 @@ class CIFilterPermsCRUD(DBMixin):
first=True, to_dict=False) first=True, to_dict=False)
if obj is not None: if obj is not None:
resource = None
if current_app.config.get('USE_ACL'): if current_app.config.get('USE_ACL'):
resource = ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER) ACLManager().del_resource(str(obj.id), ResourceTypeEnum.CI_FILTER)
obj.soft_delete() obj.soft_delete()
return resource
def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None): def has_perm_for_ci(arg_name, resource_type, perm, callback=None, app=None):
def decorator_has_perm(func): def decorator_has_perm(func):

View File

@@ -4,8 +4,6 @@ from api.lib.resp_format import CommonErrFormat
class ErrFormat(CommonErrFormat): class ErrFormat(CommonErrFormat):
ci_type_config = "模型配置"
invalid_relation_type = "无效的关系类型: {}" invalid_relation_type = "无效的关系类型: {}"
ci_type_not_found = "模型不存在!" ci_type_not_found = "模型不存在!"
argument_attributes_must_be_list = "参数 attributes 类型必须是列表" argument_attributes_must_be_list = "参数 attributes 类型必须是列表"

View File

@@ -28,8 +28,7 @@ class Search(object):
count=None, count=None,
sort=None, sort=None,
reverse=False, reverse=False,
ancestor_ids=None, ancestor_ids=None):
has_m2m=None):
self.orig_query = query self.orig_query = query
self.fl = fl self.fl = fl
self.facet_field = facet_field self.facet_field = facet_field
@@ -46,15 +45,14 @@ class Search(object):
level[0] if isinstance(level, list) and level else level) level[0] if isinstance(level, list) and level else level)
self.ancestor_ids = ancestor_ids self.ancestor_ids = ancestor_ids
self.has_m2m = has_m2m or False self.has_m2m = False
if not self.has_m2m: if self.ancestor_ids:
if self.ancestor_ids: self.has_m2m = True
self.has_m2m = True else:
else: level = level[0] if isinstance(level, list) and level else level
level = level[0] if isinstance(level, list) and level else level for _l, c in self.level2constraint.items():
for _l, c in self.level2constraint.items(): if _l < int(level) and c == ConstraintEnum.Many2Many:
if _l < int(level) and c == ConstraintEnum.Many2Many: self.has_m2m = True
self.has_m2m = True
def _get_ids(self, ids): def _get_ids(self, ids):
if self.level[-1] == 1 and len(ids) == 1: if self.level[-1] == 1 and len(ids) == 1:
@@ -92,12 +90,12 @@ class Search(object):
key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]])) key = list(set(["{},{}".format(i, j) for idx, i in enumerate(key) for j in _tmp[idx]]))
prefix = REDIS_PREFIX_CI_RELATION2 prefix = REDIS_PREFIX_CI_RELATION2
_tmp = list(map(lambda x: json.loads(x).keys() if x else [], rd.get(key, prefix) or []))
ids = [j for i in _tmp for j in i]
if not key: if not key:
return [] return []
_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 level in self.level: if level in self.level:
merge_ids.extend(ids) merge_ids.extend(ids)

View File

@@ -12,7 +12,7 @@ import api.models.cmdb as model
from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
TIME_RE = re.compile(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d') TIME_RE = re.compile(r"^20|21|22|23|[0-1]\d:[0-5]\d:[0-5]\d$")
def string2int(x): def string2int(x):

View File

@@ -1,24 +1,14 @@
import copy from flask import abort
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.extensions import db
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import CommonData 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): class CommonDataCRUD(object):
@staticmethod @staticmethod
def get_data_by_type(data_type): def get_data_by_type(data_type):
CommonDataCRUD.check_auth_type(data_type)
return CommonData.get_by(data_type=data_type) return CommonData.get_by(data_type=data_type)
@staticmethod @staticmethod
@@ -28,8 +18,6 @@ class CommonDataCRUD(object):
@staticmethod @staticmethod
def create_new_data(data_type, **kwargs): def create_new_data(data_type, **kwargs):
try: try:
CommonDataCRUD.check_auth_type(data_type)
return CommonData.create(data_type=data_type, **kwargs) return CommonData.create(data_type=data_type, **kwargs)
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
@@ -41,7 +29,6 @@ class CommonDataCRUD(object):
if not existed: if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id)) abort(404, ErrFormat.common_data_not_found.format(_id))
try: try:
CommonDataCRUD.check_auth_type(existed.data_type)
return existed.update(**kwargs) return existed.update(**kwargs)
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
@@ -53,230 +40,7 @@ class CommonDataCRUD(object):
if not existed: if not existed:
abort(404, ErrFormat.common_data_not_found.format(_id)) abort(404, ErrFormat.common_data_not_found.format(_id))
try: try:
CommonDataCRUD.check_auth_type(existed.data_type)
existed.soft_delete() existed.soft_delete()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
abort(400, str(e)) 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

View File

@@ -1,6 +1,4 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from urllib.parse import urlparse
from api.extensions import cache from api.extensions import cache
from api.models.common_setting import CompanyInfo from api.models.common_setting import CompanyInfo
@@ -13,7 +11,6 @@ class CompanyInfoCRUD(object):
@staticmethod @staticmethod
def create(**kwargs): def create(**kwargs):
CompanyInfoCRUD.check_data(**kwargs)
res = CompanyInfo.create(**kwargs) res = CompanyInfo.create(**kwargs)
CompanyInfoCache.refresh(res.info) CompanyInfoCache.refresh(res.info)
return res return res
@@ -25,26 +22,10 @@ class CompanyInfoCRUD(object):
if not existed: if not existed:
existed = CompanyInfoCRUD.create(**kwargs) existed = CompanyInfoCRUD.create(**kwargs)
else: else:
CompanyInfoCRUD.check_data(**kwargs)
existed = existed.update(**kwargs) existed = existed.update(**kwargs)
CompanyInfoCache.refresh(existed.info) CompanyInfoCache.refresh(existed.info)
return existed 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): class CompanyInfoCache(object):
key = 'CompanyInfoCache::' key = 'CompanyInfoCache::'
@@ -60,4 +41,4 @@ class CompanyInfoCache(object):
@classmethod @classmethod
def refresh(cls, info): def refresh(cls, info):
cache.set(cls.key, info) cache.set(cls.key, info)

View File

@@ -19,19 +19,3 @@ BotNameMap = {
'feishuApp': 'feishuBot', 'feishuApp': 'feishuBot',
'dingdingApp': 'dingdingBot', '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'

View File

@@ -15,13 +15,10 @@ from wtforms import validators
from api.extensions import db from api.extensions import db
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.common_setting.const import OperatorType from api.lib.common_setting.const import COMMON_SETTING_QUEUE, OperatorType
from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Employee, Department from api.models.common_setting import Employee, Department
from api.tasks.common_setting import refresh_employee_acl_info, edit_employee_department_in_acl
acl_user_columns = [ acl_user_columns = [
'email', 'email',
'mobile', 'mobile',
@@ -140,9 +137,7 @@ class EmployeeCRUD(object):
@staticmethod @staticmethod
def add(**kwargs): def add(**kwargs):
try: try:
res = CreateEmployee().create_single(**kwargs) return CreateEmployee().create_single(**kwargs)
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
return res
except Exception as e: except Exception as e:
abort(400, str(e)) abort(400, str(e))
@@ -169,9 +164,10 @@ class EmployeeCRUD(object):
existed.update(**kwargs) existed.update(**kwargs)
if len(e_list) > 0: if len(e_list) > 0:
from api.tasks.common_setting import edit_employee_department_in_acl
edit_employee_department_in_acl.apply_async( edit_employee_department_in_acl.apply_async(
args=(e_list, new_department_id, current_user.uid), args=(e_list, new_department_id, current_user.uid),
queue=CMDB_QUEUE queue=COMMON_SETTING_QUEUE
) )
return existed return existed
@@ -295,7 +291,7 @@ class EmployeeCRUD(object):
employees = [] employees = []
for r in pagination.items: for r in pagination.items:
d = r.Employee.to_dict() d = r.Employee.to_dict()
d['department_name'] = r.Department.department_name if r.Department else '' d['department_name'] = r.Department.department_name
employees.append(d) employees.append(d)
return { return {
@@ -441,7 +437,7 @@ class EmployeeCRUD(object):
employees = [] employees = []
for r in pagination.items: for r in pagination.items:
d = r.Employee.to_dict() d = r.Employee.to_dict()
d['department_name'] = r.Department.department_name if r.Department else '' d['department_name'] = r.Department.department_name
employees.append(d) employees.append(d)
return { return {
@@ -567,7 +563,6 @@ class EmployeeCRUD(object):
for column in direct_columns: for column in direct_columns:
tmp[column] = d.get(column, '') tmp[column] = d.get(column, '')
notice_info = d.get('notice_info', {}) notice_info = d.get('notice_info', {})
notice_info = copy.deepcopy(notice_info) if notice_info else {}
tmp.update(**notice_info) tmp.update(**notice_info)
results.append(tmp) results.append(tmp)
return results return results
@@ -575,7 +570,6 @@ class EmployeeCRUD(object):
@staticmethod @staticmethod
def import_employee(employee_list): def import_employee(employee_list):
res = CreateEmployee().batch_create(employee_list) res = CreateEmployee().batch_create(employee_list)
refresh_employee_acl_info.apply_async(args=(), queue=CMDB_QUEUE)
return res return res
@staticmethod @staticmethod
@@ -692,27 +686,6 @@ class EmployeeCRUD(object):
else: else:
abort(400, ErrFormat.column_name_not_support) 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): def get_user_map(key='uid', acl=None):
""" """
@@ -753,7 +726,6 @@ class CreateEmployee(object):
try: try:
existed = self.check_acl_user(user_data) existed = self.check_acl_user(user_data)
if not existed: if not existed:
user_data['add_from'] = 'common'
return self.acl.create_user(user_data) return self.acl.create_user(user_data)
return existed return existed
except Exception as e: except Exception as e:

View File

@@ -8,9 +8,6 @@ class ErrFormat(CommonErrFormat):
no_file_part = "没有文件部分" no_file_part = "没有文件部分"
file_is_required = "文件是必须的" file_is_required = "文件是必须的"
file_not_found = "文件不存在"
file_type_not_allowed = "文件类型不允许"
upload_failed = "上传失败: {}"
direct_supervisor_is_not_self = "直属上级不能是自己" direct_supervisor_is_not_self = "直属上级不能是自己"
parent_department_is_not_self = "上级部门不能是自己" parent_department_is_not_self = "上级部门不能是自己"
@@ -59,7 +56,6 @@ class ErrFormat(CommonErrFormat):
email_send_timeout = "邮件发送超时" email_send_timeout = "邮件发送超时"
common_data_not_found = "ID {} 找不到记录" common_data_not_found = "ID {} 找不到记录"
common_data_already_existed = "{} 已存在"
notice_platform_existed = "{} 已存在" notice_platform_existed = "{} 已存在"
notice_not_existed = "{} 配置项不存在" notice_not_existed = "{} 配置项不存在"
notice_please_config_messenger_first = "请先配置 messenger" notice_please_config_messenger_first = "请先配置 messenger"
@@ -67,11 +63,3 @@ class ErrFormat(CommonErrFormat):
notice_bind_failed = "绑定失败: {}" notice_bind_failed = "绑定失败: {}"
notice_bind_success = "绑定成功" notice_bind_success = "绑定成功"
notice_remove_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测试用户名必填"

View File

@@ -1,14 +1,6 @@
import base64
import uuid 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.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): def allowed_file(filename, allowed_extensions):
@@ -22,73 +14,3 @@ def generate_new_file_name(name):
cur_str = get_cur_time_str('_') cur_str = get_cur_time_str('_')
return f"{prev_name}_{cur_str}_{uid}.{ext}" 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}")

View File

@@ -94,7 +94,7 @@ class CRUDMixin(FormatMixin):
if any((isinstance(_id, six.string_types) and _id.isdigit(), if any((isinstance(_id, six.string_types) and _id.isdigit(),
isinstance(_id, (six.integer_types, float))), ): isinstance(_id, (six.integer_types, float))), ):
obj = getattr(cls, "query").get(int(_id)) obj = getattr(cls, "query").get(int(_id))
if obj and not getattr(obj, 'deleted', False): if obj and not obj.deleted:
return obj return obj
@classmethod @classmethod

View File

@@ -117,15 +117,15 @@ class ACLManager(object):
if group: if group:
PermissionCRUD.grant(role.id, permissions, group_id=group.id) 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, rebuild=True): def grant_resource_to_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
resource = self._get_resource(name, resource_type_name) resource = self._get_resource(name, resource_type_name)
if resource: if resource:
PermissionCRUD.grant(rid, permissions, resource_id=resource.id, rebuild=rebuild) PermissionCRUD.grant(rid, permissions, resource_id=resource.id)
else: else:
group = self._get_resource_group(name) group = self._get_resource_group(name)
if group: if group:
PermissionCRUD.grant(rid, permissions, group_id=group.id, rebuild=rebuild) PermissionCRUD.grant(rid, permissions, group_id=group.id)
def revoke_resource_from_role(self, name, role, resource_type_name=None, permissions=None): def revoke_resource_from_role(self, name, role, resource_type_name=None, permissions=None):
resource = self._get_resource(name, resource_type_name) resource = self._get_resource(name, resource_type_name)
@@ -138,20 +138,20 @@ class ACLManager(object):
if group: if group:
PermissionCRUD.revoke(role.id, permissions, group_id=group.id) 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, rebuild=True): def revoke_resource_from_role_by_rid(self, name, rid, resource_type_name=None, permissions=None):
resource = self._get_resource(name, resource_type_name) resource = self._get_resource(name, resource_type_name)
if resource: if resource:
PermissionCRUD.revoke(rid, permissions, resource_id=resource.id, rebuild=rebuild) PermissionCRUD.revoke(rid, permissions, resource_id=resource.id)
else: else:
group = self._get_resource_group(name) group = self._get_resource_group(name)
if group: if group:
PermissionCRUD.revoke(rid, permissions, group_id=group.id, rebuild=rebuild) PermissionCRUD.revoke(rid, permissions, group_id=group.id)
def del_resource(self, name, resource_type_name=None): def del_resource(self, name, resource_type_name=None):
resource = self._get_resource(name, resource_type_name) resource = self._get_resource(name, resource_type_name)
if resource: if resource:
return ResourceCRUD.delete(resource.id) ResourceCRUD.delete(resource.id)
def has_permission(self, resource_name, resource_type, perm, resource_id=None): def has_permission(self, resource_name, resource_type, perm, resource_id=None):
if is_app_admin(self.app_id): if is_app_admin(self.app_id):

View File

@@ -1,19 +1,14 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import datetime
import itertools import itertools
import json import json
from enum import Enum from enum import Enum
from typing import List from typing import List
from flask import has_request_context from flask import has_request_context, request
from flask import request
from flask_login import current_user from flask_login import current_user
from sqlalchemy import func from sqlalchemy import func
from api.extensions import db
from api.lib.perm.acl import AppCache from api.lib.perm.acl import AppCache
from api.models.acl import AuditLoginLog
from api.models.acl import AuditPermissionLog from api.models.acl import AuditPermissionLog
from api.models.acl import AuditResourceLog from api.models.acl import AuditResourceLog
from api.models.acl import AuditRoleLog from api.models.acl import AuditRoleLog
@@ -288,27 +283,6 @@ class AuditCRUD(object):
return data 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 @classmethod
def add_role_log(cls, app_id, operate_type: AuditOperateType, def add_role_log(cls, app_id, operate_type: AuditOperateType,
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict, scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
@@ -374,30 +348,3 @@ class AuditCRUD(object):
AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id, AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
operate_type=operate_type.value, operate_type=operate_type.value,
origin=origin, current=current, extra=extra, source=source.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

View File

@@ -328,8 +328,6 @@ class ResourceCRUD(object):
AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete, AuditCRUD.add_resource_log(resource.app_id, AuditOperateType.delete,
AuditScope.resource, resource.id, origin, {}, {}) AuditScope.resource, resource.id, origin, {}, {})
return rebuilds
@classmethod @classmethod
def delete_by_name(cls, name, type_id, app_id): 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( resource = Resource.get_by(name=name, resource_type_id=type_id, app_id=app_id) or abort(

View File

@@ -4,9 +4,6 @@ from api.lib.resp_format import CommonErrFormat
class ErrFormat(CommonErrFormat): class ErrFormat(CommonErrFormat):
login_succeed = "登录成功"
ldap_connection_failed = "连接LDAP服务失败"
invalid_password = "密码验证失败"
auth_only_with_app_token_failed = "应用 Token验证失败" auth_only_with_app_token_failed = "应用 Token验证失败"
session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)" session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
@@ -20,11 +17,11 @@ class ErrFormat(CommonErrFormat):
role_exists = "角色 {} 已经存在!" role_exists = "角色 {} 已经存在!"
global_role_not_found = "全局角色 {} 不存在!" global_role_not_found = "全局角色 {} 不存在!"
global_role_exists = "全局角色 {} 已经存在!" global_role_exists = "全局角色 {} 已经存在!"
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
resource_no_permission = "您没有资源: {}{} 权限" resource_no_permission = "您没有资源: {}{} 权限"
admin_required = "需要管理员权限" admin_required = "需要管理员权限"
role_required = "需要角色: {}" role_required = "需要角色: {}"
user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
app_is_ready_existed = "应用 {} 已经存在" app_is_ready_existed = "应用 {} 已经存在"
app_not_found = "应用 {} 不存在!" app_not_found = "应用 {} 不存在!"

View File

@@ -41,7 +41,6 @@ class UserCRUD(object):
@classmethod @classmethod
def add(cls, **kwargs): def add(cls, **kwargs):
add_from = kwargs.pop('add_from', None)
existed = User.get_by(username=kwargs['username']) existed = User.get_by(username=kwargs['username'])
existed and abort(400, ErrFormat.user_exists.format(kwargs['username'])) existed and abort(400, ErrFormat.user_exists.format(kwargs['username']))
@@ -63,12 +62,10 @@ class UserCRUD(object):
AuditCRUD.add_role_log(None, AuditOperateType.create, AuditCRUD.add_role_log(None, AuditOperateType.create,
AuditScope.user, user.uid, {}, user.to_dict(), {}, {} AuditScope.user, user.uid, {}, user.to_dict(), {}, {}
) )
from api.lib.common_setting.employee import EmployeeCRUD
if add_from != 'common': payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']}
from api.lib.common_setting.employee import EmployeeCRUD payload['rid'] = role.id
payload = {column: getattr(user, column) for column in ['uid', 'username', 'nickname', 'email', 'block']} EmployeeCRUD.add_employee_from_acl_created(**payload)
payload['rid'] = role.id
EmployeeCRUD.add_employee_from_acl_created(**payload)
return user return user

View File

@@ -93,9 +93,6 @@ def _auth_with_token():
def _auth_with_ip_white_list(): 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 ip = request.headers.get('X-Real-IP') or request.remote_addr
key = request.values.get('_key') key = request.values.get('_key')
secret = request.values.get('_secret') secret = request.values.get('_secret')

View File

@@ -1 +0,0 @@
# -*- coding:utf-8 -*-

View File

@@ -1,67 +0,0 @@
# -*- 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)

View File

@@ -1,30 +0,0 @@
# -*- 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

View File

@@ -1,139 +0,0 @@
# -*- 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)

View File

@@ -5,18 +5,17 @@ import copy
import hashlib import hashlib
from datetime import datetime 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 current_app
from flask import session
from flask_sqlalchemy import BaseQuery from flask_sqlalchemy import BaseQuery
from api.extensions import db from api.extensions import db
from api.lib.database import CRUDModel from api.lib.database import CRUDModel
from api.lib.database import Model from api.lib.database import Model
from api.lib.database import Model2
from api.lib.database import SoftDeleteMixin from api.lib.database import SoftDeleteMixin
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.const import OperateType from api.lib.perm.acl.const import OperateType
from api.lib.perm.acl.resp_format import ErrFormat
class App(Model): class App(Model):
@@ -29,26 +28,21 @@ class App(Model):
class UserQuery(BaseQuery): class UserQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(UserQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password): def authenticate(self, login, password):
from api.lib.perm.acl.audit import AuditCRUD
user = self.filter(db.or_(User.username == login, user = self.filter(db.or_(User.username == login,
User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first() User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
if user: if user:
current_app.logger.info(user)
authenticated = user.check_password(password) authenticated = user.check_password(password)
if authenticated: if authenticated:
_id = AuditCRUD.add_login_log(login, True, ErrFormat.login_succeed) from api.tasks.acl import op_record
session['LOGIN_ID'] = _id op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
else:
AuditCRUD.add_login_log(login, False, ErrFormat.invalid_password)
else: else:
authenticated = False 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 return user, authenticated
def authenticate_with_key(self, key, secret, args, path): def authenticate_with_key(self, key, secret, args, path):
@@ -63,6 +57,38 @@ class UserQuery(BaseQuery):
return user, authenticated 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): def search(self, key):
query = self.filter(db.or_(User.email == key, query = self.filter(db.or_(User.email == key,
User.nickname.ilike('%' + key + '%'), User.nickname.ilike('%' + key + '%'),
@@ -112,7 +138,6 @@ class User(CRUDModel, SoftDeleteMixin):
wx_id = db.Column(db.String(32)) wx_id = db.Column(db.String(32))
employee_id = db.Column(db.String(16), index=True) employee_id = db.Column(db.String(16), index=True)
avatar = db.Column(db.String(128)) avatar = db.Column(db.String(128))
# apps = db.Column(db.JSON) # apps = db.Column(db.JSON)
def __str__(self): def __str__(self):
@@ -143,9 +168,11 @@ class User(CRUDModel, SoftDeleteMixin):
class RoleQuery(BaseQuery): class RoleQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(RoleQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password): def authenticate(self, login, password):
role = self.filter(Role.name == login).filter(Role.deleted.is_(False)).first() role = self.filter(Role.name == login).first()
if role: if role:
authenticated = role.check_password(password) authenticated = role.check_password(password)
@@ -350,16 +377,3 @@ class AuditTriggerLog(Model):
current = db.Column(db.JSON, default=dict(), comment='当前数据') current = db.Column(db.JSON, default=dict(), comment='当前数据')
extra = db.Column(db.JSON, default=dict(), comment='权限名') extra = db.Column(db.JSON, default=dict(), comment='权限名')
source = db.Column(db.String(16), default='', 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)

View File

@@ -96,11 +96,3 @@ class NoticeConfig(Model):
platform = db.Column(db.VARCHAR(255), nullable=False) platform = db.Column(db.VARCHAR(255), nullable=False)
info = db.Column(db.JSON) 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)

View File

@@ -25,9 +25,10 @@ from api.models.acl import Role
from api.models.acl import Trigger from api.models.acl import Trigger
@celery.task(name="acl.role_rebuild", @celery.task(base=QueueOnce,
queue=ACL_QUEUE,) name="acl.role_rebuild",
@flush_db queue=ACL_QUEUE,
once={"graceful": True, "unlock_before_run": True})
@reconnect_db @reconnect_db
def role_rebuild(rids, app_id): def role_rebuild(rids, app_id):
rids = rids if isinstance(rids, list) else [rids] rids = rids if isinstance(rids, list) else [rids]
@@ -189,18 +190,18 @@ def cancel_trigger(_id, resource_id=None, operator_uid=None):
@celery.task(name="acl.op_record", queue=ACL_QUEUE) @celery.task(name="acl.op_record", queue=ACL_QUEUE)
@reconnect_db @reconnect_db
def op_record(app, role_name, operate_type, obj): def op_record(app, rolename, operate_type, obj):
if isinstance(app, int): if isinstance(app, int):
app = AppCache.get(app) app = AppCache.get(app)
app = app and app.name app = app and app.name
if isinstance(role_name, int): if isinstance(rolename, int):
u = UserCache.get(role_name) u = UserCache.get(rolename)
if u: if u:
role_name = u.username rolename = u.username
if not u: if not u:
r = RoleCache.get(role_name) r = RoleCache.get(rolename)
if r: if r:
role_name = r.name rolename = r.name
OperateRecordCRUD.add(app, role_name, operate_type, obj) OperateRecordCRUD.add(app, rolename, operate_type, obj)

View File

@@ -1,24 +1,24 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import requests
from flask import current_app from flask import current_app
from api.extensions import celery from api.extensions import celery
from api.extensions import db
from api.lib.common_setting.acl import ACLManager from api.lib.common_setting.acl import ACLManager
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.common_setting.const import COMMON_SETTING_QUEUE
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.models.common_setting import Department, Employee from api.models.common_setting import Department
from api.lib.decorator import flush_db
from api.lib.decorator import reconnect_db
@celery.task(name="common_setting.edit_employee_department_in_acl", queue=CMDB_QUEUE) @celery.task(name="common_setting.edit_employee_department_in_acl", queue=COMMON_SETTING_QUEUE)
@flush_db
@reconnect_db
def edit_employee_department_in_acl(e_list, new_d_id, op_uid): def edit_employee_department_in_acl(e_list, new_d_id, op_uid):
""" """
:param e_list:{acl_rid: 11, department_id: 22} :param e_list:{acl_rid: 11, department_id: 22}
:param new_d_id :param new_d_id
:param op_uid :param op_uid
""" """
db.session.remove()
result = [] result = []
new_department = Department.get_by( new_department = Department.get_by(
first=True, department_id=new_d_id, to_dict=False) first=True, department_id=new_d_id, to_dict=False)
@@ -75,41 +75,3 @@ 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))) result.append(ErrFormat.acl_add_user_to_role_failed.format(str(e)))
return result 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

View File

@@ -24,7 +24,6 @@ class AuditLogView(APIView):
'role': AuditCRUD.search_role, 'role': AuditCRUD.search_role,
'trigger': AuditCRUD.search_trigger, 'trigger': AuditCRUD.search_trigger,
'resource': AuditCRUD.search_resource, 'resource': AuditCRUD.search_resource,
'login': AuditCRUD.search_login,
} }
if name not in func_map: if name not in func_map:
abort(400, f'wrong {name}, please use {func_map.keys()}') abort(400, f'wrong {name}, please use {func_map.keys()}')

View File

@@ -8,15 +8,11 @@ from flask import abort
from flask import current_app from flask import current_app
from flask import request from flask import request
from flask import session from flask import session
from flask_login import login_user from flask_login import login_user, logout_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_required
from api.lib.decorator import args_validate from api.lib.decorator import args_validate
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.audit import AuditCRUD
from api.lib.perm.acl.cache import RoleCache from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.cache import User from api.lib.perm.acl.cache import User
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
@@ -38,10 +34,8 @@ class LoginView(APIView):
username = request.values.get("username") or request.values.get("email") username = request.values.get("username") or request.values.get("email")
password = request.values.get("password") password = request.values.get("password")
_role = None _role = None
config = AuthenticateDataCRUD(AuthenticateType.LDAP).get() if current_app.config.get('AUTH_WITH_LDAP'):
if config.get('enabled') or config.get('enable'): user, authenticated = User.query.authenticate_with_ldap(username, password)
from api.lib.perm.authentication.ldap import authenticate_with_ldap
user, authenticated = authenticate_with_ldap(username, password)
else: else:
user, authenticated = User.query.authenticate(username, password) user, authenticated = User.query.authenticate(username, password)
if not user: if not user:
@@ -182,7 +176,4 @@ class LogoutView(APIView):
@auth_abandoned @auth_abandoned
def post(self): def post(self):
logout_user() logout_user()
AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
self.jsonify(code=200) self.jsonify(code=200)

View File

@@ -43,11 +43,9 @@ class CIRelationSearchView(APIView):
facet = handle_arg_list(request.values.get("facet", "")) facet = handle_arg_list(request.values.get("facet", ""))
sort = request.values.get("sort") sort = request.values.get("sort")
reverse = request.values.get("reverse") in current_app.config.get('BOOL_TRUE') 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() 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)
ancestor_ids=ancestor_ids, has_m2m=has_m2m)
try: try:
response, counter, total, page, numfound, facet = s.search() response, counter, total, page, numfound, facet = s.search()
except SearchError as e: except SearchError as e:
@@ -71,10 +69,9 @@ class CIRelationStatisticsView(APIView):
level = request.values.get('level', 1) level = request.values.get('level', 1)
type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', [])))) type_ids = set(map(int, handle_arg_list(request.values.get('type_ids', []))))
ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many ancestor_ids = request.values.get('ancestor_ids') or None # only for many to many
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
start = time.time() start = time.time()
s = Search(root_ids, level, ancestor_ids=ancestor_ids, has_m2m=has_m2m) s = Search(root_ids, level, ancestor_ids=ancestor_ids)
try: try:
result = s.statistics(type_ids) result = s.statistics(type_ids)
except SearchError as e: except SearchError as e:

View File

@@ -166,8 +166,7 @@ class CITypeAttributeView(APIView):
t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found) t = CITypeCache.get(type_id) or CITypeCache.get(type_name) or abort(404, ErrFormat.ci_type_not_found)
type_id = t.id type_id = t.id
unique_id = t.unique_id unique_id = t.unique_id
unique = AttributeCache.get(unique_id) unique = AttributeCache.get(unique_id).name
unique = unique and unique.name
attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id) attr_filter = CIFilterPermsCRUD.get_attr_filter(type_id)
attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id) attributes = CITypeAttributeManager.get_attributes_by_type_id(type_id)
@@ -319,14 +318,12 @@ class CITypeAttributeGroupView(APIView):
class CITypeTemplateView(APIView): class CITypeTemplateView(APIView):
url_prefix = ("/ci_types/template/import", "/ci_types/template/export", "/ci_types/<int:type_id>/template/export") url_prefix = ("/ci_types/template/import", "/ci_types/template/export")
@role_required(RoleEnum.CONFIG) @role_required(RoleEnum.CONFIG)
def get(self, type_id=None): # export def get(self): # export
if type_id is not None: return self.jsonify(
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template_by_type(type_id))) dict(ci_type_template=CITypeTemplateManager.export_template()))
return self.jsonify(dict(ci_type_template=CITypeTemplateManager.export_template()))
@role_required(RoleEnum.CONFIG) @role_required(RoleEnum.CONFIG)
def post(self): # import def post(self): # import
@@ -460,21 +457,13 @@ class CITypeGrantView(APIView):
_type = CITypeCache.get(type_id) _type = CITypeCache.get(type_id)
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found) type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
acl = ACLManager('cmdb') 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)) return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
if request.values.get('ci_filter') or request.values.get('attr_filter'): CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values)
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) return self.jsonify(code=200)
@@ -492,27 +481,21 @@ class CITypeRevokeView(APIView):
_type = CITypeCache.get(type_id) _type = CITypeCache.get(type_id)
type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found) type_name = _type and _type.name or abort(404, ErrFormat.ci_type_not_found)
acl = ACLManager('cmdb') 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)) return abort(403, ErrFormat.no_permission.format(type_name, PermEnum.GRANT))
acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) acl.revoke_resource_from_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms)
app_id = AppCache.get('cmdb').id if PermEnum.READ in perms:
resource = None CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
if PermEnum.READ in perms or not perms:
resource = CIFilterPermsCRUD().delete(type_id=type_id, rid=rid)
if not resource: app_id = AppCache.get('cmdb').id
from api.tasks.acl import role_rebuild users = RoleRelationCRUD.get_users_by_rid(rid, app_id)
from api.lib.perm.acl.const import ACL_QUEUE for i in (users or []):
if i.get('role', {}).get('id') and not RoleCRUD.has_permission(
role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE) i.get('role').get('id'), type_name, ResourceTypeEnum.CI_TYPE, app_id, PermEnum.READ):
PreferenceManager.delete_by_type_id(type_id, i.get('uid'))
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) return self.jsonify(type_id=type_id, rid=rid)

View File

@@ -1,88 +0,0 @@
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')))

View File

@@ -24,12 +24,12 @@ class DataView(APIView):
class DataViewWithId(APIView): class DataViewWithId(APIView):
url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',) url_prefix = (f'{prefix}/<string:data_type>/<int:_id>',)
def put(self, data_type, _id): def put(self, _id):
params = request.json params = request.json
res = CommonDataCRUD.update_data(_id, **params) res = CommonDataCRUD.update_data(_id, **params)
return self.jsonify(res.to_dict()) return self.jsonify(res.to_dict())
def delete(self, data_type, _id): def delete(self, _id):
CommonDataCRUD.delete(_id) CommonDataCRUD.delete(_id)
return self.jsonify({}) return self.jsonify({})

View File

@@ -3,10 +3,9 @@ import os
from flask import request, abort, current_app, send_from_directory from flask import request, abort, current_app, send_from_directory
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import lz4.frame
from api.lib.common_setting.resp_format import ErrFormat from api.lib.common_setting.resp_format import ErrFormat
from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name, CommonFileCRUD from api.lib.common_setting.upload_file import allowed_file, generate_new_file_name
from api.resource import APIView from api.resource import APIView
prefix = '/file' prefix = '/file'
@@ -29,8 +28,7 @@ class GetFileView(APIView):
url_prefix = (f'{prefix}/<string:_filename>',) url_prefix = (f'{prefix}/<string:_filename>',)
def get(self, _filename): def get(self, _filename):
file_stream = CommonFileCRUD.get_file(_filename) return send_from_directory(current_app.config['UPLOAD_DIRECTORY_FULL'], _filename, as_attachment=True)
return self.send_file(file_stream, as_attachment=True, download_name=_filename)
class PostFileView(APIView): class PostFileView(APIView):
@@ -46,8 +44,6 @@ class PostFileView(APIView):
if not file: if not file:
abort(400, ErrFormat.file_is_required) abort(400, ErrFormat.file_is_required)
extension = file.mimetype.split('/')[-1] extension = file.mimetype.split('/')[-1]
if '+' in extension:
extension = file.filename.split('.')[-1]
if file.filename == '': if file.filename == '':
filename = f'.{extension}' filename = f'.{extension}'
else: else:
@@ -57,20 +53,11 @@ class PostFileView(APIView):
filename = file.filename filename = file.filename
if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)): if allowed_file(filename, current_app.config.get('ALLOWED_EXTENSIONS', ALLOWED_EXTENSIONS)):
new_filename = generate_new_file_name(filename) filename = generate_new_file_name(filename)
new_filename = secure_filename(new_filename) filename = secure_filename(filename)
file_content = file.read() file.save(os.path.join(
compressed_data = lz4.frame.compress(file_content) current_app.config['UPLOAD_DIRECTORY_FULL'], filename))
try:
CommonFileCRUD.add_file(
origin_name=filename,
file_name=new_filename,
binary=compressed_data,
)
return self.jsonify(file_name=new_filename) return self.jsonify(file_name=filename)
except Exception as e:
current_app.logger.error(e)
abort(400, ErrFormat.upload_failed.format(e))
abort(400, ErrFormat.file_type_not_allowed.format(filename)) abort(400, 'Extension not allow')

View File

@@ -34,7 +34,7 @@ cryptography>=41.0.2
PyJWT==2.4.0 PyJWT==2.4.0
PyMySQL==1.1.0 PyMySQL==1.1.0
ldap3==2.9.1 ldap3==2.9.1
PyYAML==6.0.1 PyYAML==6.0
redis==4.6.0 redis==4.6.0
requests==2.31.0 requests==2.31.0
requests_oauthlib==1.3.1 requests_oauthlib==1.3.1
@@ -48,6 +48,6 @@ treelib==1.6.1
Werkzeug>=2.3.6 Werkzeug>=2.3.6
WTForms==3.0.0 WTForms==3.0.0
shamir~=17.12.0 shamir~=17.12.0
hvac~=2.0.0
pycryptodomex>=3.19.0 pycryptodomex>=3.19.0
colorama>=0.4.6 colorama>=0.4.6
lz4>=4.3.2

View File

@@ -11,10 +11,10 @@ from environs import Env
env = Env() env = Env()
env.read_env() env.read_env()
ENV = env.str('FLASK_ENV', default='production') ENV = env.str("FLASK_ENV", default="production")
DEBUG = ENV == 'development' DEBUG = ENV == "development"
SECRET_KEY = env.str('SECRET_KEY') SECRET_KEY = env.str("SECRET_KEY")
BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13) BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
DEBUG_TB_ENABLED = DEBUG DEBUG_TB_ENABLED = DEBUG
DEBUG_TB_INTERCEPT_REDIRECTS = False DEBUG_TB_INTERCEPT_REDIRECTS = False
@@ -23,7 +23,7 @@ ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
# # database # # database
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8' SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
SQLALCHEMY_BINDS = { 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_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -32,11 +32,11 @@ SQLALCHEMY_ENGINE_OPTIONS = {
} }
# # cache # # cache
CACHE_TYPE = 'redis' CACHE_TYPE = "redis"
CACHE_REDIS_HOST = '127.0.0.1' CACHE_REDIS_HOST = "127.0.0.1"
CACHE_REDIS_PORT = 6379 CACHE_REDIS_PORT = 6379
CACHE_REDIS_PASSWORD = '' CACHE_REDIS_PASSWORD = ""
CACHE_KEY_PREFIX = 'CMDB::' CACHE_KEY_PREFIX = "CMDB::"
CACHE_DEFAULT_TIMEOUT = 3000 CACHE_DEFAULT_TIMEOUT = 3000
# # log # # log
@@ -55,10 +55,10 @@ DEFAULT_MAIL_SENDER = ''
# # queue # # queue
CELERY = { CELERY = {
'broker_url': 'redis://127.0.0.1:6379/2', "broker_url": 'redis://127.0.0.1:6379/2',
'result_backend': 'redis://127.0.0.1:6379/2', "result_backend": "redis://127.0.0.1:6379/2",
'broker_vhost': '/', "broker_vhost": "/",
'broker_connection_retry_on_startup': True "broker_connection_retry_on_startup": True
} }
ONCE = { ONCE = {
'backend': 'celery_once.backends.Redis', 'backend': 'celery_once.backends.Redis',
@@ -67,81 +67,33 @@ ONCE = {
} }
} }
# =============================== Authentication =========================================================== # # 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"
# # CAS # # ldap
CAS = dict( AUTH_WITH_LDAP = False
enabled=False, LDAP_SERVER = ''
cas_server='https://{your-CASServer-hostname}', LDAP_DOMAIN = ''
cas_validate_server='https://{your-CASServer-hostname}', LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
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 # # pagination
DEFAULT_PAGE_COUNT = 50 DEFAULT_PAGE_COUNT = 50
# # permission # # permission
WHITE_LIST = ['127.0.0.1'] WHITE_LIST = ["127.0.0.1"]
USE_ACL = True USE_ACL = True
# # elastic search # # elastic search
ES_HOST = '127.0.0.1' ES_HOST = '127.0.0.1'
USE_ES = False 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 # # messenger
USE_MESSENGER = True USE_MESSENGER = True

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1702544951995') format('woff2'), src: url('iconfont.woff2?t=1698273699449') format('woff2'),
url('iconfont.woff?t=1702544951995') format('woff'), url('iconfont.woff?t=1698273699449') format('woff'),
url('iconfont.ttf?t=1702544951995') format('truetype'); url('iconfont.ttf?t=1698273699449') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,274 +13,6 @@
-moz-osx-font-smoothing: grayscale; -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 { .icon-xianxing-password:before {
content: "\e894"; content: "\e894";
} }
@@ -289,11 +21,11 @@
content: "\e895"; content: "\e895";
} }
.itsm-download-all:before { .a-itsm-oneclickdownload:before {
content: "\e892"; content: "\e892";
} }
.itsm-download-package:before { .a-itsm-packagedownload:before {
content: "\e893"; content: "\e893";
} }

File diff suppressed because one or more lines are too long

View File

@@ -5,475 +5,6 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "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", "icon_id": "37830610",
"name": "icon-xianxing-password", "name": "icon-xianxing-password",
@@ -491,14 +22,14 @@
{ {
"icon_id": "37822199", "icon_id": "37822199",
"name": "itsm-oneclick download", "name": "itsm-oneclick download",
"font_class": "itsm-download-all", "font_class": "a-itsm-oneclickdownload",
"unicode": "e892", "unicode": "e892",
"unicode_decimal": 59538 "unicode_decimal": 59538
}, },
{ {
"icon_id": "37822198", "icon_id": "37822198",
"name": "itsm-package download", "name": "itsm-package download",
"font_class": "itsm-download-package", "font_class": "a-itsm-packagedownload",
"unicode": "e893", "unicode": "e893",
"unicode_decimal": 59539 "unicode_decimal": 59539
}, },

Binary file not shown.

View File

@@ -1,39 +0,0 @@
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,
})
}

View File

@@ -1,6 +1,8 @@
import config from '@/config/setting'
const api = { const api = {
Login: '/v1/acl/login', Login: config.useSSO ? '/api/sso/login' : '/v1/acl/login',
Logout: '/v1/acl/logout', Logout: config.useSSO ? '/api/sso/logout' : '/v1/acl/logout',
ForgePassword: '/auth/forge-password', ForgePassword: '/auth/forge-password',
Register: '/auth/register', Register: '/auth/register',
twoStepCode: '/auth/2step-code', twoStepCode: '/auth/2step-code',

View File

@@ -1,5 +1,6 @@
import api from './index' import api from './index'
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
import config from '@/config/setting'
/** /**
* login func * login func
* parameter: { * parameter: {
@@ -11,10 +12,9 @@ import { axios } from '@/utils/request'
* @param parameter * @param parameter
* @returns {*} * @returns {*}
*/ */
export function login(data, auth_type) { export function login(data) {
if (auth_type) { if (config.useSSO) {
localStorage.setItem('ops_auth_type', auth_type) window.location.href = config.ssoLoginUrl
window.location.href = `/api/${auth_type.toLowerCase()}/login`
} else { } else {
return axios({ return axios({
url: api.Login, url: api.Login,
@@ -43,15 +43,17 @@ export function getInfo() {
} }
export function logout() { export function logout() {
const auth_type = localStorage.getItem('ops_auth_type') if (config.useSSO) {
localStorage.clear() window.location.replace(api.Logout)
return axios({ } else {
url: auth_type ? `/${auth_type.toLowerCase()}/logout` : api.Logout, return axios({
method: auth_type ? 'get' : 'post', url: api.Logout,
headers: { method: 'post',
'Content-Type': 'application/json;charset=UTF-8' headers: {
} 'Content-Type': 'application/json;charset=UTF-8'
}) }
})
}
} }
/** /**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -95,10 +95,6 @@ export default {
const unsubTree = subscribeTreeView(citypeId, '') const unsubTree = subscribeTreeView(citypeId, '')
Promise.all([unsubCIType, unsubTree]).then(() => { Promise.all([unsubCIType, unsubTree]).then(() => {
that.$message.success('取消订阅成功') 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 href = window.location.href
const hrefSplit = href.split('/') const hrefSplit = href.split('/')

View File

@@ -66,8 +66,10 @@ export default {
this.$confirm({ this.$confirm({
title: '提示', title: '提示',
content: '确认注销登录 ?', content: '真的要注销登录 ?',
onOk() { onOk() {
// localStorage.removeItem('ops_cityps_currentId')
localStorage.clear()
return that.Logout() return that.Logout()
}, },
onCancel() {}, onCancel() {},

View File

@@ -2,6 +2,7 @@ const appConfig = {
buildModules: ['cmdb', 'acl'], // 需要编译的模块 buildModules: ['cmdb', 'acl'], // 需要编译的模块
redirectTo: '/cmdb', // 首页的重定向路径 redirectTo: '/cmdb', // 首页的重定向路径
buildAclToModules: true, // 是否在各个应用下 内联权限管理 buildAclToModules: true, // 是否在各个应用下 内联权限管理
ssoLogoutURL: '/api/sso/logout',
showDocs: false, showDocs: false,
useEncryption: false, useEncryption: false,
} }

View File

@@ -1,5 +1,6 @@
/** /**
* 项目默认配置项 * 项目默认配置项
* useSSO - 是否启用单点登录, 默认为否, 可以根据需要接入到公司的单点登录系统
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
* navTheme - sidebar theme ['dark', 'light'] 两种主题 * navTheme - sidebar theme ['dark', 'light'] 两种主题
* colorWeak - 色盲模式 * colorWeak - 色盲模式
@@ -14,6 +15,8 @@
*/ */
export default { export default {
useSSO: false,
ssoLoginUrl: '/api/sso/login',
primaryColor: '#1890ff', // primary color of ant design primaryColor: '#1890ff', // primary color of ant design
navTheme: 'dark', // theme for nav menu navTheme: 'dark', // theme for nav menu
layout: 'sidemenu', // nav menu position: sidemenu or topmenu layout: 'sidemenu', // nav menu position: sidemenu or topmenu

View File

@@ -6,6 +6,7 @@ import store from './store'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import 'nprogress/nprogress.css' import 'nprogress/nprogress.css'
import { setDocumentTitle, domTitle } from '@/utils/domUtil' import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import config from '@/config/setting'
import { ACCESS_TOKEN } from './store/global/mutation-types' import { ACCESS_TOKEN } from './store/global/mutation-types'
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
@@ -15,16 +16,16 @@ const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/lo
// 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录 // 此处不处理登录, 只处理 是否有用户信息的认证 前端permission的处理 axios处理401 -> 登录
// 登录页面处理处理 是否使用单点登录 // 登录页面处理处理 是否使用单点登录
router.beforeEach(async (to, from, next) => { router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar NProgress.start() // start progress bar
to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`)) to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
const authed = store.state.authed const authed = store.state.authed
const auth_type = localStorage.getItem('ops_auth_type')
if (whitePath.includes(to.path)) { if (whitePath.includes(to.path)) {
next() next()
} else if ((auth_type || (!auth_type && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) { } else if ((config.useSSO || (!config.useSSO && Vue.ls.get(ACCESS_TOKEN))) && store.getters.roles.length === 0) {
store.dispatch('GetAuthDataEnable')
store.dispatch('GetInfo').then(res => { store.dispatch('GetInfo').then(res => {
const roles = res.result && res.result.role const roles = res.result && res.result.role
store.dispatch("loadAllUsers") store.dispatch("loadAllUsers")
@@ -45,17 +46,10 @@ router.beforeEach(async (to, from, next) => {
}).catch((e) => { }).catch((e) => {
setTimeout(() => { store.dispatch('Logout') }, 3000) setTimeout(() => { store.dispatch('Logout') }, 3000)
}) })
} else if (to.path === '/user/login' && !auth_type && store.getters.roles.length !== 0) { } else if (to.path === '/user/login' && !config.useSSO && store.getters.roles.length !== 0) {
next({ path: '/' }) next({ path: '/' })
} else if (!auth_type && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') { } else if (!config.useSSO && !Vue.ls.get(ACCESS_TOKEN) && to.path !== '/user/login') {
await store.dispatch('GetAuthDataEnable') next({ path: '/user/login', query: { redirect: to.fullPath } })
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 { } else {
next() next()
} }

View File

@@ -65,7 +65,8 @@ const genAclRoutes = async () => {
path: `/acl/operate_history`, path: `/acl/operate_history`,
name: 'acl_operate_history', name: 'acl_operate_history',
component: () => import('../views/operation_history/index.vue'), component: () => import('../views/operation_history/index.vue'),
meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] }, // meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] },
meta: { title: '操作审计', icon: 'search' }
}, },
{ {
path: `/acl/user`, path: `/acl/user`,

View File

@@ -133,8 +133,8 @@ export default {
if (newVal) { if (newVal) {
this.tableData = this.allUsers.filter( this.tableData = this.allUsers.filter(
(item) => (item) =>
(item.username && item.username.toLowerCase().includes(newVal.toLowerCase())) || item.username.toLowerCase().includes(newVal.toLowerCase()) ||
(item.nickname && item.nickname.toLowerCase().includes(newVal.toLowerCase())) item.nickname.toLowerCase().includes(newVal.toLowerCase())
) )
} else { } else {
this.tableData = this.allUsers this.tableData = this.allUsers

View File

@@ -16,14 +16,12 @@ export function processFile(fileObj) {
} }
export function uploadData(ciId, data) { export function uploadData(ciId, data) {
data.ci_type = ciId
data.exist_policy = 'replace'
return axios({ return axios({
url: '/v0.1/ci', url: '/v0.1/ci',
method: 'POST', method: 'POST',
data: { data,
...data,
ci_type: ciId,
exist_policy: 'replace'
},
isShowMessage: false isShowMessage: false
}) })
} }

View File

@@ -111,14 +111,12 @@ export default {
}, },
methods: { methods: {
...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']), ...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']),
open({ preferenceAttrList, ciTypeName = undefined }) { open({ preferenceAttrList }) {
this.preferenceAttrList = preferenceAttrList this.preferenceAttrList = preferenceAttrList
this.visible = true this.visible = true
this.$nextTick((res) => { this.$nextTick((res) => {
this.form.setFieldsValue({ this.form.setFieldsValue({
filename: ciTypeName filename: `cmdb-${moment().format('YYYYMMDDHHmmss')}`,
? `cmdb-${ciTypeName}-${moment().format('YYYYMMDDHHmmss')}`
: `cmdb-${moment().format('YYYYMMDDHHmmss')}`,
}) })
if (this.treeType === 'tree') { if (this.treeType === 'tree') {
const _check = ['ci_type_alias'] const _check = ['ci_type_alias']

View File

@@ -77,7 +77,7 @@ const genCmdbRoutes = async () => {
path: '/cmdb/ci_types', path: '/cmdb/ci_types',
name: 'ci_type', name: 'ci_type',
component: () => import('../views/ci_types/index'), component: () => import('../views/ci_types/index'),
meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] } meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false }
}, },
{ {
path: '/cmdb/disabled3', path: '/cmdb/disabled3',

View File

@@ -1,16 +1,11 @@
<template> <template>
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }"> <div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
<div id="title"> <div id="title">
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" /> <ci-type-choice @getCiTypeAttr="showCiType" />
</div> </div>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<upload-file-form <upload-file-form :ciType="ciType" ref="uploadFileForm" @uploadDone="uploadDone"></upload-file-form>
:isUploading="isUploading"
:ciType="ciType"
ref="uploadFileForm"
@uploadDone="uploadDone"
></upload-file-form>
</a-col> </a-col>
<a-col :span="24" v-if="ciType && uploadData.length"> <a-col :span="24" v-if="ciType && uploadData.length">
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
@@ -18,19 +13,15 @@
<a-space size="large"> <a-space size="large">
<a-button type="primary" ghost @click="handleCancel">取消</a-button> <a-button type="primary" ghost @click="handleCancel">取消</a-button>
<a-button @click="handleUpload" type="primary">上传</a-button> <a-button @click="handleUpload" type="primary">上传</a-button>
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">失败下载</a-button>
</a-space> </a-space>
</div> </div>
</a-col> </a-col>
<a-col :span="24" v-if="ciType"> <a-col :span="24">
<upload-result <upload-result
ref="uploadResult" ref="uploadResult"
:upLoadData="uploadData" :upLoadData="uploadData"
:ciType="ciType" :ciType="ciType"
:unique-field="uniqueField" :unique-field="uniqueField"
:isUploading="isUploading"
@uploadResultDone="uploadResultDone"
@uploadResultError="uploadResultError"
></upload-result> ></upload-result>
</a-col> </a-col>
</a-row> </a-row>
@@ -38,7 +29,6 @@
</template> </template>
<script> <script>
import moment from 'moment'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import CiTypeChoice from './modules/CiTypeChoice' import CiTypeChoice from './modules/CiTypeChoice'
import CiUploadTable from './modules/CiUploadTable' import CiUploadTable from './modules/CiUploadTable'
@@ -61,8 +51,7 @@ export default {
ciType: 0, ciType: 0,
uniqueField: '', uniqueField: '',
uniqueId: 0, uniqueId: 0,
isUploading: false, displayUpload: true,
hasError: false,
} }
}, },
computed: { computed: {
@@ -70,12 +59,13 @@ export default {
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
}), }),
}, },
inject: ['reload'],
methods: { methods: {
showCiType(message) { showCiType(message) {
this.ciTypeAttrs = message ?? {} this.ciTypeAttrs = message
this.ciType = message?.type_id ?? 0 this.ciType = message.type_id
this.uniqueField = message?.unique ?? '' this.uniqueField = message.unique
this.uniqueId = message?.unique_id ?? 0 this.uniqueId = message.unique_id
}, },
uploadDone(dataList) { uploadDone(dataList) {
const _uploadData = filterNull(dataList).map((item, i) => { const _uploadData = filterNull(dataList).map((item, i) => {
@@ -83,20 +73,7 @@ export default {
const _ele = {} const _ele = {}
item.forEach((ele, j) => { item.forEach((ele, j) => {
if (ele !== undefined && ele !== null) { if (ele !== undefined && ele !== null) {
const _find = this.ciTypeAttrs.attributes.find( _ele[dataList[0][j]] = ele
(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 return _ele
@@ -104,8 +81,6 @@ export default {
return item return item
}) })
this.uploadData = _uploadData.slice(1) this.uploadData = _uploadData.slice(1)
this.hasError = false
this.isUploading = false
}, },
handleUpload() { handleUpload() {
if (!this.ciType) { if (!this.ciType) {
@@ -113,7 +88,6 @@ export default {
return return
} }
if (this.uploadData && this.uploadData.length > 0) { if (this.uploadData && this.uploadData.length > 0) {
this.isUploading = true
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.uploadResult.upload2Server() this.$refs.uploadResult.upload2Server()
}) })
@@ -122,24 +96,7 @@ export default {
} }
}, },
handleCancel() { handleCancel() {
if (!this.isUploading) { this.reload()
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()
}, },
}, },
} }

View File

@@ -8,7 +8,6 @@
:style="{ width: '300px' }" :style="{ width: '300px' }"
class="ops-select" class="ops-select"
:filter-option="filterOption" :filter-option="filterOption"
v-model="selectNum"
> >
<a-select-option v-for="ciType in ciTypeList" :key="ciType.name" :value="ciType.id">{{ <a-select-option v-for="ciType in ciTypeList" :key="ciType.name" :value="ciType.id">{{
ciType.alias ciType.alias
@@ -89,12 +88,10 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { mapState } from 'vuex'
import { downloadExcel } from '../../../utils/helper' import { downloadExcel } from '../../../utils/helper'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation' import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
export default { export default {
name: 'CiTypeChoice', name: 'CiTypeChoice',
@@ -102,7 +99,7 @@ export default {
return { return {
ciTypeList: [], ciTypeList: [],
ciTypeName: '', ciTypeName: '',
selectNum: null, selectNum: 0,
selectCiTypeAttrList: [], selectCiTypeAttrList: [],
visible: false, visible: false,
checkedAttrs: [], checkedAttrs: [],
@@ -114,21 +111,9 @@ export default {
canEdit: {}, canEdit: {},
} }
}, },
computed: { created: function() {
...mapState({
rid: (state) => state.user.rid,
}),
},
async created() {
const { resources } = await searchPermResourceByRoleId(this.rid, {
resource_type_id: 'CIType',
app_id: 'cmdb',
})
getCITypes().then((res) => { getCITypes().then((res) => {
this.ciTypeList = res.ci_types.filter((type) => { this.ciTypeList = res.ci_types
const _findRe = resources.find((re) => re.name === type.name)
return _findRe?.permissions.includes('create') ?? false
})
}) })
}, },
watch: { watch: {
@@ -146,6 +131,7 @@ export default {
methods: { methods: {
selectCiType(el) { selectCiType(el) {
// 当选择好模板类型时的回调函数 // 当选择好模板类型时的回调函数
this.selectNum = el
getCITypeAttributesById(el).then((res) => { getCITypeAttributesById(el).then((res) => {
this.$emit('getCiTypeAttr', res) this.$emit('getCiTypeAttr', res)
this.selectCiTypeAttrList = res this.selectCiTypeAttrList = res
@@ -169,6 +155,7 @@ export default {
}) })
} }
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id]) this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
const _parentsForm = {} const _parentsForm = {}
res.parents.forEach((item) => { res.parents.forEach((item) => {
const _find = item.attributes.find((attr) => attr.id === item.unique_id) const _find = item.attributes.find((attr) => attr.id === item.unique_id)

View File

@@ -1,7 +1,6 @@
<template> <template>
<div class="cmdb-batch-upload-table"> <div class="cmdb-batch-upload-table">
<vxe-table <vxe-table
ref="xTable"
stripe stripe
show-header-overflow show-header-overflow
show-overflow="" show-overflow=""
@@ -9,8 +8,6 @@
class="ops-stripe-table" class="ops-stripe-table"
:max-height="200" :max-height="200"
:data="dataSource" :data="dataSource"
resizable
:row-style="rowStyle"
> >
<vxe-column type="seq" width="40" /> <vxe-column type="seq" width="40" />
<vxe-column <vxe-column
@@ -39,9 +36,7 @@ export default {
}, },
}, },
data() { data() {
return { return {}
errorIndexList: [],
}
}, },
computed: { computed: {
columns() { columns() {
@@ -69,33 +64,7 @@ export default {
return _.cloneDeep(this.uploadData) return _.cloneDeep(this.uploadData)
}, },
}, },
watch: { methods: {},
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> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -7,7 +7,7 @@
accept=".xls,.xlsx" accept=".xls,.xlsx"
:showUploadList="false" :showUploadList="false"
:fileList="fileList" :fileList="fileList"
:disabled="!ciType || isUploading" :disabled="!ciType"
> >
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> <img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
<p class="ant-upload-text">点击或拖拽文件至此上传</p> <p class="ant-upload-text">点击或拖拽文件至此上传</p>
@@ -29,11 +29,7 @@ export default {
ciType: { ciType: {
type: Number, type: Number,
default: 0, default: 0,
}, }
isUploading: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@@ -44,20 +40,7 @@ export default {
percent: 0, 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: { methods: {
customRequest(data) { customRequest(data) {
this.fileList = [data.file] this.fileList = [data.file]

View File

@@ -34,10 +34,6 @@ export default {
required: true, required: true,
type: String, type: String,
}, },
isUploading: {
type: Boolean,
default: false,
},
}, },
data: function() { data: function() {
return { return {
@@ -55,38 +51,33 @@ export default {
}, },
}, },
methods: { methods: {
async sleep(n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, n || 5)
})
},
async upload2Server() { async upload2Server() {
this.visible = true this.visible = true
this.success = 0 this.success = 0
this.errorNum = 0 this.errorNum = 0
this.errorItems = [] this.errorItems = []
const floor = Math.ceil(this.total / 6) for (let i = 0; i < this.total; i++) {
for (let i = 0; i < floor; i++) { // await this.sleep(20)
if (this.isUploading) { const item = this.upLoadData[i]
const itemList = this.upLoadData.slice(6 * i, 6 * i + 6) await uploadData(this.ciType, item)
const promises = itemList.map((x) => uploadData(this.ciType, x)) .then((res) => {
await Promise.allSettled(promises) console.log(res)
.then((res) => { this.success += 1
res.forEach((r, j) => { })
if (r.status === 'fulfilled') { .catch((err) => {
this.success += 1 this.errorNum += 1
} else { this.errorItems.push(((err.response || {}).data || {}).message || '请求出现错误,请稍后再试')
this.errorItems.push(r?.reason?.response?.data.message ?? '请求出现错误,请稍后再试') })
this.errorNum += 1 .finally(() => {
this.$emit('uploadResultError', 6 * i + j) this.complete += 1
} })
})
})
.finally(() => {
this.complete += 6
})
} else {
break
}
}
if (this.isUploading) {
this.$emit('uploadResultDone')
this.$message.success('批量上传已完成')
} }
}, },
}, },

View File

@@ -108,7 +108,7 @@
<span>{{ col.title }}</span> <span>{{ col.title }}</span>
</span> </span>
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -145,18 +145,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -576,10 +564,7 @@ export default {
}, },
async openBatchDownload() { async openBatchDownload() {
this.$refs.batchDownload.open({ this.$refs.batchDownload.open({ preferenceAttrList: this.preferenceAttrList })
preferenceAttrList: this.preferenceAttrList,
ciTypeName: this.$route.meta.title || this.$route.meta.name,
})
}, },
batchDownload({ filename, type, checkedKeys }) { batchDownload({ filename, type, checkedKeys }) {
const jsonAttrList = [] const jsonAttrList = []
@@ -669,19 +654,13 @@ export default {
let errorNum = 0 let errorNum = 0
this.loading = true this.loading = true
this.loadTip = `正在删除...` this.loadTip = `正在删除...`
const floor = Math.ceil(this.selectedRowKeys.length / 6) for (let i = 0; i < this.selectedRowKeys.length; i++) {
for (let i = 0; i < floor; i++) { await deleteCI(this.selectedRowKeys[i], false)
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6) .then(() => {
const promises = itemList.map((x) => deleteCI(x, false)) successNum += 1
await Promise.allSettled(promises) })
.then((res) => { .catch(() => {
res.forEach((r) => { errorNum += 1
if (r.status === 'fulfilled') {
successNum += 1
} else {
errorNum += 1
}
})
}) })
.finally(() => { .finally(() => {
this.loadTip = `正在删除${this.selectedRowKeys.length}成功${successNum}失败${errorNum}` this.loadTip = `正在删除${this.selectedRowKeys.length}成功${successNum}失败${errorNum}`
@@ -894,10 +873,6 @@ export default {
unsubscribe(ciType, type = 'all') { unsubscribe(ciType, type = 'all') {
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')] const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')]
Promise.all(promises).then(() => { 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.$message.success('取消订阅成功')
this.resetRoute() this.resetRoute()
this.$router.push('/cmdb/preference') this.$router.push('/cmdb/preference')

View File

@@ -276,16 +276,15 @@ export default {
}, },
mergeRowMethod({ row, _rowIndex, column, visibleData }) { mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['created_at', 'username'] const fields = ['created_at', 'username']
const cellValue1 = row['created_at'] const cellValue = row[column.property]
const cellValue2 = row['username'] if (cellValue && fields.includes(column.property)) {
if (cellValue1 && cellValue2 && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { if (prevRow && prevRow[column.property] === cellValue) {
return { rowspan: 0, colspan: 0 } return { rowspan: 0, colspan: 0 }
} else { } else {
let countRowspan = 1 let countRowspan = 1
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { while (nextRow && nextRow[column.property] === cellValue) {
nextRow = visibleData[++countRowspan + _rowIndex] nextRow = visibleData[++countRowspan + _rowIndex]
} }
if (countRowspan > 1) { if (countRowspan > 1) {

View File

@@ -59,9 +59,6 @@
{{ ci[attr.name] }} {{ ci[attr.name] }}
</span> </span>
</template> </template>
<template v-else-if="attr.is_list">
<span> {{ ci[attr.name].join(',') }}</span>
</template>
<template v-else>{{ getName(ci[attr.name]) }}</template> <template v-else>{{ getName(ci[attr.name]) }}</template>
</span> </span>
<template v-else> <template v-else>
@@ -78,6 +75,7 @@
placeholder="请选择" placeholder="请选择"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
:multiple="attr.is_list"
showSearch showSearch
allowClear allowClear
size="small" size="small"
@@ -105,23 +103,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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 <a-input-number
size="small" size="small"
v-decorator="[ v-decorator="[
@@ -241,7 +222,7 @@ export default {
this.$nextTick(async () => { this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) { if (this.attr.is_list && !this.attr.is_choice) {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] || null, [`${this.attr.name}`]: this.ci[this.attr.name].join(',') || null,
}) })
return return
} }

View File

@@ -28,6 +28,7 @@
placeholder="请选择" placeholder="请选择"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
:multiple="attr.is_list"
showSearch showSearch
allowClear allowClear
> >

View File

@@ -53,8 +53,8 @@ export default {
return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 }) return postCITypeDiscovery(this.CITypeId, { adr_id: id, interval: type === 'agent' ? 300 : 3600 })
}) })
await Promise.all(promises) await Promise.all(promises)
.then((res) => { .then(() => {
this.getCITypeDiscovery(res[0].id) this.getCITypeDiscovery(this.selectedIds[0].id)
this.$message.success('添加成功') this.$message.success('添加成功')
}) })
.catch(() => { .catch(() => {

View File

@@ -2,22 +2,20 @@
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }"> <div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }">
<div v-if="adCITypeList && adCITypeList.length"> <div v-if="adCITypeList && adCITypeList.length">
<a-tabs size="small" v-model="currentTab"> <a-tabs size="small" v-model="currentTab">
<a-tab-pane v-for="item in adCITypeList" :key="item.id"> <a-tab-pane v-for="item in adCITypeList" :key="item.adr_id">
<a-space slot="tab"> <a-space slot="tab">
<span v-if="item.extra_option && item.extra_option.alias">{{ item.extra_option.alias }}</span> <span>{{ getADCITypeParam(item.adr_id) }}</span>
<span v-else>{{ getADCITypeParam(item.adr_id) }}</span>
<a-icon type="close-circle" @click="(e) => deleteADT(e, item)" /> <a-icon type="close-circle" @click="(e) => deleteADT(e, item)" />
</a-space> </a-space>
<AttrADTabpane <AttrADTabpane
:ref="`attrAdTabpane_${item.id}`" :ref="`attrAdTabpane_${item.adr_id}`"
:adr_id="item.adr_id" :currentTab="item.adr_id"
:adrList="adrList" :adrList="adrList"
:adCITypeList="adCITypeList" :adCITypeList="adCITypeList"
:currentAdt="item" :currentAdt="item"
:ciTypeAttributes="ciTypeAttributes" :ciTypeAttributes="ciTypeAttributes"
:currentAdr="getADCITypeParam(item.adr_id, undefined, true)" :currentAdr="getADCITypeParam(item.adr_id, undefined, true)"
@openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)" @openEditDrawer="(data, type, adType) => openEditDrawer(data, type, adType)"
@handleSave="getCITypeDiscovery"
/> />
</a-tab-pane> </a-tab-pane>
<a-space <a-space
@@ -136,8 +134,8 @@ export default {
async getCITypeDiscovery(currentTab) { async getCITypeDiscovery(currentTab) {
await getCITypeDiscovery(this.CITypeId).then((res) => { await getCITypeDiscovery(this.CITypeId).then((res) => {
this.adCITypeList = res.filter((item) => item.adr_id) this.adCITypeList = res.filter((item) => item.adr_id)
if (this.adCITypeList && this.adCITypeList.length && !this.currentTab) { if (res && res.length && !this.currentTab) {
this.currentTab = this.adCITypeList[0].id this.currentTab = res[0].adr_id
} }
if (currentTab) { if (currentTab) {
this.currentTab = currentTab this.currentTab = currentTab
@@ -158,7 +156,7 @@ export default {
e.stopPropagation() e.stopPropagation()
const that = this const that = this
this.$confirm({ this.$confirm({
title: `确认删除 ${item?.extra_option?.alias || this.getADCITypeParam(item.adr_id)}`, title: `确认删除 ${this.getADCITypeParam(item.adr_id)}`,
content: (h) => ( content: (h) => (
<div> <div>
<a-checkbox v-model={that.deletePlugin}>删除插件</a-checkbox> <a-checkbox v-model={that.deletePlugin}>删除插件</a-checkbox>
@@ -166,22 +164,18 @@ export default {
), ),
onOk() { onOk() {
deleteCITypeDiscovery(item.id).then(async () => { deleteCITypeDiscovery(item.id).then(async () => {
if (that.currentTab === item.id) { if (that.currentTab === item.adr_id) {
that.currentTab = '' that.currentTab = ''
} }
that.deletePlugin = false
that.$message.success('删除成功!') that.$message.success('删除成功!')
that.getCITypeDiscovery() that.getCITypeDiscovery()
if (that.deletePlugin) { if (that.deletePlugin) {
await deleteDiscovery(item.adr_id).finally(() => { await deleteDiscovery(item.adr_id)
that.deletePlugin = false
})
} }
that.deletePlugin = false
}) })
}, },
onCancel() { onCancel() {},
that.deletePlugin = false
},
}) })
}, },
openEditDrawer(data, type, adType) { openEditDrawer(data, type, adType) {
@@ -189,12 +183,12 @@ export default {
}, },
async updateNotInner(adr) { async updateNotInner(adr) {
const _idx = this.adCITypeList.findIndex((item) => item.adr_id === adr.id) const _idx = this.adCITypeList.findIndex((item) => item.adr_id === adr.id)
let res
if (_idx < 0) { if (_idx < 0) {
res = await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 }) await postCITypeDiscovery(this.CITypeId, { adr_id: adr.id, interval: 300 })
} }
await this.getDiscovery() await this.getDiscovery()
await this.getCITypeDiscovery(res?.id ?? undefined) await this.getCITypeDiscovery()
this.currentTab = adr.id
this.$nextTick(() => { this.$nextTick(() => {
this.$refs[`attrAdTabpane_${this.currentTab}`][0].init() this.$refs[`attrAdTabpane_${this.currentTab}`][0].init()
}) })

View File

@@ -14,7 +14,6 @@
<span>编辑</span> <span>编辑</span>
</a-space> </a-space>
</a> </a>
<div>别名<a-input v-model="alias" style="width:200px;" /></div>
<div class="attr-ad-header">字段映射</div> <div class="attr-ad-header">字段映射</div>
<vxe-table <vxe-table
v-if="adrType === 'agent'" v-if="adrType === 'agent'"
@@ -57,7 +56,7 @@
:ruleName="adrName" :ruleName="adrName"
:ciTypeAttributes="ciTypeAttributes" :ciTypeAttributes="ciTypeAttributes"
:adCITypeList="adCITypeList" :adCITypeList="adCITypeList"
:currentTab="adr_id" :currentTab="currentTab"
:style="{ marginBottom: '20px' }" :style="{ marginBottom: '20px' }"
/> />
<a-form-model <a-form-model
@@ -134,7 +133,7 @@ export default {
name: 'AttrADTabpane', name: 'AttrADTabpane',
components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting }, components: { Vcrontab, HttpSnmpAD, CMDBExprDrawer, MonitorNodeSetting },
props: { props: {
adr_id: { currentTab: {
type: Number, type: Number,
default: 0, default: 0,
}, },
@@ -188,7 +187,6 @@ export default {
}, },
], ],
form3: this.$form.createForm(this, { name: 'snmp_form' }), form3: this.$form.createForm(this, { name: 'snmp_form' }),
alias: '',
} }
}, },
computed: { computed: {
@@ -207,7 +205,7 @@ export default {
}, },
agentTypeRadioList() { agentTypeRadioList() {
const { permissions = [] } = this.userRoles const { permissions = [] } = this.userRoles
if ((permissions.includes('cmdb_admin') || permissions.includes('admin')) && this.adrType !== 'http') { if (permissions.includes('cmdb_admin') || permissions.includes('admin')) {
return [ return [
{ value: 'all', label: '所有节点' }, { value: 'all', label: '所有节点' },
{ value: 'agent_id', label: '指定节点' }, { value: 'agent_id', label: '指定节点' },
@@ -223,9 +221,8 @@ export default {
mounted() {}, mounted() {},
methods: { methods: {
init() { init() {
const _find = this.adrList.find((item) => Number(item.id) === Number(this.adr_id)) const _find = this.adrList.find((item) => Number(item.id) === Number(this.currentTab))
const _findADT = this.adCITypeList.find((item) => Number(item.id) === Number(this.currentAdt.id)) const _findADT = this.adCITypeList.find((item) => Number(item.adr_id) === Number(this.currentTab))
this.alias = _findADT?.extra_option?.alias ?? ''
if (this.adrType === 'http') { if (this.adrType === 'http') {
const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {} const { category = undefined, key = '', secret = '' } = _findADT?.extra_option ?? {}
this.form2 = { this.form2 = {
@@ -297,7 +294,7 @@ export default {
this.cron = cron this.cron = cron
}, },
handleSave() { handleSave() {
const { currentAdt, alias } = this const { currentAdt } = this
let params let params
if (this.adrType === 'http') { if (this.adrType === 'http') {
params = { params = {
@@ -363,15 +360,9 @@ export default {
return 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) => { putCITypeDiscovery(currentAdt.id, params).then((res) => {
this.$message.success('保存成功') this.$message.success('保存成功')
this.$emit('handleSave')
}) })
}, },
handleOpenCmdb() { handleOpenCmdb() {

View File

@@ -104,10 +104,6 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
attributes: {
type: Array,
default: () => []
}
}, },
data() { data() {
const propertyList = [ const propertyList = [
@@ -164,7 +160,7 @@ export default {
}) })
}, },
openTrigger() { openTrigger() {
this.$refs.triggerForm.open(this.property, this.attributes) this.$refs.triggerForm.open(this.property)
}, },
handleCalcComputed() { handleCalcComputed() {
const that = this const that = this

View File

@@ -98,7 +98,6 @@
:property="item" :property="item"
@ok="handleOk" @ok="handleOk"
:CITypeId="CITypeId" :CITypeId="CITypeId"
:attributes="attributes"
/> />
<i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i>
</draggable> </draggable>
@@ -138,7 +137,6 @@
:property="item" :property="item"
@ok="handleOk" @ok="handleOk"
:CITypeId="CITypeId" :CITypeId="CITypeId"
:attributes="attributes"
/> />
<i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i>
</draggable> </draggable>

View File

@@ -40,18 +40,20 @@
<a-menu-item key="0"> <a-menu-item key="0">
<a-upload <a-upload
name="file" name="file"
accept=".json" accept="json"
:showUploadList="false" :showUploadList="false"
style="display: inline-block" 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><a-icon type="upload"/></a><a> 导入</a> <a-space
><a><a-icon type="upload"/></a><a>导入</a></a-space
>
</a-upload> </a-upload>
</a-menu-item> </a-menu-item>
<a-menu-item key="1"> <a-menu-item key="1">
<a-space> <a-space>
<a href="/api/v0.1/ci_types/template/export/file"><a-icon type="download" /> 导出</a> <a><a-icon type="download"/></a>
<a href="/api/v0.1/ci_types/template/export/file">导出</a>
</a-space> </a-space>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -132,12 +134,6 @@
<a-space class="ci-types-left-detail-action"> <a-space class="ci-types-left-detail-action">
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a> <a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a>
<a><a-icon type="edit" @click="(e) => handleEdit(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 style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a>
</a-space> </a-space>
</div> </div>
@@ -297,7 +293,6 @@ import SplitPane from '@/components/SplitPane'
import CMDBGrant from '../../components/cmdbGrant' import CMDBGrant from '../../components/cmdbGrant'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons' import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
import AttributeStore from './attributeStore.vue' import AttributeStore from './attributeStore.vue'
import { getAllDepAndEmployee } from '@/api/company'
export default { export default {
name: 'CITypes', name: 'CITypes',
@@ -313,7 +308,6 @@ export default {
OpsMoveIcon, OpsMoveIcon,
AttributeStore, AttributeStore,
}, },
inject: ['reload'],
data() { data() {
return { return {
emptyImage, emptyImage,
@@ -348,8 +342,6 @@ export default {
orderSelectionOptions: [], orderSelectionOptions: [],
default_order_asc: '1', default_order_asc: '1',
allTreeDepAndEmp: [],
} }
}, },
computed: { computed: {
@@ -413,13 +405,9 @@ export default {
resource_type: () => { resource_type: () => {
return this.resource_type return this.resource_type
}, },
provide_allTreeDepAndEmp: () => {
return this.allTreeDepAndEmp
},
} }
}, },
mounted() { mounted() {
this.getAllDepAndEmployee()
const _currentId = localStorage.getItem('ops_cityps_currentId') const _currentId = localStorage.getItem('ops_cityps_currentId')
if (_currentId) { if (_currentId) {
this.currentId = _currentId this.currentId = _currentId
@@ -431,11 +419,6 @@ export default {
this.getAttributes() this.getAttributes()
}, },
methods: { methods: {
getAllDepAndEmployee() {
getAllDepAndEmployee({ block: 0 }).then((res) => {
this.allTreeDepAndEmp = res
})
},
async loadCITypes(isResetCurrentId = false) { async loadCITypes(isResetCurrentId = false) {
const groups = await getCITypeGroupsConfig({ need_other: true }) const groups = await getCITypeGroupsConfig({ need_other: true })
let alreadyReset = false let alreadyReset = false
@@ -721,21 +704,6 @@ 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() { resetRoute() {
resetRouter() resetRouter()
const roles = store.getters.roles const roles = store.getters.roles
@@ -800,19 +768,6 @@ 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> </script>

View File

@@ -372,7 +372,7 @@ export default {
}, },
async open(property, attrList) { async open(property, attrList) {
this.visible = true this.visible = true
await this.getNoticeConfigAppBot() this.getNoticeConfigAppBot()
this.attrList = attrList this.attrList = attrList
if (property.has_trigger) { if (property.has_trigger) {
this.triggerId = property.trigger.id this.triggerId = property.trigger.id

View File

@@ -71,6 +71,7 @@ import _ from 'lodash'
import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType' import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType'
import { getCITypeAttributesById } from '../../api/CITypeAttr' import { getCITypeAttributesById } from '../../api/CITypeAttr'
import TriggerForm from './triggerForm.vue' import TriggerForm from './triggerForm.vue'
import { getAllDepAndEmployee } from '@/api/company'
export default { export default {
name: 'TriggerTable', name: 'TriggerTable',
@@ -85,6 +86,7 @@ export default {
return { return {
tableData: [], tableData: [],
attrList: [], attrList: [],
allTreeDepAndEmp: [],
} }
}, },
computed: { computed: {
@@ -95,9 +97,20 @@ export default {
provide() { provide() {
return { return {
refresh: this.getTableData, refresh: this.getTableData,
provide_allTreeDepAndEmp: () => {
return this.allTreeDepAndEmp
},
} }
}, },
mounted() {
this.getAllDepAndEmployee()
},
methods: { methods: {
getAllDepAndEmployee() {
getAllDepAndEmployee({ block: 0 }).then((res) => {
this.allTreeDepAndEmp = res
})
},
async getTableData() { async getTableData() {
const [triggerList, attrList] = await Promise.all([ const [triggerList, attrList] = await Promise.all([
getTriggerList(this.CITypeId), getTriggerList(this.CITypeId),

View File

@@ -61,7 +61,7 @@
</vxe-column> </vxe-column>
<vxe-column field="type_id" title="模型" width="150px"> <vxe-column field="type_id" title="模型" width="150px">
<template #default="{ row }"> <template #default="{ row }">
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }} {{ row.operate_type === '删除模型' ? row.change.alias : row.type_id}}
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="changeDescription" title="描述"> <vxe-column field="changeDescription" title="描述">

View File

@@ -314,12 +314,6 @@ export default {
} }
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
if (type === 'all' || type === 'ci') {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (Number(ciType.id) === Number(lastTypeId)) {
localStorage.setItem('ops_ci_typeid', '')
}
}
that.$message.success('取消订阅成功') that.$message.success('取消订阅成功')
that.resetRoute() that.resetRoute()
}) })

View File

@@ -25,7 +25,7 @@
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
> >
<a-icon slot="switcherIcon" type="down" /> <a-icon slot="switcherIcon" type="down" />
<template #title="{ key: treeKey, title, isLeaf }"> <template #title="{ key: treeKey, title,isLeaf }">
<ContextMenu <ContextMenu
:title="title" :title="title"
:treeKey="treeKey" :treeKey="treeKey"
@@ -135,7 +135,7 @@
{{ col.title }}</span {{ col.title }}</span
> >
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -172,18 +172,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -322,7 +310,6 @@
v-else-if="relationViews.name2id && !relationViews.name2id.length" v-else-if="relationViews.name2id && !relationViews.name2id.length"
></a-alert> ></a-alert>
<AddTableModal ref="addTableModal" @reload="reload" /> <AddTableModal ref="addTableModal" @reload="reload" />
<!-- <GrantDrawer ref="grantDrawer" resourceTypeName="RelationView" app_id="cmdb" /> -->
<CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" /> <CMDBGrant ref="cmdbGrant" resourceType="RelationView" app_id="cmdb" />
<ci-detail ref="detail" :typeId="Number(currentTypeId[0])" /> <ci-detail ref="detail" :typeId="Number(currentTypeId[0])" />
@@ -379,7 +366,6 @@ export default {
SearchForm, SearchForm,
AddTableModal, AddTableModal,
ContextMenu, ContextMenu,
// GrantDrawer,
CMDBGrant, CMDBGrant,
SplitPane, SplitPane,
ElTree: Tree, ElTree: Tree,
@@ -497,11 +483,11 @@ export default {
}, },
inject: ['reload'], inject: ['reload'],
watch: { watch: {
'$route.path': function (newPath, oldPath) { '$route.path': function(newPath, oldPath) {
this.viewId = this.$route.params.viewId this.viewId = this.$route.params.viewId
this.reload() this.reload()
}, },
pageNo: function (newPage, oldPage) { pageNo: function(newPage, oldPage) {
this.loadData({ pageNo: newPage }, undefined, this.sortByTable) this.loadData({ pageNo: newPage }, undefined, this.sortByTable)
}, },
}, },
@@ -653,9 +639,6 @@ export default {
} else { } else {
q = `q=_type:${this.currentTypeId[0]},` + q q = `q=_type:${this.currentTypeId[0]},` + q
} }
if (Object.values(this.level2constraint).includes('2')) {
q = q + `&&has_m2m=1`
}
if (this.currentTypeId[0]) { if (this.currentTypeId[0]) {
const res = await searchCIRelation(q) const res = await searchCIRelation(q)
@@ -694,7 +677,6 @@ export default {
root_ids: key.split('%')[0], root_ids: key.split('%')[0],
level: this.treeKeys.length - index, level: this.treeKeys.length - index,
type_ids: this.showTypes.map((type) => type.id).join(','), type_ids: this.showTypes.map((type) => type.id).join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}).then((res) => { }).then((res) => {
let result let result
const getTreeItem = (data, id) => { const getTreeItem = (data, id) => {
@@ -748,10 +730,7 @@ export default {
_showTypes = JSON.parse(JSON.stringify(this.origShowTypes)) _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
} }
const promises = _showTypeIds.map((typeId) => { const promises = _showTypeIds.map((typeId) => {
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1') const _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
if (Object.values(this.level2constraint).includes('2')) {
_q = _q + `&&has_m2m=1`
}
console.log(_q) console.log(_q)
if (this.treeKeys.length === 0) { if (this.treeKeys.length === 0) {
return searchCI2(_q).then((res) => { return searchCI2(_q).then((res) => {
@@ -807,7 +786,6 @@ export default {
root_ids: ciIds.join(','), root_ids: ciIds.join(','),
level: level, level: level,
type_ids: this.showTypes.map((type) => type.id).join(','), type_ids: this.showTypes.map((type) => type.id).join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}).then((num) => { }).then((num) => {
facet.forEach((item, idx) => { facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + ''] item[1] += num[ciIds[idx] + '']
@@ -837,9 +815,6 @@ export default {
.map((item) => item.split('%')[0]) .map((item) => item.split('%')[0])
.join(',')}` .join(',')}`
} }
if (Object.values(this.level2constraint).includes('2')) {
q = q + `&&has_m2m=1`
}
searchCIRelation(q).then(async (res) => { searchCIRelation(q).then(async (res) => {
const facet = [] const facet = []
const ciIds = [] const ciIds = []
@@ -862,7 +837,6 @@ export default {
root_ids: ciIds.join(','), root_ids: ciIds.join(','),
level: _level - 1, level: _level - 1,
type_ids: this.showTypes.map((type) => type.id).join(','), type_ids: this.showTypes.map((type) => type.id).join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}).then((num) => { }).then((num) => {
facet.forEach((item, idx) => { facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + ''] item[1] += num[ciIds[idx] + '']
@@ -1336,10 +1310,7 @@ export default {
}) })
}, },
async openBatchDownload() { async openBatchDownload() {
this.$refs.batchDownload.open({ this.$refs.batchDownload.open({ preferenceAttrList: this.preferenceAttrList })
preferenceAttrList: this.preferenceAttrList,
ciTypeName: this.$route.meta.name,
})
}, },
batchDownload({ filename, type, checkedKeys }) { batchDownload({ filename, type, checkedKeys }) {
const jsonAttrList = [] const jsonAttrList = []

View File

@@ -130,27 +130,22 @@ export default {
exp = expression.match(regQ) ? expression.match(regQ)[0] : null exp = expression.match(regQ) ? expression.match(regQ)[0] : null
} }
await searchCI({ const res = await searchCI({
q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`, q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
count: 50, count: 50,
page: this.currentPage, page: this.currentPage,
sort, sort,
}) })
.then((res) => { this.tableData = res.result
this.tableData = res.result this.totalNumber = res.numfound
this.totalNumber = res.numfound this.columns = this.getColumns(res.result, this.preferenceAttrList)
this.columns = this.getColumns(res.result, this.preferenceAttrList) this.$nextTick(() => {
this.$nextTick(() => { const _table = this.$refs.xTable
const _table = this.$refs.xTable if (_table) {
if (_table) { _table.refreshColumn()
_table.refreshColumn() }
} this.loading = false
this.loading = false })
})
})
.catch(() => {
this.loading = false
})
}, },
getColumns(data, attrList) { getColumns(data, attrList) {
const modalDom = document.getElementById('add-table-modal') const modalDom = document.getElementById('add-table-modal')

View File

@@ -193,7 +193,7 @@
{{ col.title }}</span {{ col.title }}</span
> >
</template> </template>
<template v-if="col.is_choice || col.is_password || col.is_list" #edit="{ row }"> <template v-if="col.is_choice || col.is_password" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
@@ -230,18 +230,6 @@
</span> </span>
</a-select-option> </a-select-option>
</a-select> </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>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice"
@@ -516,7 +504,7 @@ export default {
}, },
}, },
watch: { watch: {
'$route.path': function (newPath, oldPath) { '$route.path': function(newPath, oldPath) {
this.newLoad = true this.newLoad = true
this.typeId = this.$route.params.typeId this.typeId = this.$route.params.typeId
this.initPage() this.initPage()
@@ -984,10 +972,7 @@ export default {
const $table = this.$refs['xTable'].getVxetableRef() const $table = this.$refs['xTable'].getVxetableRef()
const data = {} const data = {}
this.columns.forEach((item) => { this.columns.forEach((item) => {
if ( if (!(item.field in this.initialPasswordValue) && !_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])) {
!(item.field in this.initialPasswordValue) &&
!_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])
) {
data[item.field] = row[item.field] ?? null data[item.field] = row[item.field] ?? null
} }
}) })
@@ -1054,10 +1039,7 @@ export default {
this.$refs.jsonEditor.open(column, row) this.$refs.jsonEditor.open(column, row)
}, },
async openBatchDownload() { async openBatchDownload() {
this.$refs.batchDownload.open({ this.$refs.batchDownload.open({ preferenceAttrList: this.currentAttrList })
preferenceAttrList: this.currentAttrList,
ciTypeName: this.$route.meta.title || this.$route.meta.name,
})
}, },
batchDownload({ filename, type, checkedKeys }) { batchDownload({ filename, type, checkedKeys }) {
console.log(filename, type) console.log(filename, type)
@@ -1102,19 +1084,13 @@ export default {
let errorNum = 0 let errorNum = 0
this.loading = true this.loading = true
this.loadTip = `正在删除...` this.loadTip = `正在删除...`
const floor = Math.ceil(this.selectedRowKeys.length / 6) for (let i = 0; i < this.selectedRowKeys.length; i++) {
for (let i = 0; i < floor; i++) { await deleteCI(this.selectedRowKeys[i], false)
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6) .then(() => {
const promises = itemList.map((x) => deleteCI(x, false)) successNum += 1
await Promise.allSettled(promises) })
.then((res) => { .catch(() => {
res.forEach((r) => { errorNum += 1
if (r.status === 'fulfilled') {
successNum += 1
} else {
errorNum += 1
}
})
}) })
.finally(() => { .finally(() => {
this.loadTip = `正在删除${this.selectedRowKeys.length}成功${successNum}失败${errorNum}` this.loadTip = `正在删除${this.selectedRowKeys.length}成功${successNum}失败${errorNum}`

View File

@@ -92,13 +92,7 @@ export const generatorDynamicRouter = async () => {
meta: { title: '飞书', icon: 'ops-setting-notice-feishu', selectedIcon: 'ops-setting-notice-feishu-selected' }, meta: { title: '飞书', icon: 'ops-setting-notice-feishu', selectedIcon: 'ops-setting-notice-feishu-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/feishu') component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/feishu')
}] }]
}, }
{
path: '/setting/auth',
name: 'company_auth',
meta: { title: '认证设置', appName: 'backend', icon: 'ops-setting-auth', selectedIcon: 'ops-setting-auth-selected', permission: ['acl_admin'] },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/auth/index')
},
] ]
},]) },])
return routes return routes
@@ -118,11 +112,6 @@ export const constantRouterMap = [
name: 'login', name: 'login',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login'), component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login'),
}, },
{
path: '/user/logout',
name: 'logout',
component: () => import(/* webpackChunkName: "user" */ '@/views/user/Logout'),
},
{ {
path: '/user', path: '/user',
component: UserLayout, component: UserLayout,

View File

@@ -1,6 +1,4 @@
import { generatorDynamicRouter, constantRouterMap } from '@/router/config' import { generatorDynamicRouter, constantRouterMap } from '@/router/config'
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
import store from '@/store'
import user from './user' import user from './user'
/** /**
* 过滤账户是否拥有某一个权限,并将菜单从加载列表移除 * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
@@ -9,47 +7,37 @@ import user from './user'
* @param route * @param route
* @returns {boolean} * @returns {boolean}
*/ */
async function hasPermission(permission, route) { function hasPermission(permission, route) {
return new Promise(async (resolve, reject) => { const { detailPermissions } = user.state
const { detailPermissions } = user.state if (route.meta && route.meta.permission) {
if (route.meta && route.meta.permission) { // let flag = false
const totalPer = [...route.meta.appName && detailPermissions[`${route.meta.appName}`] ? detailPermissions[`${route.meta.appName}`].map(item => item.name) : [], ...permission] // for (let i = 0, len = permission.length; i < len; i++) {
let flag = false // flag = (route.meta.permission || []).includes(permission[i])
if (route.name === 'ci_type') { // if (flag) {
await searchPermResourceByRoleId(store.state.user.rid, { // return true
resource_type_id: 'page', // }
app_id: 'cmdb', // }
}).then(res => { // return false
const { resources } = res const totalPer = [...route.meta.appName && detailPermissions[`${route.meta.appName}`] ? detailPermissions[`${route.meta.appName}`].map(item => item.name) : [], ...permission]
const _idx = resources.findIndex(item => item.name === '模型配置') return route.meta.permission.some(item => totalPer.includes(item))
flag = flag || (_idx > -1) }
}) return true
}
resolve(route.meta.permission.some(item => totalPer.includes(item)) || flag)
}
resolve(true)
})
} }
async function filterAsyncRouter(routerMap, roles) { function filterAsyncRouter(routerMap, roles) {
const filteredRoutes = [] return routerMap.filter(route => {
for (let i = 0; i < routerMap.length; i++) {
const route = routerMap[i]
const default_route = ['company_info', 'company_structure', 'company_group'] const default_route = ['company_info', 'company_structure', 'company_group']
if (default_route.includes(route.name)) { if (default_route.includes(route.name)) {
filteredRoutes.push(route) return true
} else {
await hasPermission(roles.permissions, route).then(async flag => {
if (flag) {
if (route.children && route.children.length) {
route.children = await filterAsyncRouter(route.children, roles)
}
filteredRoutes.push(route)
}
})
} }
} if (hasPermission(roles.permissions, route)) {
return filteredRoutes if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, roles)
}
return true
}
return false
})
} }
const routes = { const routes = {
@@ -67,8 +55,8 @@ const routes = {
GenerateRoutes({ commit }, data) { GenerateRoutes({ commit }, data) {
return new Promise(resolve => { return new Promise(resolve => {
const { roles } = data const { roles } = data
generatorDynamicRouter().then(async routers => { generatorDynamicRouter().then(routers => {
const accessedRouters = await filterAsyncRouter(routers, roles) const accessedRouters = filterAsyncRouter(routers, roles)
commit('SET_ROUTERS', accessedRouters) commit('SET_ROUTERS', accessedRouters)
resolve(accessedRouters) resolve(accessedRouters)
}) })

View File

@@ -6,7 +6,6 @@ import { getAllUsers } from '../../api/login'
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission' import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
import { getEmployeeByUid, getEmployeeList } from '@/api/employee' import { getEmployeeByUid, getEmployeeList } from '@/api/employee'
import { getAllDepartmentList } from '@/api/company' import { getAllDepartmentList } from '@/api/company'
import { getAuthDataEnable } from '@/api/auth'
const user = { const user = {
state: { state: {
@@ -45,8 +44,7 @@ const user = {
nickname: '', nickname: '',
sex: '', sex: '',
position_name: '', position_name: '',
direct_supervisor_id: null, direct_supervisor_id: null
auth_enable: {}
}, },
mutations: { mutations: {
@@ -54,14 +52,13 @@ const user = {
state.token = token state.token = token
}, },
SET_USER_INFO: (state, { name, welcome, avatar, roles, info, uid, rid, username, mobile, department_id, employee_id, email, nickname, sex, position_name, direct_supervisor_id, annual_leave }) => { SET_USER_INFO: (state, { name, welcome, avatar, roles, info, uid, username, mobile, department_id, employee_id, email, nickname, sex, position_name, direct_supervisor_id, annual_leave }) => {
state.name = name state.name = name
state.welcome = welcome state.welcome = welcome
state.avatar = avatar state.avatar = avatar
state.roles = roles state.roles = roles
state.info = info state.info = info
state.uid = uid state.uid = uid
state.rid = rid
state.authed = true state.authed = true
state.username = username state.username = username
state.mobile = mobile state.mobile = mobile
@@ -90,27 +87,13 @@ const user = {
...data ...data
} : state.detailPermissions } : state.detailPermissions
}, },
SET_AUTH_ENABLE: (state, data) => {
state.auth_enable = data
}
}, },
actions: { actions: {
// 获取enable_list
GetAuthDataEnable({ commit }, userInfo) {
return new Promise((resolve, reject) => {
getAuthDataEnable().then(res => {
commit('SET_AUTH_ENABLE', res)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 登录 // 登录
Login({ commit }, { userInfo, auth_type = undefined }) { Login({ commit }, userInfo) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
login(userInfo, auth_type).then(response => { login(userInfo).then(response => {
Vue.ls.set(ACCESS_TOKEN, response.token, 7 * 24 * 60 * 60 * 1000) Vue.ls.set(ACCESS_TOKEN, response.token, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', response.token) commit('SET_TOKEN', response.token)
resolve() resolve()
@@ -150,7 +133,6 @@ const user = {
welcome: welcome(), welcome: welcome(),
avatar: result.avatar, avatar: result.avatar,
uid: result.uid, uid: result.uid,
rid: result.rid,
username: result.username, username: result.username,
mobile: res.mobile, mobile: res.mobile,
department_id: res.department_id, department_id: res.department_id,
@@ -177,11 +159,10 @@ const user = {
Vue.ls.remove(ACCESS_TOKEN) Vue.ls.remove(ACCESS_TOKEN)
logout(state.token).then(() => { logout(state.token).then(() => {
window.location.reload()
resolve() resolve()
}).catch(() => { }).catch(() => {
resolve() resolve()
}).finally(() => {
window.location.href = '/user/logout'
}) })
}) })
}, },

View File

@@ -9,6 +9,7 @@ import logo from './global/logo'
import notice from './global/notice' import notice from './global/notice'
import getters from './global/getters' import getters from './global/getters'
import appConfig from '@/config/app' import appConfig from '@/config/app'
console.log(appConfig)
Vue.use(Vuex) Vue.use(Vuex)

View File

@@ -1,11 +1,12 @@
/* eslint-dsiable */ /* eslint-dsiable */
import Vue from 'vue' import Vue from 'vue'
import axios from 'axios' import axios from 'axios'
import store from '@/store'
import { VueAxios } from './axios' import { VueAxios } from './axios'
import config from '@/config/setting'
import message from 'ant-design-vue/es/message' import message from 'ant-design-vue/es/message'
import notification from 'ant-design-vue/es/notification' import notification from 'ant-design-vue/es/notification'
import { ACCESS_TOKEN } from '@/store/global/mutation-types' import { ACCESS_TOKEN } from '@/store/global/mutation-types'
import router from '@/router'
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
@@ -51,8 +52,8 @@ const err = (error) => {
} }
if (error.response) { if (error.response) {
console.log(error.config.url) console.log(error.config.url)
if (error.response.status === 401 && router.path === '/user/login') { if (error.response.status === 401 && config.useSSO) {
window.location.href = '/user/logout' store.dispatch('Login')
} }
} }
return Promise.reject(error) return Promise.reject(error)

View File

@@ -1,111 +0,0 @@
<template>
<a-form-model ref="form" :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rules">
<SpanTitle>基本</SpanTitle>
<a-form-model-item label="是否启用" prop="enable">
<a-switch
:checked="Boolean(form.enable)"
@change="
() => {
$set(form, 'enable', Number(!form.enable))
}
"
/>
</a-form-model-item>
<a-form-model-item label="服务端地址" prop="cas_server" help="不包括url path例如https://xxx.com">
<a-input v-model="form.cas_server" placeholder="请输入服务端地址" />
</a-form-model-item>
<a-form-model-item label="验证服务端地址" prop="cas_validate_server" help="不包括url path例如https://xxx.com">
<a-input v-model="form.cas_validate_server" placeholder="请输入验证服务端地址" />
</a-form-model-item>
<SpanTitle>其他</SpanTitle>
<a-form-model-item label="登录路由" prop="cas_login_route">
<a-input v-model="form.cas_login_route" placeholder="/cas/built-in/cas/login" />
</a-form-model-item>
<a-form-model-item label="注销路由" prop="cas_logout_route">
<a-input v-model="form.cas_logout_route" placeholder="/cas/built-in/cas/logout" />
</a-form-model-item>
<a-form-model-item label="验证路由" prop="cas_validate_route">
<a-input v-model="form.cas_validate_route" placeholder="/cas/built-in/cas/serviceValidate" />
</a-form-model-item>
<a-form-model-item label="重定向路由" prop="cas_after_login">
<a-input v-model="form.cas_after_login" placeholder="请输入重定向路由" />
</a-form-model-item>
<a-form-model-item label="用户属性映射" prop="cas_user_map" :wrapper-col="{ span: 15 }">
<vue-json-editor
:style="{ '--custom-height': `${200}px` }"
v-model="form.cas_user_map"
:showBtns="false"
mode="code"
lang="zh"
@json-change="onJsonChange"
@has-error="onJsonError"
/>
</a-form-model-item>
</a-form-model>
</template>
<script>
import _ from 'lodash'
import vueJsonEditor from 'vue-json-editor'
import SpanTitle from '../components/spanTitle.vue'
export default {
name: 'CAS',
components: { SpanTitle, vueJsonEditor },
data() {
const defaultForm = {
enable: 0,
cas_server: '',
cas_validate_server: '',
cas_login_route: '',
cas_logout_route: '',
cas_validate_route: '',
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' } },
},
}
return {
defaultForm,
labelCol: { span: 3 },
wrapperCol: { span: 10 },
form: _.cloneDeep(defaultForm),
rules: {
enable: [{ required: true }],
cas_server: [{ required: true, message: '请输入服务端地址' }],
cas_login_route: [{ required: true, message: '请输入登录路由' }],
cas_logout_route: [{ required: true, message: '请输入注销路由' }],
cas_validate_route: [{ required: true, message: '请输入验证路由' }],
},
isJsonRight: true,
}
},
methods: {
setData(data) {
if (data) {
this.form = data
} else {
this.form = _.cloneDeep(this.defaultForm)
}
},
getData(callback) {
this.$refs.form.validate((valid) => {
if (valid && this.isJsonRight) {
callback(this.form)
}
})
},
onJsonChange(value) {
this.isJsonRight = true
},
onJsonError() {
this.isJsonRight = false
},
},
}
</script>
<style></style>

View File

@@ -1,65 +0,0 @@
<template>
<a-form-model ref="form" :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rules">
<SpanTitle>基本</SpanTitle>
<a-form-model-item
label="自动跳转到第三方登录页"
prop="auto_redirect"
help="如果关闭,则会弹出跳转到第三方登录页的确认,点取消按钮会进入系统内置的登录页"
>
<a-switch
:checked="Boolean(form.auto_redirect)"
@change="
() => {
$set(form, 'auto_redirect', Number(!form.auto_redirect))
}
"
/>
</a-form-model-item>
<!-- <a-form-model-item
label="API服务地址"
prop="api_host"
help="如果服务的部署没使用DNS, 如果要启用CAS、OAuth2.0、OIDC的则须填API服务地址"
>
<a-input v-model="form.api_host" placeholder="http://127.0.0.1:5000" />
</a-form-model-item> -->
</a-form-model>
</template>
<script>
import SpanTitle from '../components/spanTitle.vue'
export default {
name: 'AuthCommonConfig',
components: { SpanTitle },
data() {
return {
labelCol: { span: 5 },
wrapperCol: { span: 10 },
form: {
auto_redirect: 0,
api_host: '',
},
rules: {
auto_redirect: [{ required: true }],
},
}
},
methods: {
setData(data) {
if (data) {
this.form = data
} else {
this.form = { auto_redirect: 0 }
}
},
getData(callback) {
this.$refs.form.validate((valid) => {
if (valid) {
callback(this.form)
}
})
},
},
}
</script>
<style></style>

View File

@@ -1,166 +0,0 @@
<template>
<a-tabs type="card" class="ops-tab" v-model="activeKey" @change="changeActiveKey">
<a-tab-pane v-for="item in authList" :key="item.value">
<span slot="tab">
{{ item.label }}
<a-icon
v-if="enable_list.find((en) => en.auth_type === item.value)"
type="check-circle"
theme="filled"
style="color:#2f54eb"
/>
</span>
<div class="setting-auth">
<components :ref="item.value" :is="item.value === 'OIDC' ? 'OAUTH2' : item.value" :data_type="item.value" />
<a-row>
<a-col :offset="item.value === 'AuthCommonConfig' ? 5 : 3">
<a-space>
<a-button :loading="loading" type="primary" @click="handleSave">保存</a-button>
<template v-if="item.value === 'LDAP'">
<a-button :loading="loading" ghost type="primary" @click="handleTest('connect')">测试连接</a-button>
<a-button :loading="loading" ghost type="primary" @click="handleTest('login')">测试登录</a-button>
</template>
<a-button :loading="loading" @click="handleReset">重置</a-button>
</a-space>
</a-col>
</a-row>
</div>
<LoginModal v-if="item.value === 'LDAP'" ref="loginModal" @handleOK="(values) => handleTest('login', values)" />
</a-tab-pane>
</a-tabs>
</template>
<script>
import _ from 'lodash'
import LDAP from './ldap.vue'
import CAS from './cas.vue'
import AuthCommonConfig from './common.vue'
import OAUTH2 from './oauth2.vue'
import LoginModal from './loginModal.vue'
import { getAuthData, postAuthData, putAuthData, getAuthDataEnable, testLDAP } from '@/api/auth'
export default {
name: 'Auth',
components: { LDAP, CAS, AuthCommonConfig, OAUTH2, LoginModal },
data() {
const authList = [
{
value: 'LDAP',
label: 'LDAP',
},
{
value: 'CAS',
label: 'CAS',
},
{
value: 'OAUTH2',
label: 'OAUTH2',
},
{
value: 'OIDC',
label: 'OIDC',
},
{
value: 'AuthCommonConfig',
label: '通用',
},
]
return {
authList,
activeKey: 'LDAP',
dataTypeId: null,
loading: false,
enable_list: [],
}
},
mounted() {
this.changeActiveKey()
this.getAuthDataEnable()
},
methods: {
getAuthDataEnable() {
getAuthDataEnable().then((res) => {
this.enable_list = res.enable_list
})
},
changeActiveKey() {
getAuthData(this.activeKey).then((res) => {
const _res = _.cloneDeep(res)
this.$refs[this.activeKey][0].setData(_res?.data ?? null)
if (_res && JSON.stringify(_res) !== '{}') {
this.dataTypeId = _res.id
} else {
this.dataTypeId = null
}
})
},
handleSave() {
this.$refs[this.activeKey][0].getData(async (data) => {
this.loading = true
if (this.dataTypeId) {
await putAuthData(this.activeKey, this.dataTypeId, { data }).finally(() => {
this.loading = false
})
} else {
await postAuthData(this.activeKey, { data }).finally(() => {
this.loading = false
})
}
this.$message.success('保存成功')
this.changeActiveKey()
this.getAuthDataEnable()
})
},
handleReset() {
this.changeActiveKey()
},
handleTest(type, values = null) {
this.$refs[this.activeKey][0].getData(async (data) => {
if (type === 'login' && !values) {
this.$refs.loginModal[0].open()
} else {
this.loading = true
let _data = _.cloneDeep(data)
if (values) {
_data = {
..._data,
...values,
}
}
testLDAP(type, { data: _data })
.then((res) => {
this.$message.success('测试成功')
})
.finally(() => {
this.loading = false
})
}
})
},
},
}
</script>
<style lang="less" scoped>
.setting-auth {
background-color: #fff;
height: calc(100vh - 128px);
overflow: auto;
border-radius: 0 5px 5px 5px;
padding-top: 24px;
}
</style>
<style lang="less">
.setting-auth {
.jsoneditor-outer {
height: var(--custom-height) !important;
border: 1px solid #2f54eb;
}
div.jsoneditor-menu {
background-color: #2f54eb;
}
.jsoneditor-modes {
display: none;
}
}
</style>

View File

@@ -1,80 +0,0 @@
<template>
<a-form-model ref="form" :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rules">
<SpanTitle>基本</SpanTitle>
<a-form-model-item label="是否启用" prop="enable">
<a-switch
:checked="Boolean(form.enable)"
@change="
() => {
$set(form, 'enable', Number(!form.enable))
}
"
/>
</a-form-model-item>
<a-form-model-item
label="服务器地址"
prop="ldap_server"
help="例如: 192.168.1.6 或者 ldap://192.168.1.6 或者 ldap://192.168.1.6:389"
>
<a-input v-model="form.ldap_server" placeholder="请输入服务器地址" />
</a-form-model-item>
<a-form-model-item label="" prop="ldap_domain">
<a-input v-model="form.ldap_domain" placeholder="请输入域" />
</a-form-model-item>
<SpanTitle>用户</SpanTitle>
<a-form-model-item
label="用户名称"
prop="ldap_user_dn"
help="用户dn: cn={},ou=users,dc=xxx,dc=com {}会替换成用户名"
>
<a-input v-model="form.ldap_user_dn" placeholder="请输入用户名称" />
</a-form-model-item>
</a-form-model>
</template>
<script>
import SpanTitle from '../components/spanTitle.vue'
export default {
name: 'LDAP',
components: { SpanTitle },
data() {
return {
labelCol: { span: 3 },
wrapperCol: { span: 10 },
form: {
enable: 0,
ldap_server: '',
ldap_domain: '',
ldap_user_dn: 'cn={},ou=users,dc=xxx,dc=com',
},
rules: {
enable: [{ required: true }],
ldap_server: [{ required: true, message: '请输入服务器地址' }],
},
}
},
methods: {
setData(data) {
if (data) {
this.form = { ...data }
} else {
this.form = {
enable: 0,
ldap_server: '',
ldap_domain: '',
ldap_user_dn: 'cn={},ou=users,dc=xxx,dc=com',
}
}
},
getData(callback) {
this.$refs.form.validate((valid) => {
if (valid) {
callback(this.form)
}
})
},
},
}
</script>
<style></style>

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