From 3625e1d98940dabb49c656f6b67133592c87abae Mon Sep 17 00:00:00 2001 From: chalice-1831 <844589474@qq.com> Date: Mon, 18 Nov 2024 18:28:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96excel=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF,=20=E6=B7=BB=E5=8A=A0=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=BF=BB=E8=AF=91(closed=20#2437)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/node_man/constants.py | 100 +++++++++------- apps/node_man/handlers/excel.py | 179 +++++++---------------------- apps/node_man/serializers/excel.py | 4 - apps/node_man/tools/excel.py | 2 +- apps/node_man/views/excel.py | 33 +----- locale/en/LC_MESSAGES/django.mo | Bin 118269 -> 122590 bytes locale/en/LC_MESSAGES/django.po | 116 +++++++++++++++++++ 7 files changed, 218 insertions(+), 216 deletions(-) diff --git a/apps/node_man/constants.py b/apps/node_man/constants.py index 22c3ab6af..a7ab85267 100644 --- a/apps/node_man/constants.py +++ b/apps/node_man/constants.py @@ -1244,7 +1244,7 @@ def _get_member__alias_map(cls) -> Dict[Enum, str]: cls.LOGIN_PORT: _("登录端口"), cls.LOGIN_ACCOUNT: _("登录账号"), cls.AUTH_TYPE: _("认证方式"), - cls.CREDENTIALS: _("凭证"), + cls.CREDENTIALS: _("密钥/密码"), cls.OUTER_IP: _("外网 IP"), cls.LOGIN_IP: _("登录 IP"), cls.BIZ: _("业务"), @@ -1255,49 +1255,65 @@ def _get_member__alias_map(cls) -> Dict[Enum, str]: cls.DATA_COMPRESSION: _("数据压缩"), } + @classmethod + def get_excel_optional_map(cls) -> Dict[str, str]: + excel_optional_map = ExcelOptionalType._get_member__alias_map() -EXCEL_REQUIRED = "必填" -EXCEL_OPTIONAL = "可选" -EXCEL_BOTH_NOT_EMPTY = "与「{}」不能同时为空" - -EXCEL_TITLE_OPTIONAL = { - ExcelField.INNER_IPV4.value: EXCEL_BOTH_NOT_EMPTY.format("内网 IPv6"), - ExcelField.INNER_IPV6.value: EXCEL_BOTH_NOT_EMPTY.format("内网 IPv4"), - ExcelField.OS_TYPE.value: EXCEL_REQUIRED, - ExcelField.INSTALL_CHANNEL.value: EXCEL_REQUIRED, - ExcelField.LOGIN_PORT.value: EXCEL_REQUIRED, - ExcelField.LOGIN_ACCOUNT.value: EXCEL_REQUIRED, - ExcelField.AUTH_TYPE.value: EXCEL_REQUIRED, - ExcelField.CREDENTIALS.value: EXCEL_REQUIRED, - ExcelField.OUTER_IP.value: EXCEL_OPTIONAL, - ExcelField.LOGIN_IP.value: EXCEL_OPTIONAL, - ExcelField.BIZ.value: EXCEL_OPTIONAL, - ExcelField.CLOUD.value: EXCEL_OPTIONAL, - ExcelField.AP.value: EXCEL_REQUIRED, - ExcelField.TRANSFER_SPEED_LIMIT.value: EXCEL_OPTIONAL, - ExcelField.ADDRESS_TYPE.value: EXCEL_REQUIRED, - ExcelField.DATA_COMPRESSION.value: EXCEL_OPTIONAL, -} + return { + cls.INNER_IPV4: excel_optional_map[ExcelOptionalType.BOTH_NOT_EMPTY].format( + cls._get_member__alias_map()[cls.INNER_IPV6] + ), + cls.INNER_IPV6: excel_optional_map[ExcelOptionalType.BOTH_NOT_EMPTY].format( + cls._get_member__alias_map()[cls.INNER_IPV4] + ), + cls.OS_TYPE: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.INSTALL_CHANNEL: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.LOGIN_PORT: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.LOGIN_ACCOUNT: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.AUTH_TYPE: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.CREDENTIALS: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.OUTER_IP: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.LOGIN_IP: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.BIZ: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.CLOUD: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.AP: excel_optional_map[ExcelOptionalType.REQUIRED], + cls.TRANSFER_SPEED_LIMIT: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.ADDRESS_TYPE: excel_optional_map[ExcelOptionalType.OPTIONAL], + cls.DATA_COMPRESSION: excel_optional_map[ExcelOptionalType.OPTIONAL], + } -EXCEL_TITLE_DESCRIBE = { - ExcelField.INNER_IPV4.value: "目标主机 IPv4 地址。", - ExcelField.INNER_IPV6.value: "目标主机 IPv6 地址。", - ExcelField.OS_TYPE.value: "目标主机操作系统类型。", - ExcelField.INSTALL_CHANNEL.value: "在特殊复杂网络下,目标主机无法与「管控区域」内主机直接连通,可通过指定「安装通道」进行 Agent 安装。默认使用「default」即可。", - ExcelField.LOGIN_PORT.value: "登录到目标主机上的sshd端口。", - ExcelField.LOGIN_ACCOUNT.value: "登录到目标主机上所使用的用户。", - ExcelField.AUTH_TYPE.value: "登录到目标主机上所使用的认证方式。", - ExcelField.CREDENTIALS.value: "登录到目标主机上所使用的凭证,根据认证方式提供密码或私钥,某些「认证方式」的选项可能会忽略这个字段。", - ExcelField.OUTER_IP.value: "会自动注册到 CMDB。", - ExcelField.LOGIN_IP.value: "目标主机的用于登录进行 Agent 安装的 IP 地址,区别于记录在 CMDB 中的 IP;支持 IPv4、IPv6。" - "若未填写,优先使用「内网IPv4」来登录目标机器,若「内网IPv4」未填写,使用「内网IPv6」。", - ExcelField.BIZ.value: "目标主机归属业务。默认使用「蓝鲸」业务", - ExcelField.CLOUD.value: "目标主机所在的管控区域。若是在某个云区域内,选择该云区域的名字。默认使用「直连区域」。", - ExcelField.AP.value: "一般情况下使用「自动选择」即可,若有特殊的接入点无法自动识别到,可以手动选择对应接入点。", - ExcelField.TRANSFER_SPEED_LIMIT.value: "Agent配置中对文件传输速率的硬限制,单位「Mbytes/s」,不填则使用Agent默认值100Mbytes/s。", - ExcelField.ADDRESS_TYPE.value: "记录到 CMDB 中的对应枚举字段。默认为「静态」。", - ExcelField.DATA_COMPRESSION.value: "开启数据压缩后,所有通过数据管道传输的日志采集数据的流量都将进行压缩,可一定程度上降低数据上报所带来的带宽压力。但会带来少量额外的CPU消耗。", -} + @classmethod + def get_excel_describe_map(cls) -> Dict[str, str]: + return { + cls.INNER_IPV4: _("目标主机 IPv4 地址"), + cls.INNER_IPV6: _("目标主机 IPv6 地址"), + cls.OS_TYPE: _("目标主机操作系统类型"), + cls.INSTALL_CHANNEL: _("在特殊复杂网络下,目标主机无法与「管控区域」内主机直接连通,可通过指定「安装通道」进行 Agent 安装。默认使用「default」即可"), + cls.LOGIN_PORT: _("登录到目标主机上的sshd端口"), + cls.LOGIN_ACCOUNT: _("登录到目标主机上所使用的用户"), + cls.AUTH_TYPE: _("登录到目标主机上所使用的认证方式"), + cls.CREDENTIALS: _("登录到目标主机上所使用的凭证,根据认证方式提供密码或私钥"), + cls.OUTER_IP: _("目标主机外网IP,会自动注册到 CMDB"), + cls.LOGIN_IP: _( + "用于登录目标主机执行安装的 IP 地址,区别于记录在 CMDB 中的 IP;支持 IPv4、IPv6。若未填写,优先使用「内网IPv4」来登录目标机器,若「内网IPv4」未填写,使用「内网IPv6」" + ), + cls.BIZ: _("目标主机归属业务。默认使用「蓝鲸」业务"), + cls.CLOUD: _("目标主机所在的管控区域。若是在某个云区域内,选择该云区域的名字。默认使用「直连区域」"), + cls.AP: _("一般情况下使用「自动选择」即可,若有特殊的接入点无法自动识别到,可以手动选择对应接入点"), + cls.TRANSFER_SPEED_LIMIT: _("Agent配置中对文件传输速率的硬限制,单位「Mbytes/s」,不填则使用Agent默认值100Mbytes/s"), + cls.ADDRESS_TYPE: _("记录到 CMDB 中的对应枚举字段。默认为「静态」"), + cls.DATA_COMPRESSION: _("开启数据压缩后,所有通过数据管道传输的日志采集数据的流量都将进行压缩,可一定程度上降低数据上报所带来的带宽压力。但会带来少量额外的CPU消耗"), + } + + +class ExcelOptionalType(EnhanceEnum): + REQUIRED = 0 + OPTIONAL = 1 + BOTH_NOT_EMPTY = 2 + + @classmethod + def _get_member__alias_map(cls) -> Dict[Enum, str]: + return {cls.REQUIRED: _("必填"), cls.OPTIONAL: _("可选"), cls.BOTH_NOT_EMPTY: _("与「{}」不能同时为空")} class ExcelAuthType(EnhanceEnum): diff --git a/apps/node_man/handlers/excel.py b/apps/node_man/handlers/excel.py index 469b8c409..f0b3e8afd 100644 --- a/apps/node_man/handlers/excel.py +++ b/apps/node_man/handlers/excel.py @@ -9,16 +9,13 @@ specific language governing permissions and limitations under the License. """ import logging -import re -from typing import Any, Dict, List +from typing import Any, Dict -from django.core.files.uploadedfile import InMemoryUploadedFile -from openpyxl import Workbook, load_workbook +from openpyxl import Workbook from apps.node_man import constants, models from apps.node_man.handlers.cmdb import CmdbHandler from apps.node_man.tools.excel import ExcelTools -from apps.node_man.tools.host import HostTools logger = logging.getLogger("app") @@ -31,21 +28,15 @@ class ExcelHandler: @classmethod def generate_excel_template(cls): - # 整合数据转为下拉框所需列表, [id]name 格式 - all_install_channel = [ - f"[{item['id']}]{item['name']}" for item in list(models.InstallChannel.objects.all().values()) + # 整合数据转为下拉框所需列表 + all_install_channel = [constants.DEFAULT_INSTALL_CHANNEL_NAME] + list( + models.InstallChannel.install_channel_id_name_map().values() + ) + all_biz = [item["bk_biz_name"] for item in CmdbHandler().biz(param={"action": "agent_operate"})] + all_cloud = [constants.DEFAULT_CLOUD_NAME] + [ + cloud.bk_cloud_name for cloud in models.Cloud.objects.all().only("bk_cloud_name") ] - all_install_channel.insert(0, "[0]default") - all_biz = [ - f"[{item['bk_biz_id']}]{item['bk_biz_name']}" - for item in CmdbHandler().biz(param={"action": "agent_operate"}) - ] - all_cloud = [ - f"[{item['bk_cloud_id']}]{item['bk_cloud_name']}" for item in list(models.Cloud.objects.all().values()) - ] - all_cloud.insert(0, f"[{constants.DEFAULT_CLOUD}]{constants.DEFAULT_CLOUD_NAME}") - all_ap = [f"[{item['id']}]{item['name']}" for item in list(models.AccessPoint.objects.all().values())] - + all_ap = [constants.AUTOMATIC_CHOICE] + [ap.name for ap in models.AccessPoint.objects.all().only("name")] all_os = list(constants.OsType) all_auth_type = [str(type) for type in constants.ExcelAuthType.get_member_value__alias_map().values()] all_addressing = [str(type) for type in constants.CmdbAddressingType.get_member_value__alias_map().values()] @@ -56,136 +47,50 @@ def generate_excel_template(cls): excel_sheet = excel.active excel_sheet.title = MAIN_SHEET_NAME - excel_field: Dict[Any, str] = constants.ExcelField.get_member_value__alias_map() - excel_field_list = list(excel_field.keys()) - for col, key in enumerate(excel_field_list, start=1): + excel_field: Dict[Any, str] = constants.ExcelField._get_member__alias_map() + excel_optional = constants.ExcelOptionalType._get_member__alias_map() + excel_field_optional = constants.ExcelField.get_excel_optional_map() + excel_describe = constants.ExcelField.get_excel_describe_map() + for col, key in enumerate(constants.ExcelField, start=1): title_row_cell = excel_sheet.cell(row=1, column=col, value=str(excel_field[key])) ExcelTools.set_font_style(title_row_cell, font_size=16, color="538DD5", bold=True) - key_row_cell = excel_sheet.cell(row=2, column=col, value=str(key)) - ExcelTools.set_font_style(key_row_cell, font_size=12, color="538DD5", bold=True) - - optional_row_cell = excel_sheet.cell(row=3, column=col, value=constants.EXCEL_TITLE_OPTIONAL[key]) - if constants.EXCEL_TITLE_OPTIONAL[key] == constants.EXCEL_REQUIRED: + optional_row_cell = excel_sheet.cell(row=2, column=col, value=str(excel_field_optional[key])) + if excel_field_optional[key] == excel_optional[constants.ExcelOptionalType.REQUIRED]: ExcelTools.set_font_style(optional_row_cell, font_size=12, color="C0504D") else: ExcelTools.set_font_style(optional_row_cell, font_size=12, color="E26B0A") - describe_row_cell = excel_sheet.cell(row=4, column=col, value=constants.EXCEL_TITLE_DESCRIBE[key]) + describe_row_cell = excel_sheet.cell(row=3, column=col, value=str(excel_describe[key])) ExcelTools.set_font_style(describe_row_cell, font_size=12, color="000000") - if key == constants.ExcelField.OS_TYPE.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_os) - elif key == constants.ExcelField.INSTALL_CHANNEL.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_install_channel) - elif key == constants.ExcelField.AUTH_TYPE.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_auth_type) - elif key == constants.ExcelField.BIZ.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_biz) - elif key == constants.ExcelField.CLOUD.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_cloud) - elif key == constants.ExcelField.AP.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_ap) - elif key == constants.ExcelField.ADDRESS_TYPE.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_addressing) - elif key == constants.ExcelField.DATA_COMPRESSION.value: - ExcelTools.create_dropdown(excel, 5, col, key, MAIN_SHEET_NAME, all_enable_compression) + if key == constants.ExcelField.OS_TYPE: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_os) + elif key == constants.ExcelField.INSTALL_CHANNEL: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_install_channel) + elif key == constants.ExcelField.AUTH_TYPE: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_auth_type) + elif key == constants.ExcelField.BIZ: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_biz) + elif key == constants.ExcelField.CLOUD: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_cloud) + elif key == constants.ExcelField.AP: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_ap) + elif key == constants.ExcelField.ADDRESS_TYPE: + ExcelTools.create_dropdown(excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_addressing) + elif key == constants.ExcelField.DATA_COMPRESSION: + ExcelTools.create_dropdown( + excel, 4, col, str(excel_field[key]), MAIN_SHEET_NAME, all_enable_compression + ) else: pass - ExcelTools.fill_color(excel_sheet, 1, 4, 1, len(excel_field_list), "D9D9D9") - ExcelTools.adjust_row_height(excel_sheet, 1, 3, 20) - ExcelTools.adjust_row_height(excel_sheet, 4, 4, 115) - ExcelTools.adjust_col_width(excel_sheet, 1, len(excel_field_list), 35) + ExcelTools.fill_color(excel_sheet, 1, 3, 1, len(excel_field), "D9D9D9") + # 调整首行高度 25 次行 35 描述行 175 宽度 35 + ExcelTools.adjust_row_height(excel_sheet, 1, 1, 30) + ExcelTools.adjust_row_height(excel_sheet, 2, 2, 35) + ExcelTools.adjust_row_height(excel_sheet, 3, 3, 175) + ExcelTools.adjust_col_width(excel_sheet, 1, len(excel_field), 35) ExcelTools.set_alignment(excel_sheet, "center", "left") return excel - - def analyze_excel(self, file: InMemoryUploadedFile) -> List[Dict]: - - # 解析excel - excel = load_workbook(filename=file) - excel_sheet = excel.active - keys = [cell.value for cell in excel_sheet[2]] - - # 正则匹配处理 [id]name 类型的下拉框内容 - pattern = r"\[(\d+)\]" - - # 获取加密cipher - cipher = HostTools.get_asymmetric_cipher() - - required_list = [ - key for key, value in constants.EXCEL_TITLE_OPTIONAL.items() if value == constants.EXCEL_REQUIRED - ] - - error_message: List[str] = [] - excel_data = [] - for index, row in enumerate(excel_sheet.iter_rows(min_row=5, values_only=True), start=5): - - row_data = {keys[i]: cell for i, cell in enumerate(row)} - - row_err_msg: List[str] = [] - - if ( - row_data[constants.ExcelField.INNER_IPV4.value] is None - and row_data[constants.ExcelField.INNER_IPV6.value] is None - ): - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, "IP")) - - for key in required_list: - if row_data[key] is None: - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, key)) - - if row_data[constants.ExcelField.INSTALL_CHANNEL.value] is not None: - install_channel = re.findall(pattern, row_data[constants.ExcelField.INSTALL_CHANNEL.value]) - if not install_channel: - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, constants.ExcelField.INSTALL_CHANNEL.value)) - row_data[constants.ExcelField.INSTALL_CHANNEL.value] = int(install_channel[0]) - - if row_data[constants.ExcelField.BIZ.value] is not None: - biz = re.findall(pattern, row_data[constants.ExcelField.BIZ.value]) - if not biz: - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, constants.ExcelField.BIZ.value)) - row_data[constants.ExcelField.BIZ.value] = int(biz[0]) - - if row_data[constants.ExcelField.CLOUD.value] is not None: - cloud = re.findall(pattern, row_data[constants.ExcelField.CLOUD.value]) - if not cloud: - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, constants.ExcelField.CLOUD.value)) - row_data[constants.ExcelField.CLOUD.value] = int(cloud[0]) - - if row_data[constants.ExcelField.AP.value] is not None: - ap = re.findall(pattern, row_data[constants.ExcelField.AP.value]) - if not ap: - row_err_msg.append(ANALYZE_ERROR_MSG.format(index, constants.ExcelField.AP.value)) - row_data[constants.ExcelField.AP.value] = int(ap[0]) - - if len(row_err_msg) > 0: - error_message.extend(row_err_msg) - continue - - credentials: str = str(row_data[constants.ExcelField.CREDENTIALS.value]) - if ( - row_data[constants.ExcelField.AUTH_TYPE.value] - == constants.ExcelAuthType.get_member_value__alias_map()[constants.ExcelAuthType.PASSWORD.value] - ): - row_data[constants.ExcelField.AUTH_TYPE.value] = constants.ExcelAuthType.PASSWORD.value - row_data["password"] = HostTools.encrypt_with_friendly_exc_handle(cipher, credentials, ValueError) - else: - row_data[constants.ExcelField.AUTH_TYPE.value] = constants.ExcelAuthType.KEY.value - row_data["key"] = HostTools.encrypt_with_friendly_exc_handle(cipher, credentials, ValueError) - - del row_data[constants.ExcelField.CREDENTIALS.value] - - if ( - row_data[constants.ExcelField.ADDRESS_TYPE.value] - == constants.CmdbAddressingType.get_member_value__alias_map()[constants.CmdbAddressingType.STATIC.value] - ): - row_data[constants.ExcelField.ADDRESS_TYPE.value] = constants.CmdbAddressingType.STATIC.value - else: - row_data[constants.ExcelField.ADDRESS_TYPE.value] = constants.CmdbAddressingType.DYNAMIC.value - - excel_data.append(row_data) - - res = {"host": excel_data, "error_message": error_message} - return res diff --git a/apps/node_man/serializers/excel.py b/apps/node_man/serializers/excel.py index 2040bf0a6..808f67231 100644 --- a/apps/node_man/serializers/excel.py +++ b/apps/node_man/serializers/excel.py @@ -13,7 +13,3 @@ class ExcelDownloadSerializer(serializers.Serializer): pass - - -class ExcelUploadSerializer(serializers.Serializer): - file = serializers.FileField() diff --git a/apps/node_man/tools/excel.py b/apps/node_man/tools/excel.py index f5d107689..9552826bb 100644 --- a/apps/node_man/tools/excel.py +++ b/apps/node_man/tools/excel.py @@ -45,7 +45,7 @@ def create_dropdown( sheet = excel.create_sheet(title=src_sheet) main_sheet = excel[dst_sheet] for i, option in enumerate(options, start=1): - sheet[f"A{i}"] = option + sheet[f"A{i}"] = str(option) dv = DataValidation(type="list", formula1=f"={src_sheet}!$A$1:$A${len(options)}", allow_blank=True) diff --git a/apps/node_man/views/excel.py b/apps/node_man/views/excel.py index 1b9bea091..4d08a7458 100644 --- a/apps/node_man/views/excel.py +++ b/apps/node_man/views/excel.py @@ -14,14 +14,10 @@ from drf_yasg.utils import swagger_auto_schema from rest_framework import status from rest_framework.decorators import action -from rest_framework.response import Response from apps.generic import APIViewSet from apps.node_man.handlers.excel import ExcelHandler -from apps.node_man.serializers.excel import ( - ExcelDownloadSerializer, - ExcelUploadSerializer, -) +from apps.node_man.serializers.excel import ExcelDownloadSerializer EXCEL_VIEW_TAGS = ["excel"] @@ -56,30 +52,3 @@ def download(self, request): response.headers["Content-Type"] = "application/octet-stream" response.headers["Content-Disposition"] = 'attachment;filename="{}"'.format(filename) return response - - @swagger_auto_schema( - operation_summary="上传excel", - request_body=ExcelUploadSerializer(), - tags=EXCEL_VIEW_TAGS, - ) - @action(detail=False, methods=["POST"], serializer_class=ExcelUploadSerializer) - def upload(self, request): - """ - @api {POST} /excel/upload/ 上传excel - @apiName upload_excel - @apiGroup Excel - @apiParam {File} file excel文件 - @apiParamExample {Form} 请求例子: - { - "file": file - } - @apiSuccessExample {Json} 成功返回: - { - } - """ - ser = self.serializer_class(data=request.data) - ser.is_valid(raise_exception=True) - data = ser.validated_data - file = data["file"] - - return Response(ExcelHandler().analyze_excel(file)) diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo index 5f0987a6c29a744ae50afa4cd51eb4f3cc3b1ca4..4a6a5db214c566dcf0cc6c290faaca1f61e82c44 100644 GIT binary patch delta 35057 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 I14d;2Uw|pMe*gdg delta 31019 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 ARR910 diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 892d49019..24cb583cd 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -6666,3 +6666,119 @@ msgstr "Variable variable" #~ msgid "日志详情获取成功" #~ msgstr "Log details fetched successfully" + +#: apps/node_man/constants.py: 1240 +msgid "内网 IPv4" +msgstr "LAN IPv4" + +#: apps/node_man/constants.py: 1241 +msgid "内网 IPv6" +msgstr "LAN IPv6" + +#: apps/node_man/constants.py: 1244 +msgid "登录端口" +msgstr "Login port" + +#: apps/node_man/constants.py: 1245 +msgid "登录账号" +msgstr "Login account" + +#: apps/node_man/constants.py: 1246 +msgid "认证方式" +msgstr "Authentication" + +#: apps/node_man/constants.py: 1247 +msgid "密钥/密码" +msgstr "Key/Password" + +#: apps/node_man/constants.py: 1248 +msgid "外网 IP" +msgstr "WAN IP" + +#: apps/node_man/constants.py: 1249 +msgid "登录 IP" +msgstr "Login IP" + +#: apps/node_man/constants.py: 1252 +msgid "接入点" +msgstr "Access point" + +#: apps/node_man/constants.py: 1255 +msgid "数据压缩" +msgstr "Data Compression" + +#: apps/node_man/constants.py: 1316 +msgid "必填" +msgstr "Required" + +#: apps/node_man/constants.py: 1316 +msgid "可选" +msgstr "Optional" + +#: apps/node_man/constants.py: 1316 +msgid "与「{}」不能同时为空" +msgstr "Cannot be empty at the same time as「{}」" + +#: apps/node_man/constants.py: 1288 +msgid "目标主机 IPv4 地址" +msgstr "Target host IPv4 address" + +#: apps/node_man/constants.py: 1289 +msgid "目标主机 IPv6 地址" +msgstr "Target host IPv6 address" + +#: apps/node_man/constants.py: 1290 +msgid "目标主机操作系统类型" +msgstr "Target host operating system type" + +#: apps/node_man/constants.py: 1291 +msgid "在特殊复杂网络下,目标主机无法与「管控区域」内主机直接连通,可通过指定「安装通道」进行 Agent 安装。默认使用「default」即可" +msgstr "In a special complex network, the target host cannot directly connect with the hosts in the 「BK-Net」, and can install the agent by specifying the 「Install channel」. Just use 「default」 by default" + +#: apps/node_man/constants.py: 1292 +msgid "登录到目标主机上的sshd端口" +msgstr "Log in to the sshd port on the target host" + +#: apps/node_man/constants.py: 1293 +msgid "登录到目标主机上所使用的用户" +msgstr "The user used to log in to the target host" + +#: apps/node_man/constants.py: 1294 +msgid "登录到目标主机上所使用的认证方式" +msgstr "The authentication method used to log in to the target host" + +#: apps/node_man/constants.py: 1295 +msgid "登录到目标主机上所使用的凭证,根据认证方式提供密码或私钥" +msgstr "The credentials used to log in to the target host, provide the password or private key according to the authentication method" + +#: apps/node_man/constants.py: 1296 +msgid "目标主机外网IP,会自动注册到 CMDB" +msgstr "The external IP address of the target host, it will be automatically registered into CMDB" + +#: apps/node_man/constants.py: 1298 +msgid "用于登录目标主机执行安装的 IP 地址,区别于记录在 CMDB 中的 IP;支持 IPv4、IPv6。若未填写,优先使用「内网IPv4」来登录目标机器,若「内网IPv4」未填写,使用「内网IPv6」" +msgstr "The IP address used to log in to the target host for installation is different from the IP recorded in CMDB; supports IPv4 and IPv6. If not filled in, priority is given to using 「LAN IPv4」 to log in to the target machine. If 「LAN IPv4」 is not filled in, use 「LAN IPv6」" + +#: apps/node_man/constants.py: 1300 +msgid "目标主机归属业务。默认使用「蓝鲸」业务" +msgstr "The target host belongs to the business. Default use of 「Blueking」 business" + +#: apps/node_man/constants.py: 1301 +msgid "目标主机所在的管控区域。若是在某个云区域内,选择该云区域的名字。默认使用「直连区域」" +msgstr "The control area where the target host is located. If within a certain cloud region, select the name of that cloud region. Default use of 「Direct Mode」" + +#: apps/node_man/constants.py: 1302 +msgid "一般情况下使用「自动选择」即可,若有特殊的接入点无法自动识别到,可以手动选择对应接入点" +msgstr "In general, 「Choose automatically」 is sufficient. If there are special access points that cannot be automatically recognized, the corresponding access points can be manually selected" + +#: apps/node_man/constants.py: 1303 +msgid "Agent配置中对文件传输速率的硬限制,单位「Mbytes/s」,不填则使用Agent默认值100Mbytes/s" +msgstr "Hard limit on file transfer rate in Agent configuration, in Mbytes/s. If left blank, the default value of 100Mbytes/s for Agent will be used" + +#: apps/node_man/constants.py: 1304 +msgid "记录到 CMDB 中的对应枚举字段。默认为「静态」" +msgstr "Record the corresponding enumeration fields in CMDB. Default to 「static」" + +#: apps/node_man/constants.py: 1305 +msgid "开启数据压缩后,所有通过数据管道传输的日志采集数据的流量都将进行压缩,可一定程度上降低数据上报所带来的带宽压力。但会带来少量额外的CPU消耗" +msgstr "After enabling data compression, all the traffic of log collection data transmitted through the data pipeline will be compressed, which can to some extent reduce the bandwidth pressure caused by data reporting. But it will bring a small amount of additional CPU consumption"