diff --git a/.gitignore b/.gitignore index b6d55e07..92046533 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ yarn-debug.log* yarn-error.log* /*/target/ /*/dist/ +/*/.env *.pyc diff --git a/Makefile b/Makefile index 95995776..f9c6f07b 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ package_py: image_py mkdir -p package cd package && docker save $(project_name):$(version) -o image.tar cd package && cp ../register.xml . + cd package && cp ../init.sql ./init.sql cd package && sed -i "s~{{REPOSITORY}}~$(project_name)~g" register.xml cd package && sed -i "s~{{VERSION}}~$(version)~g" register.xml cd artifacts-ui/dist && zip -r ui.zip . diff --git a/artifacts-corepy/artifacts_corepy/apps/auth/__init__.py b/artifacts-corepy/artifacts_corepy/apps/auth/__init__.py new file mode 100644 index 00000000..8e8d92d4 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/auth/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from artifacts_corepy.apps.auth import route diff --git a/artifacts-corepy/artifacts_corepy/apps/auth/api.py b/artifacts-corepy/artifacts_corepy/apps/auth/api.py new file mode 100644 index 00000000..b5ca2096 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/auth/api.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import logging +import time + +import jwt +from talos.core import config, utils +from talos.core.i18n import _ +from talos.core import exceptions as core_ex + +from artifacts_corepy.common import exceptions +from artifacts_corepy.common import utils as terminal_utils +from artifacts_corepy.db import resource as db_resource + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class SysUser(db_resource.SysUser): + def generate_tokens(self, rid): + roles = self.get_roles(rid) + tokens = [] + current_time = int(time.time() * 1000) + access_token_iat = int(current_time / 1000) + access_token_exp = access_token_iat + CONF.access_token_exipres + refresh_token_exp = access_token_iat + CONF.refresh_token_exipres + decoded_secret = terminal_utils.b64decode_key(CONF.jwt_signing_key) + tokens.append({ + "expiration": str(current_time + CONF.access_token_exipres * 1000), + "token": jwt.encode({ + "sub": rid, + "iat": access_token_iat, + "type": "accessToken", + "clientType": "USER", + "exp": access_token_exp, + "authority": "[" + ','.join([r['id'] for r in roles]) + "]" + }, decoded_secret, "HS512").decode(), + "tokenType": "accessToken" + }) + tokens.append({ + "expiration": str(current_time + CONF.refresh_token_exipres * 1000), + "token": jwt.encode({ + "sub": rid, + "iat": access_token_iat, + "type": "refreshToken", + "clientType": "USER", + "exp": refresh_token_exp + }, decoded_secret, "HS512", + ).decode(), + "tokenType": "refreshToken" + }) + return tokens + + def login(self, username, password): + with self.get_session(): + if self.check_password(username, password): + return self.generate_tokens(username) + else: + raise core_ex.LoginError() + + def refresh(self, token): + with self.get_session(): + try: + decoded_secret = terminal_utils.b64decode_key(CONF.jwt_signing_key) + info = jwt.decode(token, key=decoded_secret, verify=True) + if info['type'] != 'refreshToken': + raise core_ex.AuthError() + return self.generate_tokens(info['sub']) + except jwt.exceptions.ExpiredSignatureError: + raise core_ex.AuthError() + except jwt.exceptions.DecodeError: + raise core_ex.AuthError() + + def get_menus(self, rid): + menus = [] + exists = {} + roles = self.get_roles(rid) + for role in roles: + for menu in role['menus']: + if menu['is_active'] == 'yes' and menu['id'] not in exists: + menus.append(menu) + exists[menu['id']] = True + return menus + + def get_roles(self, rid): + ref = self.get(rid) + if ref: + return ref['roles'] + return [] + + def create(self, resource, validate=True, detail=True): + resource['salt'] = utils.generate_salt(16) + password = utils.generate_salt(16) + resource['password'] = utils.encrypt_password(password, resource['salt']) + ref = super().create(resource, validate=validate, detail=detail) + ref['password'] = password + return ref + + def reset_password(self, rid, password=None): + resource = {} + resource['salt'] = utils.generate_salt(16) + password = password or utils.generate_salt(16) + resource['password'] = utils.encrypt_password(password, resource['salt']) + before_update, after_update = self.update(rid, resource, validate=False) + if after_update: + after_update['password'] = password + return after_update + + def check_password(self, rid, password): + refs = self.list_internal({'id': rid}) + if refs: + return utils.check_password(refs[0]['password'], password, refs[0]['salt']) + return False + + def update_password(self, rid, password, origin_password): + if not password: + raise exceptions.PluginError(message=_('unabled to set empty password')) + if self.check_password(rid, origin_password): + resource = {} + resource['salt'] = utils.generate_salt(16) + password = password or utils.generate_salt(16) + resource['password'] = utils.encrypt_password(password, resource['salt']) + before_update, after_update = self.update(rid, resource, validate=False) + return after_update + else: + raise exceptions.PluginError(message=_('faild to set new password: incorrect origin password')) + + def delete(self, rid, filters=None, detail=True): + refs = self.list({'id': rid}) + if refs and refs[0]['is_system'] == 'yes': + raise exceptions.PluginError(message=_('unable to delete system user')) + with self.transaction() as session: + db_resource.SysRoleUser(transaction=session).delete_all({'user_id': rid}) + return super().delete(rid, filters=filters, detail=detail) + + +class SysRole(db_resource.SysRole): + def get_users(self, rid): + ref = self.get(rid) + if ref: + return ref['users'] + return [] + + def _update_intersect_refs(self, rid, self_field, ref_field, resource_type, refs, session): + old_refs = [result[ref_field] for result in resource_type(session=session).list(filters={self_field: rid})] + create_refs = list(set(refs) - set(old_refs)) + create_refs.sort(key=refs.index) + delete_refs = set(old_refs) - set(refs) + if delete_refs: + resource_type(transaction=session).delete_all(filters={ + self_field: rid, + ref_field: { + 'in': list(delete_refs) + } + }) + for ref in create_refs: + new_ref = {} + new_ref[self_field] = rid + new_ref[ref_field] = ref + resource_type(transaction=session).create(new_ref) + + def set_users(self, rid, users): + with self.transaction() as session: + self._update_intersect_refs(rid, 'role_id', 'user_id', db_resource.SysRoleUser, users, session) + return self.get_users(rid) + + def get_menus(self, rid, is_active=True): + ref = self.get(rid) + if ref: + if is_active: + return [menu for menu in ref['menus'] if menu['is_active'] == 'yes'] + else: + return ref['menus'] + return [] + + def set_menus(self, rid, menus): + with self.transaction() as session: + self._update_intersect_refs(rid, 'role_id', 'menu_id', db_resource.SysRoleMenu, menus, session) + return self.get_menus(rid, is_active=False) + + def delete(self, rid, filters=None, detail=True): + refs = self.list({'id': rid}) + if refs and refs[0]['is_system'] == 'yes': + raise exceptions.PluginError(message=_('unable to delete system role')) + with self.transaction() as session: + bindings = db_resource.SysRoleUser(transaction=session).list({'role_id': rid}) + if len(bindings) > 0: + users = ','.join([bind['user_id'] for bind in bindings]) + raise exceptions.PluginError(message=_('role binds with %(users)s') % {'users': users}) + return super().delete(rid, filters=filters, detail=detail) + + +class SysMenu(db_resource.SysMenu): + pass diff --git a/artifacts-corepy/artifacts_corepy/apps/auth/controller.py b/artifacts-corepy/artifacts_corepy/apps/auth/controller.py new file mode 100644 index 00000000..97ea8e41 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/auth/controller.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from talos.core.i18n import _ +from talos.utils.scoped_globals import GLOBALS + +from artifacts_corepy.common import exceptions +from artifacts_corepy.common.mixin import Controller, CollectionController as Collection, ItemController as Item +from artifacts_corepy.apps.auth import api as auth_api + + +class Token(Controller): + name = 'auth.token' + resource = auth_api.SysUser + allow_methods = ('POST',) + + def create(self, req, data, **kwargs): + return self.make_resource(req).login(data.get('username', ''), data.get('password', '')) + + +class TokenRefresh(Controller): + name = 'auth.token' + resource = auth_api.SysUser + allow_methods = ('GET',) + + def get(self, req, **kwargs): + return self.make_resource(req).refresh(GLOBALS.request.auth_token) + + +class UserMenus(Controller): + name = 'auth.user-menus' + resource = auth_api.SysUser + allow_methods = ('GET',) + + def get(self, req, **kwargs): + return self.make_resource(req).get_menus(GLOBALS.request.auth_user) + + +class UserPassword(Controller): + name = 'auth.user-password' + resource = auth_api.SysUser + allow_methods = ('POST',) + + def create(self, req, data, **kwargs): + return self.make_resource(req).update_password(GLOBALS.request.auth_user, data.get('newPassword', ''), + data.get('oldPassword', '')) + + +class User(Collection): + name = 'auth.users' + resource = auth_api.SysUser + + +class UserItem(Item): + name = 'auth.users' + resource = auth_api.SysUser + + +class UserItemMenu(Item): + name = 'auth.users' + resource = auth_api.SysUser + allow_methods = ('GET',) + + def get(self, req, rid): + return self.make_resource(req).get_menus(rid) + + +class UserItemResetPassword(Controller): + name = 'auth.users.password' + resource = auth_api.SysUser + allow_methods = ('POST',) + + def create(self, req, data, rid): + data = data or {} + return self.make_resource(req).reset_password(rid, password=data.get('password', None)) + + +class UserItemRole(Item): + name = 'auth.users' + resource = auth_api.SysUser + allow_methods = ('GET',) + + def get(self, req, rid): + return self.make_resource(req).get_roles(rid) + + +class Role(Collection): + name = 'auth.roles' + resource = auth_api.SysRole + + +class RoleItem(Item): + name = 'auth.roles' + resource = auth_api.SysRole + + +class RoleItemMenu(Controller): + name = 'auth.roles' + resource = auth_api.SysRole + allow_methods = ('GET', 'POST') + + def get(self, req, rid): + return self.make_resource(req).get_menus(rid) + + def create(self, req, data, rid): + return self.make_resource(req).set_menus(rid, data) + + +class RoleItemUser(Controller): + name = 'auth.roles' + resource = auth_api.SysRole + allow_methods = ('GET', 'POST') + + def get(self, req, rid): + return self.make_resource(req).get_users(rid) + + def create(self, req, data, rid): + return self.make_resource(req).set_users(rid, data) + + +class Menu(Collection): + name = 'auth.menus' + resource = auth_api.SysMenu + allow_methods = ('GET',) + + +class MenuItem(Item): + name = 'auth.menus' + resource = auth_api.SysMenu + allow_methods = ('GET',) diff --git a/artifacts-corepy/artifacts_corepy/apps/auth/route.py b/artifacts-corepy/artifacts_corepy/apps/auth/route.py new file mode 100644 index 00000000..80743e44 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/auth/route.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from artifacts_corepy.apps.auth import controller + + +def add_routes(api): + # 不需要登陆 + api.add_route('/artifacts/v1/login', controller.Token()) + api.add_route('/artifacts/v1/refresh-token', controller.TokenRefresh()) + # 登陆但不需要权限校验 + api.add_route('/artifacts/v1/user-menus', controller.UserMenus()) + api.add_route('/artifacts/v1/user-password', controller.UserPassword()) + # 登陆且经过权限校验 + api.add_route('/artifacts/v1/users', controller.User()) + api.add_route('/artifacts/v1/users/{rid}', controller.UserItem()) + api.add_route('/artifacts/v1/users/{rid}/reset-password', controller.UserItemResetPassword()) + api.add_route('/artifacts/v1/users/{rid}/menus', controller.UserItemMenu()) + api.add_route('/artifacts/v1/users/{rid}/roles', controller.UserItemRole()) + api.add_route('/artifacts/v1/roles', controller.Role()) + api.add_route('/artifacts/v1/roles/{rid}', controller.RoleItem()) + api.add_route('/artifacts/v1/roles/{rid}/menus', controller.RoleItemMenu()) + api.add_route('/artifacts/v1/roles/{rid}/users', controller.RoleItemUser()) + api.add_route('/artifacts/v1/menus', controller.Menu()) + # api.add_route('/artifacts/v1/menus/{rid}', controller.MenuItem()) diff --git a/artifacts-corepy/artifacts_corepy/apps/package/apiv2.py b/artifacts-corepy/artifacts_corepy/apps/package/apiv2.py index 6a6bad3d..fcc647c2 100644 --- a/artifacts-corepy/artifacts_corepy/apps/package/apiv2.py +++ b/artifacts-corepy/artifacts_corepy/apps/package/apiv2.py @@ -17,6 +17,8 @@ import os.path import urllib.parse from collections import namedtuple + +from talos.common import cache from talos.core import config from talos.core import utils from talos.db import crud @@ -25,22 +27,24 @@ from talos.core.i18n import _ from talos.utils import scoped_globals -from artifacts_corepy.common import exceptions +from artifacts_corepy.common import exceptions, wecube from artifacts_corepy.common import nexus from artifacts_corepy.common import s3 from artifacts_corepy.common import wecmdbv2 as wecmdb from artifacts_corepy.common import utils as artifact_utils from artifacts_corepy.common import constant +from artifacts_corepy.common.wecmdbv2 import URL_PREFIX LOG = logging.getLogger(__name__) CONF = config.CONF +TOKEN_KEY = 'artifacts_subsystem_token' # Common field_pkg_baseline_package_name = 'baseline_package' -field_pkg_is_decompression_name = 'is_decompression' # true,false as string -field_pkg_package_type_name = 'package_type' # APP DB APP&DB +field_pkg_is_decompression_name = 'is_decompression' # true,false as string +field_pkg_package_type_name = 'package_type' # APP DB APP&DB field_pkg_key_service_code_name = 'key_service_code' -fields_pkg_common = [field_pkg_baseline_package_name, field_pkg_is_decompression_name, field_pkg_package_type_name, +fields_pkg_common = [field_pkg_baseline_package_name, field_pkg_is_decompression_name, field_pkg_package_type_name, field_pkg_key_service_code_name] field_pkg_baseline_package_default_value = None field_pkg_is_decompression_default_value = 'true' @@ -58,7 +62,8 @@ field_pkg_log_file_keyword_name = 'log_file_keyword' field_pkg_log_file_metric_name = 'log_file_metric' field_pkg_log_file_trace_name = 'log_file_trace' -fields_pkg_app = [field_pkg_diff_conf_directory_name, field_pkg_diff_conf_file_name, field_pkg_script_file_directory_name, +fields_pkg_app = [field_pkg_diff_conf_directory_name, field_pkg_diff_conf_file_name, + field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, field_pkg_start_file_path_name, field_pkg_stop_file_path_name, field_pkg_log_file_directory_name, field_pkg_log_file_trade_name, field_pkg_log_file_keyword_name, field_pkg_log_file_metric_name, field_pkg_log_file_trace_name] @@ -86,9 +91,11 @@ field_pkg_db_rollback_file_path_name = 'db_rollback_file_path' field_pkg_db_upgrade_directory_name = 'db_upgrade_directory' field_pkg_db_upgrade_file_path_name = 'db_upgrade_file_path' -fields_pkg_db = [field_pkg_db_deploy_file_directory_name, field_pkg_db_deploy_file_path_name, field_pkg_db_diff_conf_directory_name, - field_pkg_db_diff_conf_file_name, field_pkg_db_rollback_directory_name, field_pkg_db_rollback_file_path_name, - field_pkg_db_upgrade_directory_name, field_pkg_db_upgrade_file_path_name] +fields_pkg_db = [field_pkg_db_deploy_file_directory_name, field_pkg_db_deploy_file_path_name, + field_pkg_db_diff_conf_directory_name, + field_pkg_db_diff_conf_file_name, field_pkg_db_rollback_directory_name, + field_pkg_db_rollback_file_path_name, + field_pkg_db_upgrade_directory_name, field_pkg_db_upgrade_file_path_name] fields_pkg_all = [] fields_pkg_all.extend(fields_pkg_common) fields_pkg_all.extend(fields_pkg_app) @@ -125,7 +132,8 @@ field_diff_conf_tpl_map = json.loads(field_diff_conf_tpl_map_str) except Exception as e: LOG.error('Failed to parse ARTIFACTS_DIFF_CONF_TEMPLATE_MAP: %s', field_diff_conf_tpl_map_str) - LOG.exception(e) + LOG.exception(e) + def is_upload_local_enabled(): return utils.bool_from_string(CONF.wecube.upload_enabled) @@ -134,11 +142,13 @@ def is_upload_local_enabled(): def is_upload_nexus_enabled(): return utils.bool_from_string(CONF.wecube.upload_nexus_enabled) + def remove_prefix(text, prefix): if text.startswith(prefix): return text[len(prefix):] return text + def calculate_md5(fileobj): hasher = hashlib.md5() chunk_size = 64 * 1024 @@ -154,19 +164,23 @@ def calculate_file_md5(filepath): with open(filepath, 'rb') as fileobj: return calculate_md5(fileobj) + def split_to_list(value, spliter=None): if spliter is None: spliter = r'[|,]' return re.split(spliter, value) + class FileNameConcater(converter.NullConverter): def convert(self, value): return ','.join([i.get('filename') for i in value if i.get('filename')]) + class FilePathConcater(converter.NullConverter): def convert(self, value): return ','.join([i.get('path') for i in value if i.get('path')]) - + + class BooleanNomalizedConverter(converter.NullConverter): def __init__(self, default=False): self.fallback_value = default @@ -196,7 +210,8 @@ def list(self, params): url = self.server + '/platform/v1/users/retrieve' resp_json = api_client.get(url, {}, check_resp=False) return resp_json.get('data', []) - + + class ProcessDef(WeCubeResource): def list(self, params): params['plugin'] = 'artifacts' @@ -211,6 +226,7 @@ def list(self, params): url = self.server + '/platform/v1/public/process/definitions' return api_client.get(url, params, check_resp=False) + class SystemDesign(WeCubeResource): def list(self, params): cmdb_client = self.get_cmdb_client() @@ -228,7 +244,7 @@ def list(self, params): "value": "" }], "paging": - False, + False, "sorting": { "asc": False, "field": "confirm_time" @@ -364,21 +380,24 @@ def list_by_post(self, query, unit_design_id): query['filters'].append({"name": "unit_design", "operator": "eq", "value": unit_design_id}) resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) for i in resp_json['data']['contents']: - i[field_pkg_package_type_name] = i.get(field_pkg_package_type_name, constant.PackageType.default) or constant.PackageType.default - i[field_pkg_is_decompression_name] = i.get(field_pkg_is_decompression_name, field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value - i[field_pkg_key_service_code_name] = i.get(field_pkg_key_service_code_name, field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value + i[field_pkg_package_type_name] = i.get(field_pkg_package_type_name, + constant.PackageType.default) or constant.PackageType.default + i[field_pkg_is_decompression_name] = i.get(field_pkg_is_decompression_name, + field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value + i[field_pkg_key_service_code_name] = i.get(field_pkg_key_service_code_name, + field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value fields = (field_pkg_diff_conf_directory_name, field_pkg_diff_conf_file_name, - field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, - field_pkg_start_file_path_name, field_pkg_stop_file_path_name, - field_pkg_log_file_directory_name,field_pkg_log_file_trade_name, - field_pkg_log_file_keyword_name, field_pkg_log_file_metric_name, field_pkg_log_file_trace_name,) + field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, + field_pkg_start_file_path_name, field_pkg_stop_file_path_name, + field_pkg_log_file_directory_name, field_pkg_log_file_trade_name, + field_pkg_log_file_keyword_name, field_pkg_log_file_metric_name, field_pkg_log_file_trace_name,) for field in fields: i[field] = self.build_file_object(i.get(field, None)) # db部署支持 fields = (field_pkg_db_deploy_file_directory_name, field_pkg_db_deploy_file_path_name, - field_pkg_db_diff_conf_directory_name, field_pkg_db_diff_conf_file_name, - field_pkg_db_upgrade_directory_name, field_pkg_db_rollback_file_path_name, - field_pkg_db_rollback_directory_name, field_pkg_db_upgrade_file_path_name,) + field_pkg_db_diff_conf_directory_name, field_pkg_db_diff_conf_file_name, + field_pkg_db_upgrade_directory_name, field_pkg_db_rollback_file_path_name, + field_pkg_db_rollback_directory_name, field_pkg_db_upgrade_file_path_name,) for field in fields: i[field] = self.build_file_object(i.get(field, None)) return resp_json['data'] @@ -391,10 +410,11 @@ def get_package_statistics(self, post_data, unit_design_id): query.setdefault('dialect', {"queryMode": "new"}) query.setdefault('filters', []) query.setdefault('paging', True) - query.setdefault('pageable', {'pageSize':1, 'startIndex': 1}) + query.setdefault('pageable', {'pageSize': 1, 'startIndex': 1}) self.set_package_query_fields(query) query['filters'].append({"name": "unit_design", "operator": "eq", "value": unit_design_id}) - for t in [constant.PackageType.app, constant.PackageType.db, constant.PackageType.mixed, constant.PackageType.image, constant.PackageType.rule]: + for t in [constant.PackageType.app, constant.PackageType.db, constant.PackageType.mixed, + constant.PackageType.image, constant.PackageType.rule]: query_tmp = copy.deepcopy(query) query_tmp['filters'].append({"name": field_pkg_package_type_name, "operator": "eq", "value": t}) resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query_tmp) @@ -403,7 +423,7 @@ def get_package_statistics(self, post_data, unit_design_id): def build_file_object(self, filenames, spliter=None): if spliter is None: - spliter = r'[|,]' + spliter = r'[|,]' if not filenames: return [] return [{ @@ -435,20 +455,27 @@ def download_url_parse(self, url): else: ret['group'] = '/' return ret - - def _is_compose_package(self, filename:str) -> bool: + + def _is_compose_package(self, filename: str) -> bool: if filename.startswith('[W]') and '_weart' in filename: return True return False - - def _conv_diff_conf_type(self, diff_conf_type:str) -> str: + + def _conv_diff_conf_type(self, diff_conf_type: str) -> str: """ 将差异化配置文件类型转换为对应的枚举类型 """ - variable_prefix_encrypt = [] if not CONF.encrypt_variable_prefix.strip() else [s.strip() for s in CONF.encrypt_variable_prefix.split(',')] - variable_prefix_file = [] if not CONF.file_variable_prefix.strip() else [s.strip() for s in CONF.file_variable_prefix.split(',')] - variable_prefix_default = [] if not CONF.default_special_replace.strip() else [s.strip() for s in CONF.default_special_replace.split(',')] - variable_prefix_global = [] if not CONF.global_variable_prefix.strip() else [s.strip() for s in CONF.global_variable_prefix.split(',')] + variable_prefix_encrypt = [] if not CONF.encrypt_variable_prefix.strip() else [s.strip() for s in + CONF.encrypt_variable_prefix.split( + ',')] + variable_prefix_file = [] if not CONF.file_variable_prefix.strip() else [s.strip() for s in + CONF.file_variable_prefix.split(',')] + variable_prefix_default = [] if not CONF.default_special_replace.strip() else [s.strip() for s in + CONF.default_special_replace.split( + ',')] + variable_prefix_global = [] if not CONF.global_variable_prefix.strip() else [s.strip() for s in + CONF.global_variable_prefix.split( + ',')] if diff_conf_type in variable_prefix_encrypt: return 'ENCRYPTED' elif diff_conf_type in variable_prefix_file: @@ -458,13 +485,15 @@ def _conv_diff_conf_type(self, diff_conf_type:str) -> str: elif diff_conf_type in variable_prefix_global: return 'GLOBAL' return '' - + """上传组合物料包[含差异化变量,包配置,包文件] - """ - def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_design_id:str, force_operator=None, baseline_package=None): + """ + + def upload_compose_package(self, compose_filename: str, compose_fileobj, unit_design_id: str, force_operator=None, + baseline_package=None): # 组合包上传需要解压并且提取真正包上传到nexus中 chunk_size = 1024 * 1024 - with tempfile.NamedTemporaryFile('w+b',suffix='.tar.gz') as upload_stream_file: + with tempfile.NamedTemporaryFile('w+b', suffix='.tar.gz') as upload_stream_file: chunk = compose_fileobj.read(chunk_size) while chunk: upload_stream_file.write(chunk) @@ -486,9 +515,9 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des 'unpack file error: %(detail)s, is file contains paxheader(mac archive) and modify with 7zip? (cause paxheader corruption)' % {'detail': str(e)})) raise exceptions.PluginError(message=_('unpack file error: %(detail)s' % - {'detail': str(e)})) + {'detail': str(e)})) LOG.info('unpack complete') - + filenames = os.listdir(file_cache_dir) # 提取包配置 filename = 'package.json' @@ -509,7 +538,7 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des with open(os.path.join(file_cache_dir, filename), 'r') as f: pacakge_db_diffconfigs = json.loads(f.read()) # 剩下的即是原始物料包 - if len(filenames) ==1: + if len(filenames) == 1: filename = filenames[0] filename = os.path.join(file_cache_dir, filename) unit_design = self._get_unit_design_by_id(unit_design_id) @@ -526,10 +555,12 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des artifact_path = self.build_local_nexus_path(unit_design) artifact_repository = CONF.nexus.repository with open(filename, 'rb') as fileobj: - upload_result = nexus_client.upload(artifact_repository, artifact_path, filename, 'application/octet-stream', fileobj) + upload_result = nexus_client.upload(artifact_repository, artifact_path, filename, + 'application/octet-stream', fileobj) new_download_url = upload_result['downloadUrl'].replace(nexus_server, - CONF.wecube.server.rstrip('/') + '/artifacts') - if deploy_package is None : + CONF.wecube.server.rstrip( + '/') + '/artifacts') + if deploy_package is None: raise exceptions.PluginError(message=_("invalid deploy package!")) # 创建差异化变量,并更新包的绑定字段 # bound': true, 'key': name, 'diffExpr': 'expr' @@ -569,7 +600,7 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des 'description': key, 'variable_value': value.get('diffExpr', ''), 'variable_type': self._conv_diff_conf_type(value.get('type', '')) - } for key,value in new_diff_configs.items()]) + } for key, value in new_diff_configs.items()]) # 新创建的差异化变量也需要检测是否需要绑定 all_diff_configs = self._get_diff_configs_by_keyname(list(new_diff_configs.keys())) for conf in all_diff_configs: @@ -585,9 +616,9 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des cmdb_client.update(CONF.wecube.wecmdb.citypes.diff_config, [{ 'guid': key, 'variable_value': value['diffExpr'] - } for key,value in update_diff_configs.items()]) + } for key, value in update_diff_configs.items()]) # 创建CMDB 包记录 - deploy_package['baseline_package'] = baseline_package or None + deploy_package['baseline_package'] = baseline_package or None deploy_package['unit_design'] = unit_design_id deploy_package['upload_user'] = force_operator or scoped_globals.GLOBALS.request.auth_user deploy_package['upload_time'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -595,7 +626,7 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des # 更新差异化变量配置 deploy_package[field_pkg_diff_conf_var_name] = list(bind_app_diff_configs) deploy_package[field_pkg_db_diff_conf_var_name] = list(bind_db_diff_configs) - exist_package = self._get_deploy_package_by_name_unit(deploy_package['name'],unit_design_id) + exist_package = self._get_deploy_package_by_name_unit(deploy_package['name'], unit_design_id) if exist_package is None: package_result = self.create([deploy_package]) else: @@ -607,7 +638,8 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des # upgrade 文件清单仅追加 file_objs = self.find_files_by_status( baseline_package, new_package_guid, - split_to_list(deploy_package[field_pkg_db_upgrade_directory_name]) if deploy_package[field_pkg_db_upgrade_directory_name] else [], + split_to_list(deploy_package[field_pkg_db_upgrade_directory_name]) if deploy_package[ + field_pkg_db_upgrade_directory_name] else [], ['new', 'changed']) filtered_file_objs = [] # append new files @@ -620,7 +652,8 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des # rollback 文件清单仅追加 file_objs = self.find_files_by_status( baseline_package, new_package_guid, - split_to_list(deploy_package[field_pkg_db_rollback_directory_name]) if deploy_package[field_pkg_db_rollback_directory_name] else [], + split_to_list(deploy_package[field_pkg_db_rollback_directory_name]) if deploy_package[ + field_pkg_db_rollback_directory_name] else [], ['new', 'changed']) filtered_file_objs = [] # append new files @@ -634,15 +667,16 @@ def upload_compose_package(self, compose_filename:str, compose_fileobj, unit_des deploy_package['guid'] = new_package_guid return self.pure_update([deploy_package])['data'] return package_result['data'] - + """推送组合物料包[含差异化变量,包配置,包文件] 到nexus - """ - def push_compose_package(self, params, unit_design_id:str, deploy_package_id:str): - filename,fileobj,filesize = self.download_compose_package(deploy_package_id) + """ + + def push_compose_package(self, params, unit_design_id: str, deploy_package_id: str): + filename, fileobj, filesize = self.download_compose_package(deploy_package_id) # 新增nexus配置 nexus_server = CONF.pushnexus.server.rstrip('/') nexus_client = nexus.NeuxsClient(CONF.pushnexus.server, CONF.pushnexus.username, - CONF.pushnexus.password) + CONF.pushnexus.password) artifact_path = '/' if params and params.get('path', None): artifact_path = params['path'] @@ -650,20 +684,22 @@ def push_compose_package(self, params, unit_design_id:str, deploy_package_id:str unit_design = self._get_unit_design_by_id(unit_design_id) artifact_path = self.get_unit_design_artifact_path(unit_design) artifact_repository = CONF.pushnexus.repository - upload_result = nexus_client.upload(artifact_repository, artifact_path, os.path.basename(filename), 'application/octet-stream', fileobj) + upload_result = nexus_client.upload(artifact_repository, artifact_path, os.path.basename(filename), + 'application/octet-stream', fileobj) return upload_result - + """导出组合物料包[含差异化变量,包配置,包文件] - """ - def download_compose_package(self, deploy_package_id:str): + """ + + def download_compose_package(self, deploy_package_id: str): pack_fileobj = tempfile.NamedTemporaryFile() pack_filename = self._pack_compose_package(pack_fileobj.name, deploy_package_id) pack_fileobj.seek(0, os.SEEK_END) # 从当前位置(2)移动到文件末尾 # 获取文件指针当前位置,即文件的大小 pack_filesize = pack_fileobj.tell() pack_fileobj.seek(0, os.SEEK_SET) - return pack_filename,pack_fileobj,pack_filesize - + return pack_filename, pack_fileobj, pack_filesize + def _get_deploy_package_by_id(self, deploy_package_id: str): cmdb_client = self.get_cmdb_client() query = { @@ -680,7 +716,7 @@ def _get_deploy_package_by_id(self, deploy_package_id: str): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': deploy_package_id}) + {'rid': deploy_package_id}) return resp_json['data']['contents'][0] def _get_deploy_package_by_name_unit(self, pkg_name: str, unit_design_id: str): @@ -693,7 +729,7 @@ def _get_deploy_package_by_name_unit(self, pkg_name: str, unit_design_id: str): "name": "name", "operator": "eq", "value": pkg_name - },{ + }, { "name": "unit_design", "operator": "eq", "value": unit_design_id @@ -721,7 +757,7 @@ def _get_unit_design_by_id(self, unit_design_id: str): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.unit_design, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': unit_design_id}) + {'rid': unit_design_id}) return resp_json['data']['contents'][0] def _get_diff_configs_by_keyname(self, key_names): @@ -744,8 +780,8 @@ def _get_diff_configs_by_keyname(self, key_names): # {'names': key_names}) return resp_json['data']['contents'] return [] - - def _pack_compose_package(self, pack_filepath, deploy_package_id:str): + + def _pack_compose_package(self, pack_filepath, deploy_package_id: str): deploy_package = self._get_deploy_package_by_id(deploy_package_id) deploy_package_url = deploy_package['deploy_package_url'] @@ -771,8 +807,10 @@ def _pack_compose_package(self, pack_filepath, deploy_package_id:str): deploy_package_detail = self.get(None, deploy_package_id) package_app_diff_configs = deploy_package_detail.get(field_pkg_diff_conf_var_name, []) or [] package_db_diff_configs = deploy_package_detail.get(field_pkg_db_diff_conf_var_name, []) or [] - package_app_diff_configs = [{'bound': d['bound'], 'key':d['key'], 'diffExpr':d['diffExpr'], 'type': d['type']} for d in package_app_diff_configs] - package_db_diff_configs = [{'bound': d['bound'], 'key':d['key'], 'diffExpr':d['diffExpr'], 'type': d['type']} for d in package_db_diff_configs] + package_app_diff_configs = [{'bound': d['bound'], 'key': d['key'], 'diffExpr': d['diffExpr'], 'type': d['type']} + for d in package_app_diff_configs] + package_db_diff_configs = [{'bound': d['bound'], 'key': d['key'], 'diffExpr': d['diffExpr'], 'type': d['type']} + for d in package_db_diff_configs] # 下载原包文件 with tempfile.TemporaryDirectory() as tmp_path: package_path_file = self.download_from_url(tmp_path, deploy_package_url) @@ -790,7 +828,7 @@ def _pack_compose_package(self, pack_filepath, deploy_package_id:str): f.write(content) # 指定输出的 tar.gz 文件名 clean_filename = os.path.splitext(os.path.splitext(os.path.basename(package_path_file))[0])[0] - output_filename = os.path.join(tmp_path, '[W]'+clean_filename + '_weart.tar.gz') + output_filename = os.path.join(tmp_path, '[W]' + clean_filename + '_weart.tar.gz') # 创建压缩文件 with tarfile.open(pack_filepath, "w:gz") as tar: tar.add(package_path_file, arcname=os.path.basename(package_path_file)) @@ -805,7 +843,7 @@ def upload(self, filename, filetype, fileobj, baseline_package, package_type, un if not is_upload_local_enabled(): raise exceptions.PluginError(message=_("Package uploading is disabled!")) if self._is_compose_package(filename): - return self.upload_compose_package(filename, fileobj, unit_design_id,baseline_package=baseline_package) + return self.upload_compose_package(filename, fileobj, unit_design_id, baseline_package=baseline_package) unit_design = self._get_unit_design_by_id(unit_design_id) nexus_server = None if utils.bool_from_string(CONF.use_remote_nexus_only): @@ -834,7 +872,7 @@ def upload(self, filename, filetype, fileobj, baseline_package, package_type, un 'unit_design': unit_design_id, field_pkg_package_type_name: package_type }] - exist_package = self._get_deploy_package_by_name_unit(filename,unit_design_id) + exist_package = self._get_deploy_package_by_name_unit(filename, unit_design_id) if exist_package is None: package_result = self.create(package_rows) else: @@ -856,18 +894,19 @@ def upload_from_nexus(self, download_url, baseline_package, package_type, unit_d raise exceptions.PluginError(message=_("Package uploading is disabled!")) url_info = self.download_url_parse(download_url) if self._is_compose_package(url_info['filename']): - r_nexus_client = nexus.NeuxsClient(CONF.wecube.nexus.server, CONF.wecube.nexus.username, + r_nexus_client = nexus.NeuxsClient(CONF.wecube.nexus.server, CONF.wecube.nexus.username, CONF.wecube.nexus.password) - with r_nexus_client.download_stream(url=download_url) as resp: - stream = resp.raw - chunk_size = 1024 * 1024 - with tempfile.TemporaryFile() as tmp_file: + with r_nexus_client.download_stream(url=download_url) as resp: + stream = resp.raw + chunk_size = 1024 * 1024 + with tempfile.TemporaryFile() as tmp_file: + chunk = stream.read(chunk_size) + while chunk: + tmp_file.write(chunk) chunk = stream.read(chunk_size) - while chunk: - tmp_file.write(chunk) - chunk = stream.read(chunk_size) - tmp_file.seek(0) - return self.upload_compose_package(url_info['filename'], tmp_file, unit_design_id, baseline_package=baseline_package) + tmp_file.seek(0) + return self.upload_compose_package(url_info['filename'], tmp_file, unit_design_id, + baseline_package=baseline_package) cmdb_client = self.get_cmdb_client() query = { "dialect": { @@ -883,11 +922,11 @@ def upload_from_nexus(self, download_url, baseline_package, package_type, unit_d resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.unit_design, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': unit_design_id}) + {'rid': unit_design_id}) unit_design = resp_json['data']['contents'][0] if utils.bool_from_string(CONF.use_remote_nexus_only): # 更新unit_design.artifact_path && package.create 即上传成功 - + r_nexus_client = nexus.NeuxsClient(CONF.wecube.nexus.server, CONF.wecube.nexus.username, CONF.wecube.nexus.password) nexus_files = r_nexus_client.list(url_info['repository'], url_info['group']) @@ -910,9 +949,9 @@ def upload_from_nexus(self, download_url, baseline_package, package_type, unit_d 'upload_user': scoped_globals.GLOBALS.request.auth_user, 'upload_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'unit_design': unit_design_id, - field_pkg_package_type_name:package_type + field_pkg_package_type_name: package_type }] - exist_package = self._get_deploy_package_by_name_unit(url_info['filename'],unit_design_id) + exist_package = self._get_deploy_package_by_name_unit(url_info['filename'], unit_design_id) if exist_package is None: package_result = self.create(package_rows) else: @@ -949,24 +988,24 @@ def upload_from_nexus(self, download_url, baseline_package, package_type, unit_d package_rows = [{ 'baseline_package': baseline_package or None, 'name': - filename, + filename, 'code': - filename, + filename, 'deploy_package_url': - upload_result['downloadUrl'].replace(CONF.nexus.server.rstrip('/'), - CONF.wecube.server.rstrip('/') + '/artifacts'), + upload_result['downloadUrl'].replace(CONF.nexus.server.rstrip('/'), + CONF.wecube.server.rstrip('/') + '/artifacts'), 'md5_value': - calculate_md5(fileobj), + calculate_md5(fileobj), field_pkg_is_decompression_name: field_pkg_is_decompression_default_value, 'upload_user': - scoped_globals.GLOBALS.request.auth_user, + scoped_globals.GLOBALS.request.auth_user, 'upload_time': - datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'unit_design': - unit_design_id, - field_pkg_package_type_name:package_type + unit_design_id, + field_pkg_package_type_name: package_type }] - exist_package = self._get_deploy_package_by_name_unit(filename,unit_design_id) + exist_package = self._get_deploy_package_by_name_unit(filename, unit_design_id) if exist_package is None: package_result = self.create(package_rows) else: @@ -1090,17 +1129,19 @@ def _pop_none(d, k): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.unit_design, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': clean_data['unit_design']}) + {'rid': clean_data['unit_design']}) unit_design = resp_json['data']['contents'][0] if clean_data['package_guid']: # 有package_guid,则更新 - new_deploy_attrs = self._analyze_package_attrs(clean_data['package_guid'], clean_data['baseline_package_guid'], { - field_pkg_package_type_name: clean_data.get('package_type') - }) + new_deploy_attrs = self._analyze_package_attrs(clean_data['package_guid'], + clean_data['baseline_package_guid'], { + field_pkg_package_type_name: clean_data.get( + 'package_type') + }) # update 属性 new_deploy_attrs['guid'] = clean_data['package_guid'] self.pure_update([new_deploy_attrs]) - else : + else: # 没有package_guid,则创建 r_artifact_path = self.get_unit_design_artifact_path(unit_design) if r_artifact_path != '/': @@ -1108,8 +1149,10 @@ def _pop_none(d, k): group = '/' + group.rstrip('/') + '/' r_artifact_path = group download_url = CONF.wecube.nexus.server.rstrip( - '/') + '/repository/' + CONF.wecube.nexus.repository + r_artifact_path + clean_data['package_name'] - self.upload_from_nexus(download_url, clean_data['baseline_package_guid'], clean_data.get('package_type'), clean_data['unit_design']) + '/') + '/repository/' + CONF.wecube.nexus.repository + r_artifact_path + clean_data[ + 'package_name'] + self.upload_from_nexus(download_url, clean_data['baseline_package_guid'], + clean_data.get('package_type'), clean_data['unit_design']) result['results']['outputs'].append(single_result) except Exception as e: single_result['errorCode'] = '1' @@ -1127,14 +1170,15 @@ def _pop_none(d, k): return result # 纯cmdb创建物料包 - def create(self, data:list) -> list: + def create(self, data: list) -> list: cmdb_client = self.get_cmdb_client() return cmdb_client.create(CONF.wecube.wecmdb.citypes.deploy_package, data) - + # 纯cmdb更新物料包 - def pure_update(self, data:list) -> list: + def pure_update(self, data: list) -> list: cmdb_client = self.get_cmdb_client() - return cmdb_client.update(CONF.wecube.wecmdb.citypes.deploy_package, data, keep_origin_value=(field_pkg_key_service_code_name,)) + return cmdb_client.update(CONF.wecube.wecmdb.citypes.deploy_package, data, + keep_origin_value=(field_pkg_key_service_code_name,)) def update(self, data, @@ -1152,54 +1196,76 @@ def update(self, converter=BooleanNomalizedConverter(True), nullable=True), crud.ColumnValidator(field_pkg_package_type_name, validate_on=['update:O'], nullable=True), - crud.ColumnValidator(field_pkg_key_service_code_name, validate_on=['update:O'], rule=(list, tuple),rule_type='type', nullable=True), + crud.ColumnValidator(field_pkg_key_service_code_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', nullable=True), # app diff conf - crud.ColumnValidator(field_pkg_diff_conf_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_diff_conf_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_diff_conf_file_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_diff_conf_file_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_diff_conf_var_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', nullable=False), + crud.ColumnValidator(field_pkg_diff_conf_var_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', nullable=False), # app script - crud.ColumnValidator(field_pkg_script_file_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_script_file_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_deploy_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_deploy_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_start_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_start_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_stop_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_stop_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), # app log - crud.ColumnValidator(field_pkg_log_file_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_log_file_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_log_file_trade_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_log_file_trade_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_log_file_keyword_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_log_file_keyword_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_log_file_metric_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_log_file_metric_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_log_file_trace_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_log_file_trace_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), # db diff conf - crud.ColumnValidator(field_pkg_db_diff_conf_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_diff_conf_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_db_diff_conf_file_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_diff_conf_file_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_db_diff_conf_var_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_diff_conf_var_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), # db deploy - crud.ColumnValidator(field_pkg_db_deploy_file_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_deploy_file_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_db_deploy_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_deploy_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), # db upgrade - crud.ColumnValidator(field_pkg_db_upgrade_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_upgrade_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_db_upgrade_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_upgrade_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), # db rollback - crud.ColumnValidator(field_pkg_db_rollback_directory_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_rollback_directory_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), - crud.ColumnValidator(field_pkg_db_rollback_file_path_name, validate_on=['update:O'], rule=(list, tuple), rule_type='type', + crud.ColumnValidator(field_pkg_db_rollback_file_path_name, validate_on=['update:O'], rule=(list, tuple), + rule_type='type', converter=FileNameConcater(), nullable=False), ] cmdb_client = self.get_cmdb_client() @@ -1217,7 +1283,7 @@ def update(self, resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': deploy_package_id}) + {'rid': deploy_package_id}) deploy_package = resp_json['data']['contents'][0] data['guid'] = deploy_package_id clean_data = crud.ColumnValidator.get_clean_data(validates, data, 'update') @@ -1271,12 +1337,15 @@ def update(self, # 根据用户指定进行变量绑定 auto_bind = True if field_pkg_diff_conf_var_name in data: - clean_data[field_pkg_diff_conf_var_name] = [c['diffConfigGuid'] for c in data[field_pkg_diff_conf_var_name] if c['bound']] + clean_data[field_pkg_diff_conf_var_name] = [c['diffConfigGuid'] for c in data[field_pkg_diff_conf_var_name] + if c['bound']] auto_bind = False # 根据diff_conf_file计算变量进行更新绑定 if field_pkg_diff_conf_file_name in data and auto_bind: - bind_variables, new_create_variables = self._analyze_diff_var(deploy_package['guid'], deploy_package['deploy_package_url'], - deploy_package[field_pkg_diff_conf_file_name], data[field_pkg_diff_conf_file_name]) + bind_variables, new_create_variables = self._analyze_diff_var(deploy_package['guid'], + deploy_package['deploy_package_url'], + deploy_package[field_pkg_diff_conf_file_name], + data[field_pkg_diff_conf_file_name]) if bind_variables is not None: clean_data[field_pkg_diff_conf_var_name] = bind_variables # db部署支持 @@ -1289,28 +1358,33 @@ def update(self, db_auto_bind = False # 根据diff_conf_file计算变量进行更新绑定 if field_pkg_db_diff_conf_file_name in data and db_auto_bind: - bind_variables, new_create_variables = self._analyze_diff_var(deploy_package['guid'], deploy_package['deploy_package_url'], - deploy_package[field_pkg_db_diff_conf_file_name], data[field_pkg_db_diff_conf_file_name]) + bind_variables, new_create_variables = self._analyze_diff_var(deploy_package['guid'], + deploy_package['deploy_package_url'], + deploy_package[ + field_pkg_db_diff_conf_file_name], + data[field_pkg_db_diff_conf_file_name]) if bind_variables is not None: clean_data[field_pkg_db_diff_conf_var_name] = bind_variables if db_upgrade_detect: clean_data[field_pkg_db_upgrade_file_path_name] = FileNameConcater().convert( self.find_files_by_status( clean_data['baseline_package'], deploy_package_id, - split_to_list(clean_data[field_pkg_db_upgrade_directory_name]) if clean_data[field_pkg_db_upgrade_directory_name] else [], + split_to_list(clean_data[field_pkg_db_upgrade_directory_name]) if clean_data[ + field_pkg_db_upgrade_directory_name] else [], ['new', 'changed'])) if db_rollback_detect: clean_data[field_pkg_db_rollback_file_path_name] = FileNameConcater().convert( self.find_files_by_status( clean_data['baseline_package'], deploy_package_id, - split_to_list(clean_data[field_pkg_db_rollback_directory_name]) if clean_data[field_pkg_db_rollback_directory_name] else [], + split_to_list(clean_data[field_pkg_db_rollback_directory_name]) if clean_data[ + field_pkg_db_rollback_directory_name] else [], ['new', 'changed'])) - resp_json = cmdb_client.update(CONF.wecube.wecmdb.citypes.deploy_package, [clean_data], keep_origin_value=(field_pkg_key_service_code_name,)) + resp_json = cmdb_client.update(CONF.wecube.wecmdb.citypes.deploy_package, [clean_data], + keep_origin_value=(field_pkg_key_service_code_name,)) if with_detail: return self.get(unit_design_id, deploy_package_id) return resp_json['data'] - def _analyze_diff_var(self, package_id, package_url, origin_conf_list, new_conf_list): ''' conf_list是列表,每个元素是string 或 dict[{configKeyInfos,filename}] @@ -1421,7 +1495,7 @@ def get(self, unit_design_id, deploy_package_id): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': deploy_package_id}) + {'rid': deploy_package_id}) deploy_package = resp_json['data']['contents'][0] baseline_package = (deploy_package.get('baseline_package', None) or {}) if baseline_package: @@ -1438,16 +1512,19 @@ def get(self, unit_design_id, deploy_package_id): } resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): - raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': baseline_package['guid']}) - baseline_package = resp_json['data']['contents'][0] + LOG.warn("Can not find ci data for guid [%(rid)s]" % {'rid': baseline_package['guid']}) + # raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % + # {'rid': baseline_package['guid']}) + baseline_package = {} + else: + baseline_package = resp_json['data']['contents'][0] result = {} result['nextOperations'] = list(set(deploy_package.get('nextOperations', []))) result['packageId'] = deploy_package_id result['baseline_package'] = baseline_package.get('guid', None) # db部署支持 result[field_pkg_package_type_name] = deploy_package.get(field_pkg_package_type_name, - constant.PackageType.default) or constant.PackageType.default + constant.PackageType.default) or constant.PackageType.default # 文件对比[same, changed, new, deleted] baseline_cached_dir = None package_cached_dir = None @@ -1457,7 +1534,8 @@ def get(self, unit_design_id, deploy_package_id): baseline_package['deploy_package_url']) package_cached_dir = self.ensure_package_cached(deploy_package['guid'], deploy_package['deploy_package_url']) # common 字段 - result[field_pkg_is_decompression_name] = utils.bool_from_string(deploy_package[field_pkg_is_decompression_name], default=True) + result[field_pkg_is_decompression_name] = utils.bool_from_string( + deploy_package[field_pkg_is_decompression_name], default=True) result[field_pkg_package_type_name] = deploy_package[field_pkg_package_type_name] result[field_pkg_key_service_code_name] = deploy_package[field_pkg_key_service_code_name] # var 字段 @@ -1465,7 +1543,7 @@ def get(self, unit_design_id, deploy_package_id): result[field_pkg_db_diff_conf_var_name] = deploy_package.get(field_pkg_db_diff_conf_var_name, []) # |切割为列表, 更新文件的md5,comparisonResult,isDir fields = (field_pkg_diff_conf_directory_name, field_pkg_diff_conf_file_name, - field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, + field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, field_pkg_start_file_path_name, field_pkg_stop_file_path_name, field_pkg_log_file_directory_name) for field in fields: @@ -1480,13 +1558,13 @@ def get(self, unit_design_id, deploy_package_id): # db fields = (field_pkg_db_deploy_file_directory_name, field_pkg_db_deploy_file_path_name, field_pkg_db_diff_conf_directory_name, field_pkg_db_diff_conf_file_name, - field_pkg_db_upgrade_directory_name, field_pkg_db_rollback_file_path_name, + field_pkg_db_upgrade_directory_name, field_pkg_db_rollback_file_path_name, field_pkg_db_rollback_directory_name, field_pkg_db_upgrade_file_path_name,) for field in fields: result[field] = self.build_file_object(deploy_package.get(field, None)) if result[field_pkg_package_type_name] in (constant.PackageType.db, constant.PackageType.mixed): self.update_file_status(baseline_cached_dir, package_cached_dir, result[field]) - + package_app_diff_configs = [] if result[field_pkg_package_type_name] in (constant.PackageType.app, constant.PackageType.mixed): # 更新差异化配置文件的变量列表 @@ -1520,12 +1598,15 @@ def get(self, unit_design_id, deploy_package_id): # all_diff_configs = resp_json['data']['contents'] if package_app_diff_configs: # 更新差异化变量bound/diffConfigGuid/diffExpr/fixedDate/key/type - result[field_pkg_diff_conf_var_name] = self.update_diff_conf_variable(all_diff_configs, package_app_diff_configs, - result[field_pkg_diff_conf_var_name]) + result[field_pkg_diff_conf_var_name] = self.update_diff_conf_variable(all_diff_configs, + package_app_diff_configs, + result[field_pkg_diff_conf_var_name]) if package_db_diff_configs: # 更新差异化变量bound/diffConfigGuid/diffExpr/fixedDate/key/type - result[field_pkg_db_diff_conf_var_name] = self.update_diff_conf_variable(all_diff_configs, package_db_diff_configs, - result[field_pkg_db_diff_conf_var_name]) + result[field_pkg_db_diff_conf_var_name] = self.update_diff_conf_variable(all_diff_configs, + package_db_diff_configs, + result[ + field_pkg_db_diff_conf_var_name]) return result def baseline_compare(self, unit_design_id, deploy_package_id, baseline_package_id): @@ -1541,9 +1622,9 @@ def baseline_compare(self, unit_design_id, deploy_package_id, baseline_package_i package_type = baseline_package.get(field_pkg_package_type_name, constant.PackageType.default) or constant.PackageType.default is_decompression = baseline_package.get(field_pkg_is_decompression_name, - field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value + field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value key_service_code = baseline_package.get(field_pkg_key_service_code_name, - field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value + field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value new_deploy_package_attrs = self._analyze_package_attrs(deploy_package_id, baseline_package_id, { field_pkg_package_type_name: package_type, field_pkg_is_decompression_name: is_decompression, @@ -1557,24 +1638,24 @@ def baseline_compare(self, unit_design_id, deploy_package_id, baseline_package_i if package_type in (constant.PackageType.app, constant.PackageType.mixed): # |切割为列表 fields = (field_pkg_diff_conf_directory_name, field_pkg_diff_conf_file_name, - field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, - field_pkg_start_file_path_name, field_pkg_stop_file_path_name, - field_pkg_log_file_directory_name) + field_pkg_script_file_directory_name, field_pkg_deploy_file_path_name, + field_pkg_start_file_path_name, field_pkg_stop_file_path_name, + field_pkg_log_file_directory_name) for field in fields: result[field] = self.build_file_object(new_deploy_package_attrs.get(field, None)) if package_type in (constant.PackageType.app, constant.PackageType.mixed): # 更新文件的md5,comparisonResult,isDir self.update_file_status(baseline_cached_dir, package_cached_dir, result[field]) fields = (field_pkg_log_file_trade_name, field_pkg_log_file_keyword_name, - field_pkg_log_file_metric_name, field_pkg_log_file_trace_name,) + field_pkg_log_file_metric_name, field_pkg_log_file_trace_name,) for field in fields: result[field] = self.build_file_object(new_deploy_package_attrs.get(field, None)) # db部署支持 if package_type in (constant.PackageType.db, constant.PackageType.mixed): fields = (field_pkg_db_deploy_file_directory_name, field_pkg_db_deploy_file_path_name, - field_pkg_db_diff_conf_directory_name, field_pkg_db_diff_conf_file_name, - field_pkg_db_upgrade_directory_name, field_pkg_db_upgrade_file_path_name, - field_pkg_db_rollback_directory_name, field_pkg_db_rollback_file_path_name,) + field_pkg_db_diff_conf_directory_name, field_pkg_db_diff_conf_file_name, + field_pkg_db_upgrade_directory_name, field_pkg_db_upgrade_file_path_name, + field_pkg_db_rollback_directory_name, field_pkg_db_rollback_file_path_name,) for field in fields: result[field] = self.build_file_object(new_deploy_package_attrs.get(field, None)) self.update_file_status(baseline_cached_dir, package_cached_dir, result[field]) @@ -1597,7 +1678,7 @@ def baseline_files_compare(self, data, unit_design_id, deploy_package_id, baseli resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': deploy_package_id}) + {'rid': deploy_package_id}) deploy_package = resp_json['data']['contents'][0] baseline_package = None if baseline_package_id: @@ -1616,7 +1697,7 @@ def baseline_files_compare(self, data, unit_design_id, deploy_package_id, baseli resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': baseline_package_id}) + {'rid': baseline_package_id}) baseline_package = resp_json['data']['contents'][0] # 确认baselin和package文件已下载并解压缓存在本地(加锁) baseline_cached_dir = None @@ -1640,7 +1721,7 @@ def baseline_files_compare(self, data, unit_design_id, deploy_package_id, baseli b_is_dir = os.path.isdir(b_package_filepath) if exists is False and (b_exists is False or (not baseline_package and not b_exists)): raise exceptions.PluginError(message=_('%(file)s not exists in both package & baseline package') % - {'file': f['path']}) + {'file': f['path']}) if is_dir is True or b_is_dir is True: raise exceptions.PluginError(message=_('%(file)s is dir, not regular file') % {'file': f['path']}) result = {'path': f['path'], 'content': '', 'baseline_content': ''} @@ -1805,7 +1886,7 @@ def _get_file_list(baseline_path, package_path, file_list, with_dir, recursive): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': deploy_package_id}) + {'rid': deploy_package_id}) deploy_package = resp_json['data']['contents'][0] baseline_package = None if baseline_package_id: @@ -1823,9 +1904,13 @@ def _get_file_list(baseline_path, package_path, file_list, with_dir, recursive): self.set_package_query_fields(query) resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.deploy_package, query) if not resp_json.get('data', {}).get('contents', []): - raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': baseline_package_id}) - baseline_package = resp_json['data']['contents'][0] + LOG.warn(_("Can not find ci data for guid [%(rid)s]") % + {'rid': baseline_package_id}) + # raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % + # {'rid': baseline_package_id}) + baseline_package = {} + else: + baseline_package = resp_json['data']['contents'][0] baseline_cached_dir = None package_cached_dir = None if baseline_package: @@ -1925,6 +2010,7 @@ def update_file_variable(self, package_cached_dir, files): i['configKeyInfos'] = artifact_utils.variable_parse(content, spliters) else: i['configKeyInfos'] = [] + return files def update_diff_conf_variable(self, all_diff_configs, package_diff_configs, bounded_diff_configs): ''' @@ -2008,8 +2094,9 @@ def download_from_url(self, dir_path, url, random_name=False): # 替换外部下载地址为Nexus内部地址 urlinfo = urllib.parse.urlparse(url) nexusurlinfo = urllib.parse.urlparse(nexus_server) - - new_url = urlinfo._replace(scheme=nexusurlinfo.scheme, netloc=nexusurlinfo.netloc,path=remove_prefix(urlinfo.path,'/artifacts')).geturl() + + new_url = urlinfo._replace(scheme=nexusurlinfo.scheme, netloc=nexusurlinfo.netloc, + path=remove_prefix(urlinfo.path, '/artifacts')).geturl() # new_url = url.replace(CONF.wecube.server.rstrip('/') + '/artifacts', nexus_server) client = nexus.NeuxsClient(nexus_server, nexus_username, nexus_password) client.download_file(filepath, url=new_url) @@ -2018,7 +2105,8 @@ def download_from_url(self, dir_path, url, random_name=False): client.download_file(filepath, CONF.wecube.s3.access_key, CONF.wecube.s3.secret_key) return filepath - def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_attrs:map, do_bind_vars=True) -> map: + def _analyze_package_attrs(self, package_id: str, baseline_package_id: str, input_attrs: map, + do_bind_vars=True) -> map: # input_attrs都是以CMDB字段值方式传递,比如列表实际上是A|B|C格式 ret_data = {} deploy_package = self._get_deploy_package_by_id(package_id) @@ -2029,12 +2117,21 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ baseline_package = self._get_deploy_package_by_id(baseline_package_id) self.ensure_package_cached(baseline_package_id, baseline_package['deploy_package_url']) # common - ret_data[field_pkg_is_decompression_name] = input_attrs.get(field_pkg_is_decompression_name, None) or baseline_package.get(field_pkg_is_decompression_name, field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value - ret_data[field_pkg_package_type_name] = input_attrs.get(field_pkg_package_type_name, None) or baseline_package.get(field_pkg_package_type_name, field_pkg_package_type_default_value) or field_pkg_package_type_default_value - ret_data[field_pkg_key_service_code_name] = input_attrs.get(field_pkg_key_service_code_name, None) or baseline_package.get(field_pkg_key_service_code_name, field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value + ret_data[field_pkg_is_decompression_name] = input_attrs.get(field_pkg_is_decompression_name, + None) or baseline_package.get( + field_pkg_is_decompression_name, + field_pkg_is_decompression_default_value) or field_pkg_is_decompression_default_value + ret_data[field_pkg_package_type_name] = input_attrs.get(field_pkg_package_type_name, + None) or baseline_package.get( + field_pkg_package_type_name, field_pkg_package_type_default_value) or field_pkg_package_type_default_value + ret_data[field_pkg_key_service_code_name] = input_attrs.get(field_pkg_key_service_code_name, + None) or baseline_package.get( + field_pkg_key_service_code_name, + field_pkg_key_service_code_default_value) or field_pkg_key_service_code_default_value # app diff conf FieldSetting = namedtuple("FieldSetting", 'name, default_value') - fset = FieldSetting(name=field_pkg_diff_conf_directory_name, default_value=field_pkg_diff_conf_directory_default_value) + fset = FieldSetting(name=field_pkg_diff_conf_directory_name, + default_value=field_pkg_diff_conf_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: @@ -2045,19 +2142,25 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ for f in file_objs: for ext in available_extensions: if fnmatch.fnmatch(f['name'], '*' + ext): - filtered_file_objs.append(f) + # 扫描每个文件,有差异化变量的才加入清单 + var_rets = self.update_file_variable(self.get_package_cached_path(package_id), [{'filename': f['path']}]) + if len(var_rets[0]['configKeyInfos']) > 0: + filtered_file_objs.append(f) + ret_data[field_pkg_diff_conf_file_name] = FilePathConcater().convert(filtered_file_objs) if do_bind_vars: conf_files = self.build_file_object(ret_data[field_pkg_diff_conf_file_name]) - bind_variables, new_create_variables = self._analyze_diff_var(package_id, deploy_package['deploy_package_url'], - [], conf_files) + bind_variables, new_create_variables = self._analyze_diff_var(package_id, + deploy_package['deploy_package_url'], + [], conf_files) if bind_variables is not None: ret_data[field_pkg_diff_conf_var_name] = bind_variables else: # 差异化文件清单继承删除+继承追加(扩展名限制,去重,保持原顺序) baseline_file_value = baseline_package[field_pkg_diff_conf_file_name] baseline_file_obj = self.build_file_object(baseline_file_value) - self.update_file_status(self.get_package_cached_path(baseline_package_id), self.get_package_cached_path(package_id), + self.update_file_status(self.get_package_cached_path(baseline_package_id), + self.get_package_cached_path(package_id), baseline_file_obj, file_key='filename') # remove deleted status filtered_file_objs = [f for f in baseline_file_obj if f['comparisonResult'] != 'deleted'] @@ -2074,13 +2177,17 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ for ext in available_extensions: if fnmatch.fnmatch(f['filename'], '*' + ext): if f['filename'] not in changed_file_objs_map: - filtered_file_objs.append(f) + # 扫描每个文件,有差异化变量的才加入清单 + var_rets = self.update_file_variable(self.get_package_cached_path(package_id), [{'filename': f['filename']}]) + if len(var_rets[0]['configKeyInfos']) > 0: + filtered_file_objs.append(f) # filtered_file_objs.sort(key=lambda x: x['filename'], reverse=False) ret_data[field_pkg_diff_conf_file_name] = FileNameConcater().convert(filtered_file_objs) if do_bind_vars: conf_files = self.build_file_object(ret_data[field_pkg_diff_conf_file_name]) - bind_variables, new_create_variables = self._analyze_diff_var(package_id, deploy_package['deploy_package_url'], - [], conf_files) + bind_variables, new_create_variables = self._analyze_diff_var(package_id, + deploy_package['deploy_package_url'], + [], conf_files) if new_create_variables is not None: bind_variables = [c['guid'] for c in baseline_package[field_pkg_diff_conf_var_name]] bind_variables.extend(new_create_variables) @@ -2095,12 +2202,16 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ for f in file_objs: for ext in available_extensions: if fnmatch.fnmatch(f['name'], '*' + ext): - filtered_file_objs.append(f) + # 扫描每个文件,有差异化变量的才加入清单 + var_rets = self.update_file_variable(self.get_package_cached_path(package_id), [{'filename': f['path']}]) + if len(var_rets[0]['configKeyInfos']) > 0: + filtered_file_objs.append(f) ret_data[field_pkg_diff_conf_file_name] = FilePathConcater().convert(filtered_file_objs) if do_bind_vars: conf_files = self.build_file_object(ret_data[field_pkg_diff_conf_file_name]) - bind_variables, new_create_variables = self._analyze_diff_var(package_id, deploy_package['deploy_package_url'], - [], conf_files) + bind_variables, new_create_variables = self._analyze_diff_var(package_id, + deploy_package['deploy_package_url'], + [], conf_files) if bind_variables is not None: ret_data[field_pkg_diff_conf_var_name] = bind_variables else: @@ -2114,7 +2225,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ # 差异化文件清单继承删除+继承追加(扩展名限制,去重,保持原顺序) baseline_file_value = baseline_package[field_pkg_diff_conf_file_name] baseline_file_obj = self.build_file_object(baseline_file_value) - self.update_file_status(self.get_package_cached_path(baseline_package_id), self.get_package_cached_path(package_id), + self.update_file_status(self.get_package_cached_path(baseline_package_id), + self.get_package_cached_path(package_id), baseline_file_obj, file_key='filename') # remove deleted status filtered_file_objs = [f for f in baseline_file_obj if f['comparisonResult'] != 'deleted'] @@ -2131,31 +2243,42 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ for ext in available_extensions: if fnmatch.fnmatch(f['filename'], '*' + ext): if f['filename'] not in changed_file_objs_map: - filtered_file_objs.append(f) + # 扫描每个文件,有差异化变量的才加入清单 + var_rets = self.update_file_variable(self.get_package_cached_path(package_id), [{'filename': f['filename']}]) + if len(var_rets[0]['configKeyInfos']) > 0: + filtered_file_objs.append(f) # filtered_file_objs.sort(key=lambda x: x['filename'], reverse=False) ret_data[field_pkg_diff_conf_file_name] = FileNameConcater().convert(filtered_file_objs) if do_bind_vars: conf_files = self.build_file_object(ret_data[field_pkg_diff_conf_file_name]) - bind_variables, new_create_variables = self._analyze_diff_var(package_id, deploy_package['deploy_package_url'], - [], conf_files) + bind_variables, new_create_variables = self._analyze_diff_var(package_id, + deploy_package['deploy_package_url'], + [], conf_files) if new_create_variables is not None: bind_variables = [c['guid'] for c in baseline_package[field_pkg_diff_conf_var_name]] bind_variables.extend(new_create_variables) ret_data[field_pkg_diff_conf_var_name] = bind_variables # app bin script - fset = FieldSetting(name=field_pkg_script_file_directory_name, default_value=field_pkg_script_file_directory_default_value) + fset = FieldSetting(name=field_pkg_script_file_directory_name, + default_value=field_pkg_script_file_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: # 无输入则填充默认,否则使用输入值 - ret_data[field_pkg_deploy_file_path_name] = input_attrs.get(field_pkg_deploy_file_path_name, None) or field_pkg_deploy_file_path_default_value - ret_data[field_pkg_start_file_path_name] = input_attrs.get(field_pkg_start_file_path_name, None) or field_pkg_start_file_path_default_value - ret_data[field_pkg_stop_file_path_name] = input_attrs.get(field_pkg_stop_file_path_name, None) or field_pkg_stop_file_path_default_value + ret_data[field_pkg_deploy_file_path_name] = input_attrs.get(field_pkg_deploy_file_path_name, + None) or field_pkg_deploy_file_path_default_value + ret_data[field_pkg_start_file_path_name] = input_attrs.get(field_pkg_start_file_path_name, + None) or field_pkg_start_file_path_default_value + ret_data[field_pkg_stop_file_path_name] = input_attrs.get(field_pkg_stop_file_path_name, + None) or field_pkg_stop_file_path_default_value else: # 无输入则仅继承,否则使用输入值 - ret_data[field_pkg_deploy_file_path_name] = input_attrs.get(field_pkg_deploy_file_path_name, None) or baseline_package[field_pkg_deploy_file_path_name] - ret_data[field_pkg_start_file_path_name] = input_attrs.get(field_pkg_start_file_path_name, None) or baseline_package[field_pkg_start_file_path_name] - ret_data[field_pkg_stop_file_path_name] = input_attrs.get(field_pkg_stop_file_path_name, None) or baseline_package[field_pkg_stop_file_path_name] + ret_data[field_pkg_deploy_file_path_name] = input_attrs.get(field_pkg_deploy_file_path_name, None) or \ + baseline_package[field_pkg_deploy_file_path_name] + ret_data[field_pkg_start_file_path_name] = input_attrs.get(field_pkg_start_file_path_name, None) or \ + baseline_package[field_pkg_start_file_path_name] + ret_data[field_pkg_stop_file_path_name] = input_attrs.get(field_pkg_stop_file_path_name, None) or \ + baseline_package[field_pkg_stop_file_path_name] else: if not baseline_package: # 填充默认目录值,脚本文件清单填充默认 @@ -2193,21 +2316,30 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ ret_data[field_pkg_start_file_path_name] = baseline_package[field_pkg_start_file_path_name] ret_data[field_pkg_stop_file_path_name] = baseline_package[field_pkg_stop_file_path_name] # app log - fset = FieldSetting(name=field_pkg_log_file_directory_name, default_value=field_pkg_log_file_directory_default_value) + fset = FieldSetting(name=field_pkg_log_file_directory_name, + default_value=field_pkg_log_file_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: # 无输入则填充默认,否则使用输入值 - ret_data[field_pkg_log_file_trade_name] = input_attrs.get(field_pkg_log_file_trade_name, None) or field_pkg_log_file_trade_default_value - ret_data[field_pkg_log_file_keyword_name] = input_attrs.get(field_pkg_log_file_keyword_name, None) or field_pkg_log_file_keyword_default_value - ret_data[field_pkg_log_file_metric_name] = input_attrs.get(field_pkg_log_file_metric_name, None) or field_pkg_log_file_metric_default_value - ret_data[field_pkg_log_file_trace_name] = input_attrs.get(field_pkg_log_file_trace_name, None) or field_pkg_log_file_trace_default_value + ret_data[field_pkg_log_file_trade_name] = input_attrs.get(field_pkg_log_file_trade_name, + None) or field_pkg_log_file_trade_default_value + ret_data[field_pkg_log_file_keyword_name] = input_attrs.get(field_pkg_log_file_keyword_name, + None) or field_pkg_log_file_keyword_default_value + ret_data[field_pkg_log_file_metric_name] = input_attrs.get(field_pkg_log_file_metric_name, + None) or field_pkg_log_file_metric_default_value + ret_data[field_pkg_log_file_trace_name] = input_attrs.get(field_pkg_log_file_trace_name, + None) or field_pkg_log_file_trace_default_value else: # 无输入则仅继承,否则使用输入值 - ret_data[field_pkg_log_file_trade_name] = input_attrs.get(field_pkg_log_file_trade_name, None) or baseline_package[field_pkg_log_file_trade_name] - ret_data[field_pkg_log_file_keyword_name] = input_attrs.get(field_pkg_log_file_keyword_name, None) or baseline_package[field_pkg_log_file_keyword_name] - ret_data[field_pkg_log_file_metric_name] = input_attrs.get(field_pkg_log_file_metric_name, None) or baseline_package[field_pkg_log_file_metric_name] - ret_data[field_pkg_log_file_trace_name] = input_attrs.get(field_pkg_log_file_trace_name, None) or baseline_package[field_pkg_log_file_trace_name] + ret_data[field_pkg_log_file_trade_name] = input_attrs.get(field_pkg_log_file_trade_name, None) or \ + baseline_package[field_pkg_log_file_trade_name] + ret_data[field_pkg_log_file_keyword_name] = input_attrs.get(field_pkg_log_file_keyword_name, None) or \ + baseline_package[field_pkg_log_file_keyword_name] + ret_data[field_pkg_log_file_metric_name] = input_attrs.get(field_pkg_log_file_metric_name, None) or \ + baseline_package[field_pkg_log_file_metric_name] + ret_data[field_pkg_log_file_trace_name] = input_attrs.get(field_pkg_log_file_trace_name, None) or \ + baseline_package[field_pkg_log_file_trace_name] else: if not baseline_package: # 填充默认目录值,日志文件清单填充默认 @@ -2224,11 +2356,13 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ ret_data[field_pkg_log_file_metric_name] = baseline_package[field_pkg_log_file_metric_name] ret_data[field_pkg_log_file_trace_name] = baseline_package[field_pkg_log_file_trace_name] # db diff 数据库差异化文件不继承 - fset = FieldSetting(name=field_pkg_db_diff_conf_directory_name, default_value=field_pkg_db_diff_conf_directory_default_value) + fset = FieldSetting(name=field_pkg_db_diff_conf_directory_name, + default_value=field_pkg_db_diff_conf_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] # 无输入则填充默认,否则使用输入值 - ret_data[field_pkg_db_diff_conf_file_name] = input_attrs.get(field_pkg_db_diff_conf_file_name, None) or field_pkg_db_diff_conf_file_default_value + ret_data[field_pkg_db_diff_conf_file_name] = input_attrs.get(field_pkg_db_diff_conf_file_name, + None) or field_pkg_db_diff_conf_file_default_value else: if not baseline_package: # 填充默认目录值,差异化文件清单填充默认(空) @@ -2239,7 +2373,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ ret_data[fset.name] = baseline_package[fset.name] ret_data[field_pkg_db_diff_conf_file_name] = field_pkg_db_diff_conf_file_default_value # db install - fset = FieldSetting(name=field_pkg_db_deploy_file_directory_name, default_value=field_pkg_db_deploy_file_directory_default_value) + fset = FieldSetting(name=field_pkg_db_deploy_file_directory_name, + default_value=field_pkg_db_deploy_file_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: @@ -2256,7 +2391,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ # 文件清单继承追加 baseline_file_value = baseline_package[field_pkg_db_deploy_file_path_name] baseline_file_obj = self.build_file_object(baseline_file_value) - self.update_file_status(self.get_package_cached_path(baseline_package_id), self.get_package_cached_path(package_id), + self.update_file_status(self.get_package_cached_path(baseline_package_id), + self.get_package_cached_path(package_id), baseline_file_obj, file_key='filename') changed_file_objs = [f for f in baseline_file_obj if f['comparisonResult'] == 'changed'] changed_file_objs_map = set([f['filename'] for f in changed_file_objs]) @@ -2297,7 +2433,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ # 文件清单继承追加 baseline_file_value = baseline_package[field_pkg_db_deploy_file_path_name] baseline_file_obj = self.build_file_object(baseline_file_value) - self.update_file_status(self.get_package_cached_path(baseline_package_id), self.get_package_cached_path(package_id), + self.update_file_status(self.get_package_cached_path(baseline_package_id), + self.get_package_cached_path(package_id), baseline_file_obj, file_key='filename') changed_file_objs = [f for f in baseline_file_obj if f['comparisonResult'] == 'changed'] changed_file_objs_map = set([f['filename'] for f in changed_file_objs]) @@ -2316,7 +2453,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ # baseline_file_obj.sort(key=lambda x: x['filename'], reverse=False) ret_data[field_pkg_db_deploy_file_path_name] = FileNameConcater().convert(baseline_file_obj) # db upgrade - fset = FieldSetting(name=field_pkg_db_upgrade_directory_name, default_value=field_pkg_db_upgrade_directory_default_value) + fset = FieldSetting(name=field_pkg_db_upgrade_directory_name, + default_value=field_pkg_db_upgrade_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: @@ -2382,7 +2520,8 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ filtered_file_objs.append(f) ret_data[field_pkg_db_upgrade_file_path_name] = FileNameConcater().convert(filtered_file_objs) # db rollback - fset = FieldSetting(name=field_pkg_db_rollback_directory_name, default_value=field_pkg_db_rollback_directory_default_value) + fset = FieldSetting(name=field_pkg_db_rollback_directory_name, + default_value=field_pkg_db_rollback_directory_default_value) if input_attrs.get(fset.name, None): ret_data[fset.name] = input_attrs[fset.name] if not baseline_package: @@ -2448,7 +2587,7 @@ def _analyze_package_attrs(self, package_id:str, baseline_package_id:str, input_ filtered_file_objs.append(f) ret_data[field_pkg_db_rollback_file_path_name] = FileNameConcater().convert(filtered_file_objs) return ret_data - + class UnitDesignNexusPackages(WeCubeResource): def get_unit_design_artifact_path(self, unit_design): @@ -2478,7 +2617,7 @@ def list_by_post(self, query, unit_design_id): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.unit_design, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': unit_design_id}) + {'rid': unit_design_id}) unit_design = resp_json['data']['contents'][0] nexus_client = nexus.NeuxsClient(CONF.wecube.nexus.server, CONF.wecube.nexus.username, CONF.wecube.nexus.password) @@ -2494,7 +2633,7 @@ def _extract_key(name): return tuple([int(i) for i in re.findall('\d+', name)]) return sorted(datas, key=lambda x: _extract_key(x['name']), reverse=True) - + def get(self, unit_design_id): cmdb_client = self.get_cmdb_client() query = { @@ -2511,10 +2650,11 @@ def get(self, unit_design_id): resp_json = cmdb_client.retrieve(CONF.wecube.wecmdb.citypes.unit_design, query) if not resp_json.get('data', {}).get('contents', []): raise exceptions.NotFoundError(message=_("Can not find ci data for guid [%(rid)s]") % - {'rid': unit_design_id}) + {'rid': unit_design_id}) unit_design = resp_json['data']['contents'][0] return {'artifact_path': self.get_unit_design_artifact_path(unit_design)} + class DiffConfig(WeCubeResource): def update(self, data): cmdb_client = self.get_cmdb_client() @@ -2606,6 +2746,7 @@ def list_by_post(self, query): return result + class CiData(WeCubeResource): def list_by_post(self, query, citype): cmdb_client = self.get_cmdb_client() @@ -2614,3 +2755,45 @@ def list_by_post(self, query, citype): query.setdefault('paging', False) resp_json = cmdb_client.retrieve(citype, query) return resp_json['data'] + + +class AppInstancePackages(WeCubeResource): + def get_variable_values(self, post_data): + """差异化变量试算""" + cmdb_client = self.get_cmdb_client() + ret = cmdb_client.render_variable_values(post_data) + return ret['data'][0]['variable_values'] if ret['data'] else "" + + +class UnitDesignApps(WeCubeResource): + + def list_by_post(self, payload): + """应用/DB实例列表""" + wecube_client = wecube.WeCubeClient(CONF.wecube.server, self.token) + + if payload.get('type') == "db": + filter_expression = CONF.wecube.wecmdb.expressions.db_filter + else: + filter_expression = CONF.wecube.wecmdb.expressions.app_filter + + filters = [ + { + "index": 0, + "packageName": "wecmdb", + "entityName": CONF.wecube.wecmdb.citypes.unit_design, + "attributeFilters": [ + { + "name": "guid", + "value": payload.get('guid'), + "operator": "eq" + } + ] + } + ] + ret = wecube_client.post( + wecube_client.build_url('/platform/v1/data-model/dme/integrated-query'), { + 'dataModelExpression': filter_expression, + 'filters': filters + }) + + return ret['data'] or [] diff --git a/artifacts-corepy/artifacts_corepy/apps/package/controller.py b/artifacts-corepy/artifacts_corepy/apps/package/controller.py index 43b5ba9b..4504282d 100644 --- a/artifacts-corepy/artifacts_corepy/apps/package/controller.py +++ b/artifacts-corepy/artifacts_corepy/apps/package/controller.py @@ -58,7 +58,7 @@ class CollectionUser(Collection): allow_methods = ('GET', ) name = 'artifacts.users.list' resource = package_api.User - + class CollectionCiData(POSTCollection): allow_methods = ('POST',) name = 'artifacts.diffconfigs' @@ -114,11 +114,28 @@ class CollectionPackageStatistics(POSTCollection): def list(self, req, criteria, **kwargs): return self.make_resource(req).get_package_statistics(req.json, **kwargs) + +class CollectionAppVariables(POSTCollection): + allow_methods = ('POST', ) + name = 'artifacts.packages.variable-values' + resource = package_api.AppInstancePackages + + def list(self, req, criteria, **kwargs): + return self.make_resource(req).get_variable_values(req.json, **kwargs) + + +class CollectionUnitDesignApps(POSTCollection): + allow_methods = ('POST', ) + name = 'artifacts.unit-design.app-instances' + resource = package_api.UnitDesignApps + + class CollectionUnitDesignNexusPackages(POSTCollection): allow_methods = ('POST', ) name = 'artifacts.unit-design.nexus.packages' resource = package_api.UnitDesignNexusPackages - + + class ItemUnitDesignNexusPackages(Item): allow_methods = ('GET', ) name = 'artifacts.unit-design.nexus.path' @@ -334,7 +351,7 @@ class PackageFromRemote(base_controller.Controller): def on_post(self, req, resp, **kwargs): resp.json = self.resource().upload_and_create2(req.json, **kwargs) resp.status = falcon.HTTP_200 - + class DownloadComposePackage(base_controller.Controller): allow_methods = ('GET',) name = 'artifacts.downloadcomposepackage' @@ -346,7 +363,7 @@ def on_get(self, req, resp, **kwargs): resp.set_header('Content-Disposition', 'attachment;filename="%s"' % urllib.parse.quote(os.path.basename(filename))) resp.set_header('Content-Type', 'application/octet-stream') resp.status = falcon.HTTP_200 - + class PushComposePackage(base_controller.Controller): allow_methods = ('POST',) name = 'artifacts.pushcomposepackage' @@ -363,7 +380,7 @@ def on_post(self, req, resp, **kwargs): 'message': 'success' } resp.status = falcon.HTTP_200 - + class SystemConfig(base_controller.Controller): allow_methods = ('GET',) name = 'artifacts.systemconfig' diff --git a/artifacts-corepy/artifacts_corepy/apps/package/route.py b/artifacts-corepy/artifacts_corepy/apps/package/route.py index 4c58a249..7bef16c4 100644 --- a/artifacts-corepy/artifacts_corepy/apps/package/route.py +++ b/artifacts-corepy/artifacts_corepy/apps/package/route.py @@ -24,6 +24,7 @@ def __call__(self, req, resp, repository): resp.set_header('Content-Disposition', stream.headers.get('Content-Disposition')) resp.set_header('Content-Type', stream.headers.get('Content-Type')) + class EntityAdapter(object): def __call__(self, req, resp, package_name, entity_name, action_name): server = CONF.wecube.server @@ -63,6 +64,36 @@ def __call__(self, req, resp, package_name, entity_name, action_name): }) +class RoleAdapter(object): + def __call__(self, req, resp, action_name): + server = CONF.wecube.server + token = req.auth_token + client = wecube.WeCubeClient(server, token) + if action_name == 'retrieve': + data = client.get('/platform/v1/roles/retrieve') + resp.json = {'code': 200, 'status': 'OK', 'data': data['data'], 'message': 'success'} + else: + raise exceptions.NotFoundError( + _('%(action_name)s for role not supported') % { + 'action_name': action_name, + }) + + +class UserAdapter(object): + def __call__(self, req, resp, action_name): + server = CONF.wecube.server + token = req.auth_token + client = wecube.WeCubeClient(server, token) + if action_name == 'roles': + data = client.get('/platform/v1/users/roles') + resp.json = {'code': 200, 'status': 'OK', 'data': data['data'], 'message': 'success'} + else: + raise exceptions.NotFoundError( + _('%(action_name)s for user not supported') % { + 'action_name': action_name, + }) + + def add_routes(api): # process api.add_route('/artifacts/process/definitions', controller.CollectionProcessDef()) @@ -77,6 +108,8 @@ def add_routes(api): api.add_route('/artifacts/ci-types', controller.CollectionCiTypes()) api.add_route('/artifacts/enum/system/codes/{cat_id}', controller.ItemEnumCodes()) api.add_route('/artifacts/ci-types/{ci_type_id}/operations', controller.ItemCITypeOperations()) + api.add_route('/artifacts/app-instances/variable-values', controller.CollectionAppVariables()) + api.add_route('/artifacts/unit-designs/app-instances', controller.CollectionUnitDesignApps()) api.add_route('/artifacts/unit-designs/{unit_design_id}/packages/query', controller.CollectionUnitDesignPackages()) api.add_route('/artifacts/unit-designs/{unit_design_id}/packages/statistics', controller.CollectionPackageStatistics()) api.add_route('/artifacts/ci-types/{ci_type_id}/references/by', controller.ItemCiReferences()) @@ -88,6 +121,14 @@ def add_routes(api): EntityAdapter(), r'/artifacts/platform/v1/packages/(?P[-_A-Za-z0-9]+)/entities/(?P[-_A-Za-z0-9]+)/(?P[-_A-Za-z0-9]+)' ) + api.add_sink( + RoleAdapter(), + r'/artifacts/platform/v1/roles/(?P[-_A-Za-z0-9]+)' + ) + api.add_sink( + UserAdapter(), + r'/artifacts/platform/v1/users/(?P[-_A-Za-z0-9]+)' + ) # nexus query api.add_route('/artifacts/unit-designs/{unit_design_id}/packages/queryNexusDirectiry', controller.CollectionUnitDesignNexusPackages()) @@ -123,13 +164,13 @@ def add_routes(api): # packages exist in remote nexus but not in cmdb api.add_route('/artifacts/entities/packages/query', controller.CollectionOnlyInRemoteNexusPackages()) - + # compose package api.add_route('/artifacts/packages/{deploy_package_id}/download', controller.DownloadComposePackage()) api.add_route('/artifacts/unit-designs/{unit_design_id}/packages/{deploy_package_id}/push', controller.PushComposePackage()) - + # system config api.add_route('/artifacts/sysconfig', controller.SystemConfig()) diff --git a/artifacts-corepy/artifacts_corepy/apps/variable/__init__.py b/artifacts-corepy/artifacts_corepy/apps/variable/__init__.py new file mode 100644 index 00000000..a094396d --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/variable/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from artifacts_corepy.apps.variable import route diff --git a/artifacts-corepy/artifacts_corepy/apps/variable/api.py b/artifacts-corepy/artifacts_corepy/apps/variable/api.py new file mode 100644 index 00000000..7427d874 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/variable/api.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import collections +import logging +from talos.core import config +from talos.utils.scoped_globals import GLOBALS + +from artifacts_corepy.db import resource + +from artifacts_corepy.common import exceptions + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class DiffConfTemplate(resource.DiffConfTemplate): + def _addtional_create(self, session, data, created): + if 'roles' in data: + for perm, perm_roles in data['roles'].items(): + for perm_role in perm_roles: + if not perm_role: + continue + resource.DiffConfTemplateRole(transaction=session).create({ + 'diff_conf_template_id': created['id'], + 'role': perm_role, + 'permission': perm + }) + + def _addtional_update(self, session, rid, data, before_updated, after_updated): + if 'roles' in data: + current_roles = collections.defaultdict(set) + for r in after_updated["roles"]: + current_roles[r['permission']].add(r['role']) + + for perm, perm_roles in data['roles'].items(): + deleted_perm_roles = current_roles[perm] - set(perm_roles) + new_perm_roles = set(perm_roles) - current_roles[perm] + + for perm_role in new_perm_roles: + if not perm_role: + continue + resource.DiffConfTemplateRole(transaction=session).create({ + 'diff_conf_template_id': after_updated['id'], + 'role': perm_role, + 'permission': perm + }) + + if deleted_perm_roles: + resource.DiffConfTemplateRole(transaction=session).delete_all(filters={ + 'diff_conf_template_id': before_updated['id'], + 'permission': perm, + 'role': { + 'in': list(deleted_perm_roles) + } + }) + + # update final roles + after_updated["roles"] = [ + {"permission": perm, "role": perm_role} + for perm, perm_roles in data['roles'].items() + for perm_role in perm_roles + ] + + def _addtional_list(self, query, filters): + """权限控制,角色数据过滤""" + query = super()._addtional_list(query, filters) + permission_filters = {"roles.role": {'in': list(GLOBALS.request.auth_permissions)}} + query = self._apply_filters(query, self.orm_meta, permission_filters) + return query + + def _addtional_count(self, query, filters): + return self._addtional_list(query, filters) + + def list(self, filters=None, orders=None, offset=None, limit=None, hooks=None): + """ + 补充 is_owner + """ + results = super().list(filters, orders, offset, limit, hooks) + for result in results: + result['is_owner'] = GLOBALS.request.auth_user == result['create_user'] + return results + + def get(self, rid): + """ + 补充 is_owner + """ + result = super().get(rid) + if result: + result['is_owner'] = GLOBALS.request.auth_user == result['create_user'] + + return result diff --git a/artifacts-corepy/artifacts_corepy/apps/variable/controller.py b/artifacts-corepy/artifacts_corepy/apps/variable/controller.py new file mode 100644 index 00000000..80608901 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/variable/controller.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from artifacts_corepy.common.mixin import CollectionController, ItemController +from artifacts_corepy.apps.variable import api + + +class CollectionDiffConfTemplates(CollectionController): + name = 'artifacts.diff-conf-templates' + resource = api.DiffConfTemplate + + +class ItemDiffConfTemplate(ItemController): + name = 'artifacts.diff-conf-templates' + resource = api.DiffConfTemplate diff --git a/artifacts-corepy/artifacts_corepy/apps/variable/route.py b/artifacts-corepy/artifacts_corepy/apps/variable/route.py new file mode 100644 index 00000000..9568481e --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/apps/variable/route.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from talos.core import config +from artifacts_corepy.apps.variable import controller + +CONF = config.CONF + + +def add_routes(api): + api.add_route('/artifacts/api/v1/diff-conf-templates', controller.CollectionDiffConfTemplates()) + api.add_route('/artifacts/api/v1/diff-conf-templates/{rid}', controller.ItemDiffConfTemplate()) diff --git a/artifacts-corepy/artifacts_corepy/common/exceptions.py b/artifacts-corepy/artifacts_corepy/common/exceptions.py index 26f4d8ab..1ff6eff3 100644 --- a/artifacts-corepy/artifacts_corepy/common/exceptions.py +++ b/artifacts-corepy/artifacts_corepy/common/exceptions.py @@ -11,19 +11,6 @@ from talos.core import exceptions as core_ex -class BatchPartialError(core_ex.Error): - """批量数据操作异常""" - code = 400 - - @property - def title(self): - return _('Batch Operation Partial Error') - - @property - def message_format(self): - return _('Fail to %(action)s [%(num)s] record, detail error in the data block') - - class PluginError(core_ex.Error): """基础异常""" code = 200 @@ -80,4 +67,35 @@ class PluginCallError(PluginError): class NotFoundError(PluginError): """查找系统间数据异常""" code = 200 - error_code = 40004 \ No newline at end of file + error_code = 40004 + + +class BatchPartialError(PluginError): + """批量数据操作异常""" + code = 200 + # 统一走cmdb接口规范,code统一返回200 + error_code = code + + @property + def title(self): + return _('Batch Operation Partial Error') + + @property + def message_format(self): + return _('fail to %(action)s [%(num)s] record, detail error in the data block') + + +class ConflictError(PluginError): + """约束冲突错误异常""" + code = 200 + # error_code = 40009 + # 统一走cmdb接口规范,code统一返回200 + error_code = code + + @property + def title(self): + return _('Conflict') + + @property + def message_format(self): + return _('object[%(oid)s] is used by %(name)s') diff --git a/artifacts-corepy/artifacts_corepy/common/mixin.py b/artifacts-corepy/artifacts_corepy/common/mixin.py new file mode 100644 index 00000000..2dbf9bb3 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/common/mixin.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +import falcon +from talos.common.controller import CollectionController as BaseCollectionController +from talos.common.controller import ItemController as BaseItemController +from talos.common.controller import Controller as BaseController +from talos.core import exceptions as base_ex +from talos.core import utils +from talos.core.i18n import _ + +from artifacts_corepy.common import exceptions + + +class Controller(BaseController): + allow_methods = ( + 'GET', + 'POST', + ) + + def on_post(self, req, resp, **kwargs): + self._validate_method(req) + self._validate_data(req) + data = req.json + resp.json = {'code': 200, 'status': 'OK', 'data': self.create(req, data, **kwargs), 'message': 'success'} + resp.status = falcon.HTTP_200 + + def create(self, req, data, **kwargs): + return self.make_resource(req).create(data, **kwargs) + + def on_get(self, req, resp, **kwargs): + self._validate_method(req) + resp.json = {'code': 200, 'status': 'OK', 'data': self.get(req, **kwargs), 'message': 'success'} + resp.status = falcon.HTTP_200 + + def get(self, req, **kwargs): + return self.make_resource(req).get(**kwargs) + + +class CollectionController(BaseCollectionController): + def on_get(self, req, resp, **kwargs): + self._validate_method(req) + refs = [] + count = 0 + criteria = self._build_criteria(req) + if criteria: + refs = self.list(req, criteria, **kwargs) + count = self.count(req, criteria, results=refs, **kwargs) + resp.json = {'code': 200, 'status': 'OK', 'data': {'count': count, 'data': refs}, 'message': 'success'} + + def on_post(self, req, resp, **kwargs): + self._validate_method(req) + self._validate_data(req) + datas = req.json + if not utils.is_list_type(datas): + raise exceptions.PluginError(_('data must be list type')) + rets = [] + ex_rets = [] + for idx, data in enumerate(datas): + try: + rets.append(self.create(req, data, **kwargs)) + except base_ex.Error as e: + # 添加 errorMessage,使其匹配前端错误解析协议 + ex_rets.append({'index': idx + 1, 'message': str(e), 'errorMessage': str(e)}) + if len(ex_rets): + raise exceptions.BatchPartialError(num=len(ex_rets), action='create', exception_data={'data': ex_rets}) + resp.json = {'code': 200, 'status': 'OK', 'data': rets, 'message': 'success'} + resp.status = falcon.HTTP_200 + + def on_patch(self, req, resp, **kwargs): + self._validate_method(req) + self._validate_data(req) + datas = req.json + if not utils.is_list_type(datas): + raise exceptions.PluginError(_('data must be list type')) + rets = [] + ex_rets = [] + for idx, data in enumerate(datas): + try: + res_instance = self.make_resource(req) + if res_instance.primary_keys not in data: + raise exceptions.FieldRequired(attribute=res_instance.primary_keys) + rid = data.pop(res_instance.primary_keys) + before_update, after_update = self.update(req, data, rid=rid) + if after_update is None: + raise exceptions.NotFoundError(resource='%s[%s]' % (self.resource.__name__, rid)) + rets.append(after_update) + except base_ex.Error as e: + ex_rets.append({'index': idx + 1, 'message': str(e)}) + if len(ex_rets): + raise exceptions.BatchPartialError(num=len(ex_rets), action='update', exception_data={'data': ex_rets}) + resp.json = {'code': 200, 'status': 'OK', 'data': rets, 'message': 'success'} + resp.status = falcon.HTTP_200 + + def update(self, req, data, **kwargs): + rid = kwargs.pop('rid') + return self.make_resource(req).update(rid, data) + + def on_delete(self, req, resp, **kwargs): + self._validate_method(req) + self._validate_data(req) + datas = req.json + if not utils.is_list_type(datas): + raise exceptions.PluginError(_('data must be list type')) + rets = [] + ex_rets = [] + for idx, data in enumerate(datas): + try: + res_instance = self.make_resource(req) + ref_count, ref_details = self.delete(req, rid=data) + rets.append(ref_details[0]) + except base_ex.Error as e: + ex_rets.append({'index': idx + 1, 'message': str(e)}) + if len(ex_rets): + raise exceptions.BatchPartialError(num=len(ex_rets), action='delete', exception_data={'data': ex_rets}) + resp.json = {'code': 200, 'status': 'OK', 'data': rets, 'message': 'success'} + resp.status = falcon.HTTP_200 + + def delete(self, req, **kwargs): + # before, after = self.make_resource(req).update( + # rid=kwargs.get('rid'), resource={"is_deleted": 1}, validate=False + # ) + # return 1 if before else 0, [after] + return self.make_resource(req).delete(**kwargs) + + +class ItemController(BaseItemController): + + def on_get(self, req, resp, **kwargs): + self._validate_method(req) + ref = self.get(req, **kwargs) + if ref is not None: + resp.json = {'code': 200, 'status': 'OK', 'data': ref, 'message': 'success'} + else: + raise exceptions.NotFoundError(resource='%s[%s]' % (self.resource.__name__, kwargs.get('rid', '-'))) + + def on_patch(self, req, resp, **kwargs): + self._validate_method(req) + self._validate_data(req) + data = req.json + if data is not None and not isinstance(data, dict): + raise exceptions.PluginError(_('data must be dict type')) + ref_before, ref_after = self.update(req, data, **kwargs) + if ref_after is not None: + resp.json = {'code': 200, 'status': 'OK', 'data': ref_after, 'message': 'success'} + else: + raise exceptions.NotFoundError(resource='%s[%s]' % (self.resource.__name__, kwargs.get('rid', '-'))) + + def on_delete(self, req, resp, **kwargs): + self._validate_method(req) + ref, details = self.delete(req, **kwargs) + if ref: + resp.json = {'code': 200, 'status': 'OK', 'data': {'count': ref, 'data': details}, 'message': 'success'} + else: + raise exceptions.NotFoundError(resource='%s[%s]' % (self.resource.__name__, kwargs.get('rid', '-'))) + + # def delete(self, req, **kwargs): + # before, after = self.make_resource(req).update( + # rid=kwargs.get('rid'), resource={"is_deleted": 1}, validate=False + # ) + # return 1 if before else 0, [after] diff --git a/artifacts-corepy/artifacts_corepy/common/utils.py b/artifacts-corepy/artifacts_corepy/common/utils.py index 3cf3f4ec..d389c6f8 100644 --- a/artifacts-corepy/artifacts_corepy/common/utils.py +++ b/artifacts-corepy/artifacts_corepy/common/utils.py @@ -295,10 +295,11 @@ def get(self, url, param=None): self.check_response(resp_json) return resp_json - def post(self, url, data, param=None): + def post(self, url, data, param=None, check=True): LOG.info('POST %s', url) LOG.debug('Request: query - %s, data - %s', str(param), str(data)) resp_json = RestfulJson.post(url, headers=self.build_headers(), params=param, json=data) LOG.debug('Response: %s', str(resp_json)) - self.check_response(resp_json) + if check: + self.check_response(resp_json) return resp_json diff --git a/artifacts-corepy/artifacts_corepy/common/wecmdbv2.py b/artifacts-corepy/artifacts_corepy/common/wecmdbv2.py index 6ab80ce5..11fe8d41 100644 --- a/artifacts-corepy/artifacts_corepy/common/wecmdbv2.py +++ b/artifacts-corepy/artifacts_corepy/common/wecmdbv2.py @@ -154,6 +154,10 @@ def state_operation(self, operation, citype, data): url = self.server + self.build_state_operation_url(operation, citype) return self.post(url, self.format(data)) + def render_variable_values(self, data): + url = self.server + f'{URL_PREFIX}/ci-data/do/Change/app_instance?onlyQuery=true' + return self.post(url, data) + def enumcodes(self, cat_id): url = self.server + self.build_enumcode_url(cat_id) return self.get(url) diff --git a/artifacts-corepy/artifacts_corepy/common/wecube.py b/artifacts-corepy/artifacts_corepy/common/wecube.py index a2ac39e9..98af476d 100644 --- a/artifacts-corepy/artifacts_corepy/common/wecube.py +++ b/artifacts-corepy/artifacts_corepy/common/wecube.py @@ -62,7 +62,11 @@ def update(self, url_path, data): url = self.server + url_path return self.post(url, data) - def retrieve(self, url_path, data=None): + def get(self, url_path, param=None): + url = self.server + url_path + return super().get(url, param) + + def retrieve(self, url_path, data=None, check=True): # op:eq-等于;neq-不等于;in-范围过滤;like-模糊过滤;gt-大于;lt-小于;is - NULL ; isnot - NULL; # { # "criteria": { @@ -80,4 +84,4 @@ def retrieve(self, url_path, data=None): # }] # } url = self.server + url_path - return self.post(url, data or {}) + return self.post(url, data or {}, check=check) diff --git a/artifacts-corepy/artifacts_corepy/db/__init__.py b/artifacts-corepy/artifacts_corepy/db/__init__.py new file mode 100644 index 00000000..2825e7a3 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/db/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import diff --git a/artifacts-corepy/artifacts_corepy/db/models.py b/artifacts-corepy/artifacts_corepy/db/models.py new file mode 100644 index 00000000..942ae25a --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/db/models.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from talos.db.dictbase import DictBase +from sqlalchemy import ( + Column, DateTime, ForeignKey, String, text, Text, func, create_engine, UniqueConstraint, +) +from sqlalchemy.dialects.mysql import BIGINT, TINYINT, INTEGER +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() +metadata = Base.metadata + + +class DiffConfTemplate(Base, DictBase): + __tablename__ = 'diff_conf_template' + + attributes = [ + 'id', 'type', 'code', 'value', 'description', 'create_user', 'create_time', 'update_user', 'update_time', + 'roles', + ] + + id = Column(BIGINT, primary_key=True, index=True) + type = Column(String(16), nullable=True, comment='类型:应用-app,数据库-db') + code = Column(String(36), nullable=False, unique=True, comment='编码') + value = Column(Text, comment='文本值') + description = Column(String(128), server_default=text("''")) + create_user = Column(String(36)) + create_time = Column(DateTime, default=func.now()) + update_user = Column(String(36)) + update_time = Column(DateTime, default=func.now(), onupdate=func.now()) + # is_deleted = Column(TINYINT, nullable=False, default=0, comment='软删除:0,1') + + roles = relationship("DiffConfTemplateRole", back_populates="diff_conf_template") + + @property + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + def __repr__(self): + return "{0}:{1}".format(self.type, self.code) + + +class DiffConfTemplateRole(Base, DictBase): + __tablename__ = 'diff_conf_template_role' + attributes = ['role', 'permission'] + + id = Column(BIGINT, primary_key=True, nullable=False) + permission = Column(String(16), nullable=False, comment='权限:MGMT,USE') + role = Column(String(64), nullable=False, comment='角色') + + diff_conf_template_id = Column(ForeignKey('diff_conf_template.id', ondelete='CASCADE')) + diff_conf_template = relationship('DiffConfTemplate', lazy=True) + + @property + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + def __repr__(self): + return "{0}:{1}".format(self.role, self.diff_conf_template_id) + + +class SysMenu(Base, DictBase): + __tablename__ = 'sys_menu' + attributes = [ + 'id', 'display_name', 'url', 'seq_no', 'parent', 'is_active', 'created_by', 'created_time', 'updated_by', + 'updated_time' + ] + summary_attributes = ['id', 'display_name', 'url', 'seq_no', 'parent', 'is_active'] + + id = Column(String(36), primary_key=True, comment='主键') + display_name = Column(String(64), comment='显示名') + url = Column(String(255), comment='访问路径') + seq_no = Column(INTEGER(11), server_default=text("'0'"), comment='排序号') + parent = Column(String(36), comment='父菜单') + is_active = Column(String(8), server_default=text("'yes'"), comment='状态') + created_by = Column(String(36)) + created_time = Column(DateTime) + updated_by = Column(String(36)) + updated_time = Column(DateTime) + + roles = relationship("SysRole", secondary="sys_role_menu", back_populates="menus", uselist=True, viewonly=True) + + +class SysRole(Base, DictBase): + __tablename__ = 'sys_role' + attributes = [ + 'id', 'description', 'role_type', 'is_system', 'created_by', 'created_time', 'updated_by', 'updated_time', + 'menus' + ] + summary_attributes = ['id', 'description', 'role_type', 'is_system'] + detail_attributes = [ + 'id', 'description', 'role_type', 'is_system', 'created_by', 'created_time', 'updated_by', 'updated_time', + 'users', 'menus' + ] + + id = Column(String(36), primary_key=True, comment='主键') + description = Column(String(255), comment='描述') + role_type = Column(String(32), comment='角色类型') + is_system = Column(String(8), server_default=text("'no'"), comment='是否系统角色') + created_by = Column(String(36)) + created_time = Column(DateTime) + updated_by = Column(String(36)) + updated_time = Column(DateTime) + + users = relationship("SysUser", secondary="sys_role_user", back_populates="roles", uselist=True, viewonly=True) + menus = relationship("SysMenu", secondary="sys_role_menu", back_populates="roles", uselist=True, viewonly=True) + + +class SysUser(Base, DictBase): + __tablename__ = 'sys_user' + attributes = [ + 'id', 'display_name', 'password', 'salt', 'description', 'is_system', 'created_by', 'created_time', + 'updated_by', 'updated_time' + ] + summary_attributes = ['id', 'display_name', 'description', 'is_system'] + detail_attributes = [ + 'id', 'display_name', 'password', 'salt', 'description', 'is_system', 'created_by', 'created_time', + 'updated_by', 'updated_time', 'roles' + ] + + id = Column(String(36), primary_key=True, comment='主键') + display_name = Column(String(64), comment='显示名') + password = Column(String(128), comment='加密密钥') + salt = Column(String(36), comment='加密盐') + description = Column(String(255), comment='描述') + is_system = Column(String(8), server_default=text("'no'"), comment='是否系统用户') + created_by = Column(String(36)) + created_time = Column(DateTime) + updated_by = Column(String(36)) + updated_time = Column(DateTime) + + roles = relationship("SysRole", secondary="sys_role_user", back_populates="users", uselist=True, viewonly=True) + + +class SysRoleMenu(Base, DictBase): + __tablename__ = 'sys_role_menu' + + id = Column(BIGINT(20), primary_key=True) + role_id = Column(ForeignKey('sys_role.id'), index=True, comment='角色id') + menu_id = Column(ForeignKey('sys_menu.id'), index=True, comment='菜单id') + created_by = Column(String(36)) + created_time = Column(DateTime) + updated_by = Column(String(36)) + updated_time = Column(DateTime) + + menu = relationship('SysMenu') + role = relationship('SysRole') + + +class SysRoleUser(Base, DictBase): + __tablename__ = 'sys_role_user' + + id = Column(BIGINT(20), primary_key=True) + role_id = Column(ForeignKey('sys_role.id'), index=True, comment='角色id') + user_id = Column(ForeignKey('sys_user.id'), index=True, comment='用户id') + created_by = Column(String(36)) + created_time = Column(DateTime) + updated_by = Column(String(36)) + updated_time = Column(DateTime) + + role = relationship('SysRole') + user = relationship('SysUser') diff --git a/artifacts-corepy/artifacts_corepy/db/resource.py b/artifacts-corepy/artifacts_corepy/db/resource.py new file mode 100644 index 00000000..6b3af92f --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/db/resource.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from talos.core import utils +from talos.db import crud, validator +from talos.utils import scoped_globals + +from artifacts_corepy.db import validator as my_validator +from artifacts_corepy.db import models +from artifacts_corepy.common import exceptions + + +class MetaCRUD(crud.ResourceBase): + _id_prefix = '' + _remove_fields = [] + + def _before_create(self, resource, validate): + if 'id' not in resource and self._id_prefix: + resource['id'] = utils.generate_prefix_uuid(self._id_prefix) + resource['create_user'] = scoped_globals.GLOBALS.request.auth_user or None + + def _before_update(self, rid, resource, validate): + resource['update_user'] = scoped_globals.GLOBALS.request.auth_user or None + + def _apply_primary_key_filter(self, query, rid): + query = super()._apply_primary_key_filter(query, rid) + + if hasattr(self.orm_meta, 'is_deleted'): + query = query.filter(self.orm_meta.is_deleted == 0) + + return query + + def _addtional_list(self, query, filters): + query = super()._addtional_list(query, filters) + if hasattr(self.orm_meta, 'is_deleted'): + query = query.filter(self.orm_meta.is_deleted == 0) + return query + + +class DiffConfTemplate(MetaCRUD): + orm_meta = models.DiffConfTemplate + _default_order = ['-id', 'update_time', 'create_time'] + + _validate = [ + crud.ColumnValidator(field='type', + # rule=my_validator.validator.InValidator(['app', 'db']), + rule=my_validator.LengthValidator(0, 16), + validate_on=('create:O', 'update:O')), + crud.ColumnValidator(field='code', + rule=my_validator.LengthValidator(1, 36), + validate_on=('create:M', 'update:M'), + # error_msg='%(result)s exist error, please check', + nullable=False), + crud.ColumnValidator(field='value', + rule=my_validator.LengthValidator(1, 40960), + validate_on=('create:M', 'update:M')), + crud.ColumnValidator(field='description', + rule=my_validator.LengthValidator(0, 128), + validate_on=('*:O',), + nullable=True), + crud.ColumnValidator(field='create_user', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='update_user', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='roles', + rule=validator.TypeValidator(dict), + validate_on=('create:M', 'update:O'), + orm_required=False), + ] + + def _before_create(self, resource, validate): + if 'id' not in resource and self._id_prefix: + resource['id'] = utils.generate_prefix_uuid(self._id_prefix) + resource['create_user'] = scoped_globals.GLOBALS.request.auth_user or None + + # 单独校验 code,返回人性化报错信息 + if self.list({'code': resource['code']}): + raise exceptions.ValidationError('code: %s exist error, please check' % resource['code']) + +class DiffConfTemplateRole(crud.ResourceBase): + orm_meta = models.DiffConfTemplateRole + _default_order = ['-id'] + + +class SysRoleMenu(MetaCRUD): + orm_meta = models.SysRoleMenu + _default_order = ['-created_time'] + + +class SysRoleUser(MetaCRUD): + orm_meta = models.SysRoleUser + _default_order = ['-created_time'] + + +class SysMenu(MetaCRUD): + orm_meta = models.SysMenu + _default_order = ['seq_no'] + _id_prefix = 'menu-' + + _validate = [ + crud.ColumnValidator(field='id', validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='display_name', + rule=my_validator.LengthValidator(1, 64), + validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='url', + rule=my_validator.LengthValidator(0, 255), + validate_on=('create:O', 'update:O'), + nullable=True), + crud.ColumnValidator(field='seq_no', + rule=my_validator.validator.NumberValidator(int, range_min=0, range_max=65535), + validate_on=('create:O', 'update:O')), + crud.ColumnValidator(field='parent', + rule=my_validator.BackRefValidator(SysRoleMenu), + validate_on=('create:O', 'update:O'), + nullable=True), + crud.ColumnValidator(field='is_active', + rule=my_validator.validator.InValidator(['yes', 'no']), + validate_on=('create:O', 'update:O')), + crud.ColumnValidator(field='created_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='created_time', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_time', validate_on=('*:O',), nullable=True), + ] + + +class SysRole(MetaCRUD): + orm_meta = models.SysRole + _default_order = ['-created_time'] + _id_prefix = 'role-' + _detail_relationship_as_summary = True + + _validate = [ + crud.ColumnValidator(field='id', validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='description', + rule=my_validator.LengthValidator(1, 255), + validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='role_type', + rule=my_validator.LengthValidator(0, 32), + validate_on=('create:O', 'update:O'), + nullable=True), + crud.ColumnValidator(field='is_system', + rule=my_validator.validator.InValidator(['yes', 'no']), + validate_on=('create:O', 'update:O')), + crud.ColumnValidator(field='created_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='created_time', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_time', validate_on=('*:O',), nullable=True), + ] + + +class SysUser(MetaCRUD): + orm_meta = models.SysUser + _default_order = ['-created_time'] + _remove_fields = ['password', 'salt'] + _id_prefix = 'user-' + + _validate = [ + crud.ColumnValidator(field='id', validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='display_name', + rule=my_validator.LengthValidator(1, 64), + validate_on=('create:M', 'update:O')), + crud.ColumnValidator(field='password', rule=my_validator.LengthValidator(1, 128), validate_on=('create:M',)), + crud.ColumnValidator(field='salt', rule=my_validator.LengthValidator(1, 36), validate_on=('create:M',)), + crud.ColumnValidator(field='description', + rule=my_validator.LengthValidator(0, 255), + validate_on=('create:O', 'update:O'), + nullable=True), + crud.ColumnValidator(field='is_system', + rule=my_validator.validator.InValidator(['yes', 'no']), + validate_on=('create:O', 'update:O')), + crud.ColumnValidator(field='created_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='created_time', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_by', validate_on=('*:O',), nullable=True), + crud.ColumnValidator(field='updated_time', validate_on=('*:O',), nullable=True), + ] + + def list_internal(self, filters=None, orders=None, offset=None, limit=None, hooks=None): + return super(MetaCRUD, self).list(filters=filters, orders=orders, offset=offset, limit=limit, hooks=hooks) diff --git a/artifacts-corepy/artifacts_corepy/db/validator.py b/artifacts-corepy/artifacts_corepy/db/validator.py new file mode 100644 index 00000000..b0c0db29 --- /dev/null +++ b/artifacts-corepy/artifacts_corepy/db/validator.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +import re +import ipaddress +from talos.core import utils +from talos.core.i18n import _ +from talos.db import validator + + +class LengthValidator(validator.NullValidator): + def __init__(self, minimum, maximum): + self._minimum = minimum + self._maximum = maximum + + def validate(self, value): + if not utils.is_string_type(value): + return _('expected string, not %(type)s ') % {'type': type(value).__name__} + if self._minimum <= len(value) and len(value) <= self._maximum: + return True + return _('length required: %(min)d <= %(value)d <= %(max)d') % { + 'min': self._minimum, + 'value': len(value), + 'max': self._maximum + } + + +class BackRefValidator(validator.NullValidator): + def __init__(self, cls_res): + self.cls_res = cls_res + + def validate(self, value): + if self.cls_res().count(filters={'id': value}) == 0: + return _('reference of %(resource)s(%(id)s) not found') % {'resource': self.cls_res.__name__, 'id': value} + return True + + +TypeValidator = validator.TypeValidator + + +class RepeatableValidator(validator.NullValidator): + def validate(self, value): + choices = ['?', '+', '*'] + if utils.is_string_type(value): + if value not in choices: + return _('expected %(choices)s, not %(value)s') % {'choices': choices, 'value': value} + elif isinstance(value, int): + if value < 1: + return _('value should be >= 1, not %(value)s') % {'value': value} + else: + return _('expected string in %(choices)s or int(>=1), not %(type)s ') % { + 'choices': choices, + 'type': type(value).__name__ + } + return True + + +class ConcatCIDRValidator(validator.NullValidator): + def __init__(self, splitter=r',|\||;'): + self._splitter = splitter + + def validate(self, value): + if not utils.is_string_type(value): + return _('expected string, not %(type)s ') % {'type': type(value).__name__} + cidrs = re.split(self._splitter, value) + cidrs = [x for x in cidrs if x] + errors = [] + for cidr in cidrs: + try: + ipaddress.IPv4Network(cidr) + except Exception: + errors.append(_('invalid cidr: %(cidr)s') % {'cidr': cidr}) + if errors: + return ','.join(errors) + return True diff --git a/artifacts-corepy/artifacts_corepy/server/base.py b/artifacts-corepy/artifacts_corepy/server/base.py index 4623d0e5..846fa04e 100644 --- a/artifacts-corepy/artifacts_corepy/server/base.py +++ b/artifacts-corepy/artifacts_corepy/server/base.py @@ -14,6 +14,7 @@ from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5 from Crypto.PublicKey import RSA from talos.core import config +from urllib.parse import quote_plus from artifacts_corepy.common import utils as plugin_utils @@ -28,7 +29,8 @@ def decrypt_rsa(secret_key, encrypt_text): return text.decode('utf-8') -@config.intercept('upload_enabled', 'upload_nexus_enabled', 'ci_typeid_system_design', 'ci_typeid_unit_design', +@config.intercept('db_username', 'db_hostip', 'db_hostport', 'db_schema', + 'upload_enabled', 'upload_nexus_enabled', 'ci_typeid_system_design', 'ci_typeid_unit_design', 'ci_typeid_diff_config', 'ci_typeid_deploy_package', 'encrypt_variable_prefix', 'file_variable_prefix', 'default_special_replace', 'artifact_field', 's3_access_key', 's3_secret_key', 'nexus_server', 'nexus_repository', 'nexus_username', 'nexus_password', 'local_nexus_server', @@ -36,10 +38,10 @@ def decrypt_rsa(secret_key, encrypt_text): 'diff_conf_extension', 'variable_expression', 'jwt_signing_key', 'use_remote_nexus_only', 'nexus_sort_as_string', 'local_nexus_connector_port', 'nexus_connector_port', 'platform_timezone', 'system_design_view', 'sub_system_code', 'sub_system_key', 'cleanup_corn', 'cleanup_keep_topn', - 'cleanup_keep_unit_field', 'delete_op', 'log_level','ci_typeid_app_root_ci', 'ci_typeid_db_root_ci', - 'ci_typeid_app_template_ci', 'ci_typeid_db_template_ci', 'push_nexus_server', - 'push_nexus_repository', 'push_nexus_username', 'push_nexus_password', 's3_server_url', - 'db_script_extension', 'global_variable_prefix', 'cache_cleanup_interval_min') + 'cleanup_keep_unit_field', 'delete_op', 'log_level', 'ci_typeid_app_root_ci', 'ci_typeid_db_root_ci', + 'ci_typeid_app_template_ci', 'ci_typeid_db_template_ci', 'push_nexus_server', 'push_nexus_repository', + 'push_nexus_username', 'push_nexus_password', 'app_filter_expression', 'db_filter_expression', + 's3_server_url', 'db_script_extension', 'global_variable_prefix', 'cache_cleanup_interval_min') def get_env_value(value, origin_value): prefix = 'ENV@' encrypt_prefix = 'RSA@' @@ -55,3 +57,23 @@ def get_env_value(value, origin_value): raise ValueError('keys with "RSA@", but rsa_key file not exists') return new_value return value + + +@config.intercept('db_password') +def get_env_value(value, origin_value): + prefix = 'ENV@' + encrypt_prefix = 'RSA@' + if value.startswith(prefix): + env_name = value[len(prefix):] + new_value = os.getenv(env_name, default='') + if new_value.startswith(encrypt_prefix): + certs_path = RSA_KEY_PATH + if os.path.exists(certs_path) and os.path.isfile(certs_path): + with open(certs_path) as f: + new_value = decrypt_rsa(f.read(), new_value[len(encrypt_prefix):]) + else: + raise ValueError('keys with "RSA@", but rsa_key file not exists') + new_value = quote_plus(new_value) + return new_value + value = quote_plus(value) + return value diff --git a/artifacts-corepy/artifacts_corepy/server/wsgi_server.py b/artifacts-corepy/artifacts_corepy/server/wsgi_server.py index c754ba17..3091e7ca 100644 --- a/artifacts-corepy/artifacts_corepy/server/wsgi_server.py +++ b/artifacts-corepy/artifacts_corepy/server/wsgi_server.py @@ -26,7 +26,7 @@ def error_serializer(req, resp, exception): if 'error_code' in representation: representation['code'] = representation.pop('error_code') representation['status'] = 'ERROR' - representation['data'] = None + representation['data'] = representation.get('data') or None representation['message'] = representation.pop('description', '') resp.body = json.dumps(representation, cls=utils.ComplexEncoder) resp.content_type = 'application/json' diff --git a/artifacts-corepy/etc/artifacts_corepy.conf b/artifacts-corepy/etc/artifacts_corepy.conf index 8b61d139..6db2c490 100644 --- a/artifacts-corepy/etc/artifacts_corepy.conf +++ b/artifacts-corepy/etc/artifacts_corepy.conf @@ -10,6 +10,11 @@ "port": 9000 }, "variables": { + "db_username": "ENV@ARTIFACTS_DB_USERNAME", + "db_password": "ENV@ARTIFACTS_DB_PASSWORD", + "db_hostip": "ENV@ARTIFACTS_DB_HOSTIP", + "db_hostport": "ENV@ARTIFACTS_DB_HOSTPORT", + "db_schema": "ENV@ARTIFACTS_DB_SCHEMA", "upload_enabled": "ENV@ARTIFACTS_UPLOAD_ENABLED", "upload_nexus_enabled": "ENV@ARTIFACTS_UPLOAD_NEXUS_ENABLED", "ci_typeid_system_design": "ENV@ARTIFACTS_CITYPE_SYSTEM_DESIGN", @@ -20,6 +25,8 @@ "ci_typeid_db_root_ci": "ENV@ARTIFACTS_CITYPE_DB_ROOT_CI", "ci_typeid_app_template_ci": "ENV@ARTIFACTS_CITYPE_APP_TEMPLATE_CI", "ci_typeid_db_template_ci": "ENV@ARTIFACTS_CITYPE_DB_TEMPLATE_CI", + "app_filter_expression": "ENV@ARTIFACTS_APP_FILTER_EXPRESSION", + "db_filter_expression": "ENV@ARTIFACTS_DB_FILTER_EXPRESSION", "encrypt_variable_prefix": "ENV@ARTIFACTS_ENCRYPT_VARIABLE_PREFIX", "file_variable_prefix": "ENV@ARTIFACTS_FILE_VARIABLE_PREFIX", "global_variable_prefix": "ENV@ARTIFACTS_GLOBAL_VARIABLE_PREFIX", @@ -75,14 +82,17 @@ ] }, "db": { - "connection": "", + "connection": "mysql+pymysql://${db_username}:${db_password}@${db_hostip}:${db_hostport}/${db_schema}", "pool_size": 3, "pool_recycle": 3600, "pool_timeout": 5, - "max_overflow": 5 + "max_overflow": 60 }, "application": { - "names": ["artifacts_corepy.apps.package", "artifacts_corepy.apps.plugin"] + "names": [ + "artifacts_corepy.apps.package", "artifacts_corepy.apps.plugin", + "artifacts_corepy.apps.variable", "artifacts_corepy.apps.auth" + ] }, "rate_limit": { "enabled": false, @@ -140,6 +150,10 @@ "delete_op": "${delete_op}", "artifact_field": "${artifact_field}", "system_design_view": "${system_design_view}", + "expressions": { + "app_filter": "${app_filter_expression}", + "db_filter": "${db_filter_expression}" + }, "citypes": { "system_design": "${ci_typeid_system_design}", "unit_design": "${ci_typeid_unit_design}", @@ -162,6 +176,8 @@ "artifacts.special-connector": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.ci-types": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.enum-codes": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], + "artifacts.packages.variable-values": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], + "artifacts.unit-design.app-instances": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.unit-design.packages": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.packages.statistics": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.unit-design.nexus.packages": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], @@ -184,8 +200,13 @@ "artifacts.systemconfig": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.unit-design.nexus.path": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.process.defs": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], + "artifacts.diff-conf-templates": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"], "artifacts.users.list": ["SUB_SYSTEM", "IMPLEMENTATION_ARTIFACT_MANAGEMENT"] }, + "permission_passthrough": ["auth.token", "auth.user-menus", "auth.user-password"], + "menu_permissions": { + "system_authorization": ["auth.users", "auth.users.password", "auth.roles", "auth.menus"] + }, "plugin_permissions": [ "artifacts.plugins.fromimage" ] diff --git a/artifacts-corepy/requirements.txt b/artifacts-corepy/requirements.txt index d6df43f0..7ae0e21f 100644 --- a/artifacts-corepy/requirements.txt +++ b/artifacts-corepy/requirements.txt @@ -14,3 +14,4 @@ apscheduler==3.10.4 pytz==2023.3.post1 # for platform login encryption, apt install swig M2Crypto==0.40.1 +pymysql \ No newline at end of file diff --git a/artifacts-ui/package-lock.json b/artifacts-ui/package-lock.json index 230405cd..0f07e6de 100644 --- a/artifacts-ui/package-lock.json +++ b/artifacts-ui/package-lock.json @@ -27,6 +27,7 @@ "@vue/cli-service": "^3.12.0", "@vue/eslint-config-standard": "^4.0.0", "babel-eslint": "^10.0.1", + "dotenv": "^16.4.7", "eslint": "^5.16.0", "eslint-config-standard": "^10.2.1", "eslint-friendly-formatter": "^3.0.0", @@ -1670,6 +1671,15 @@ "ms": "^2.1.1" } }, + "node_modules/@vue/cli-service/node_modules/dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@vue/cli-service/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2038,13 +2048,13 @@ "dev": true }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -2824,24 +2834,27 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", + "bytes": "3.1.2", + "content-type": "~1.0.5", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -2853,13 +2866,28 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body-parser/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/bonjour": { @@ -3108,9 +3136,9 @@ "dev": true }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "engines": { "node": ">= 0.8" @@ -3192,6 +3220,35 @@ "node": ">= 4" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -3991,21 +4048,41 @@ } }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "engines": { "node": ">= 0.6" @@ -4021,9 +4098,9 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -5031,10 +5108,14 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-node": { "version": "2.0.4", @@ -5189,12 +5270,15 @@ } }, "node_modules/dotenv": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", - "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "version": "16.4.7", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -5203,6 +5287,20 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -5243,7 +5341,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, "node_modules/ejs": { @@ -5316,9 +5414,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" @@ -5421,6 +5519,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -6206,7 +6334,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "engines": { "node": ">= 0.6" @@ -6328,44 +6456,49 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", + "depd": "2.0.0", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.3.1", "fresh": "0.5.2", - "merge-descriptors": "1.0.1", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -6377,13 +6510,57 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" } }, "node_modules/extend": { @@ -6704,17 +6881,17 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -6730,6 +6907,15 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-babel-config": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", @@ -6875,9 +7061,9 @@ } }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "engines": { "node": ">= 0.6" @@ -6898,7 +7084,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "engines": { "node": ">= 0.6" @@ -6963,10 +7149,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -7006,12 +7195,49 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -7127,6 +7353,18 @@ "node": ">= 4" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -7218,9 +7456,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -7332,6 +7570,18 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -7567,26 +7817,38 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } }, "node_modules/http-proxy": { "version": "1.18.1", @@ -9358,6 +9620,15 @@ "node": ">=4" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -9378,7 +9649,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "engines": { "node": ">= 0.6" @@ -9395,10 +9666,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-source-map": { "version": "1.1.0", @@ -9498,21 +9772,21 @@ } }, "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "dependencies": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -9754,9 +10028,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "engines": { "node": ">= 0.6" @@ -10022,10 +10296,13 @@ } }, "node_modules/object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10135,9 +10412,9 @@ "dev": true }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { "ee-first": "1.1.1" @@ -10489,9 +10766,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "node_modules/path-type": { @@ -11550,12 +11827,12 @@ "dev": true }, "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "dependencies": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { @@ -11727,13 +12004,13 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -12460,24 +12737,24 @@ "dev": true }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -12495,9 +12772,27 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12511,11 +12806,20 @@ } }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/serialize-javascript": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", @@ -12580,15 +12884,15 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -12634,9 +12938,9 @@ "dev": true }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "node_modules/sha.js": { @@ -12691,6 +12995,78 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -13887,9 +14263,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "engines": { "node": ">=0.6" @@ -14142,7 +14518,7 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "engines": { "node": ">= 0.8" @@ -17150,6 +17526,12 @@ "ms": "^2.1.1" } }, + "dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -17489,13 +17871,13 @@ "dev": true }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -18101,21 +18483,23 @@ "dev": true }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", + "bytes": "3.1.2", + "content-type": "~1.0.5", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -18127,11 +18511,20 @@ "ms": "2.0.0" } }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "requires": { + "side-channel": "^1.0.6" + } } } }, @@ -18356,9 +18749,9 @@ "dev": true }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, "cacache": { @@ -18427,6 +18820,26 @@ } } }, + "call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + } + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -19082,18 +19495,26 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -19106,9 +19527,9 @@ } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true }, "cookie-signature": { @@ -19943,9 +20364,9 @@ } }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detect-node": { @@ -20089,9 +20510,9 @@ } }, "dotenv": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", - "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "version": "16.4.7", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true }, "dotenv-expand": { @@ -20100,6 +20521,17 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -20137,7 +20569,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, "ejs": { @@ -20202,9 +20634,9 @@ "dev": true }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true }, "end-of-stream": { @@ -20291,6 +20723,27 @@ "string.prototype.trimstart": "^1.0.1" } }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -20909,7 +21362,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, "event-pubsub": { @@ -21009,38 +21462,39 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", + "depd": "2.0.0", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.3.1", "fresh": "0.5.2", - "merge-descriptors": "1.0.1", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -21055,10 +21509,31 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "requires": { + "side-channel": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -21327,17 +21802,17 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -21349,6 +21824,12 @@ "requires": { "ms": "2.0.0" } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true } } }, @@ -21467,9 +21948,9 @@ } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, "fragment-cache": { @@ -21484,7 +21965,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, "from2": { @@ -21534,9 +22015,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, "functional-red-black-tree": { @@ -21568,12 +22049,40 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, "get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -21663,6 +22172,12 @@ } } }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -21734,9 +22249,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "has-value": { @@ -21817,6 +22332,15 @@ "minimalistic-assert": "^1.0.1" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -22020,22 +22544,28 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -23448,6 +23978,12 @@ "escape-string-regexp": "^1.0.4" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -23468,7 +24004,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "memory-fs": { @@ -23482,9 +24018,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true }, "merge-source-map": { @@ -23568,18 +24104,18 @@ "dev": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -23785,9 +24321,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, "neo-async": { @@ -24003,9 +24539,9 @@ "dev": true }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true }, "object-is": { @@ -24083,9 +24619,9 @@ "dev": true }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "requires": { "ee-first": "1.1.1" @@ -24370,9 +24906,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "path-type": { @@ -25273,12 +25809,12 @@ "dev": true }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -25427,13 +25963,13 @@ "dev": true }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -26017,24 +26553,24 @@ "dev": true }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -26049,11 +26585,23 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -26061,9 +26609,15 @@ "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -26128,15 +26682,15 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.19.0" } }, "set-blocking": { @@ -26175,9 +26729,9 @@ "dev": true }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "sha.js": { @@ -26220,6 +26774,54 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -27223,9 +27825,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, "toposort": { @@ -27434,7 +28036,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, "unquote": { diff --git a/artifacts-ui/package.json b/artifacts-ui/package.json index 75667cf2..59211816 100644 --- a/artifacts-ui/package.json +++ b/artifacts-ui/package.json @@ -56,7 +56,8 @@ "sass": "^1.77.5", "sass-loader": "^7.1.0", "sortablejs": "^1.10.2", - "vue-template-compiler": "^2.6.10" + "vue-template-compiler": "^2.6.10", + "dotenv": "^16.4.7" }, "license": "MIT" } diff --git a/artifacts-ui/src/api/base.js b/artifacts-ui/src/api/base.js index cf19b9d9..6e1a436e 100644 --- a/artifacts-ui/src/api/base.js +++ b/artifacts-ui/src/api/base.js @@ -71,7 +71,6 @@ req.interceptors.response.use( res => { if (res.status === 200) { if (res.data.status.startsWith('ERR')) { - this.$Spin.hide() const errorMes = Array.isArray(res.data.data) ? res.data.data.map(_ => _.errorMessage).join('
') : res.data.message Vue.prototype.$Notice.error({ title: 'Error', @@ -83,14 +82,12 @@ req.interceptors.response.use( ...res.data } } else { - this.$Spin.hide() return { data: throwError(res) } } }, error => { - this.$Spin.hide() const { response } = error Vue.prototype.$Notice.error({ title: 'error', diff --git a/artifacts-ui/src/api/server.js b/artifacts-ui/src/api/server.js index 7c289c5c..859bfcf0 100644 --- a/artifacts-ui/src/api/server.js +++ b/artifacts-ui/src/api/server.js @@ -6,7 +6,8 @@ if (window.request) { post: (url, ...params) => pluginErrorMessage(window.request.post(baseURL + url, ...params)), get: (url, ...params) => pluginErrorMessage(window.request.get(baseURL + url, ...params)), delete: (url, ...params) => pluginErrorMessage(window.request.delete(baseURL + url, ...params)), - put: (url, ...params) => pluginErrorMessage(window.request.put(baseURL + url, ...params)) + put: (url, ...params) => pluginErrorMessage(window.request.put(baseURL + url, ...params)), + patch: (url, ...params) => pluginErrorMessage(window.request.patch(baseURL + url, ...params)) } } @@ -67,3 +68,18 @@ export const getFlowLists = guid => req.get(`/process/definitions?rootEntityGuid export const getPkgTypeNum = unitDesignId => req.post(`/unit-designs/${unitDesignId}/packages/statistics`, {}) export const getUserList = guid => req.get(`/users`) +// 获取所有角色 +export const getRoleList = params => req.get('/platform/v1/roles/retrieve', { params }) +// 获取当前用户角色 +export const getCurrentUserRoles = () => req.get('/platform/v1/users/roles') +// 保存模版 +export const saveTemplate = data => req.post(`/api/v1/diff-conf-templates`, data) +export const updateTemplate = (data, id) => req.patch(`/api/v1/diff-conf-templates/${id}`, data) + +// 获取模版列表 +export const getTemplate = queryString => req.get(`/api/v1/diff-conf-templates?${queryString}`) +export const deleteTemplate = id => req.delete(`/api/v1/diff-conf-templates/${id}`) +// 获取差异化变量试算结果 +export const getVariableValue = data => req.post(`/app-instances/variable-values`, data) +// 获取待试算实例 +export const getCalcInstance = data => req.post(`/unit-designs/app-instances`, data) diff --git a/artifacts-ui/src/components/diff-variable-template.vue b/artifacts-ui/src/components/diff-variable-template.vue new file mode 100644 index 00000000..ee85e12a --- /dev/null +++ b/artifacts-ui/src/components/diff-variable-template.vue @@ -0,0 +1,405 @@ + + + diff --git a/artifacts-ui/src/components/pkg-diff-variable.vue b/artifacts-ui/src/components/pkg-diff-variable.vue index 19566969..da280a18 100644 --- a/artifacts-ui/src/components/pkg-diff-variable.vue +++ b/artifacts-ui/src/components/pkg-diff-variable.vue @@ -2,7 +2,7 @@
- +
- +
+ + +
+ +
{{ prefix.label }}({{ getNum(item.configKeyInfos || [], prefix.filterKey) }}) @@ -40,9 +45,14 @@
{{ $t('artifacts_loading') }}
+
+ + +
- - +
{{ prefix.label }}({{ getNum(item.configKeyInfos || [], prefix.filterKey) }}) @@ -60,7 +70,8 @@ - + +

{{ $t('art_copy_exist') }} @@ -68,19 +79,40 @@

- +
- + -
+ + + +

+ {{ $t('art_use_template') }} + + +

+
+ + +
+
+ + +
+ +
+
@@ -99,28 +131,40 @@
- -
- - - - - - -
+ +
+
+ +
+ +
+
+ +
+ * + {{ input.key }} +
+ +
+
+
+
+ + +
+ diff --git a/artifacts-ui/src/components/simple-table.vue b/artifacts-ui/src/components/simple-table.vue index d45260ac..23c75aa9 100644 --- a/artifacts-ui/src/components/simple-table.vue +++ b/artifacts-ui/src/components/simple-table.vue @@ -1,6 +1,6 @@ @@ -217,7 +219,7 @@ export default { { title: this.$t('artifacts_package_name'), key: 'name', - minWidth: 160, + minWidth: 140, render: (h, params) => { return {params.row.name} } @@ -273,14 +275,18 @@ export default { fixed: 'right', width: 230, render: (h, params) => { + // 镜像包不需要查看差异化配置 + const showDiffEdit = params.row.package_type !== this.constPackageOptions.image return (
- - - + {showDiffEdit && ( + + + + )}