Skip to content

Commit

Permalink
feat: 优化excel模板(closed TencentBlueKing#2437)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalice-1831 committed Nov 4, 2024
1 parent 7f47072 commit 396c61c
Show file tree
Hide file tree
Showing 9 changed files with 601 additions and 95 deletions.
98 changes: 98 additions & 0 deletions apps/node_man/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,3 +1208,101 @@ def _get_member__alias_map(cls) -> Dict[Enum, str]:
@classmethod
def cpu_type__os_bit_map(cls):
return {CpuType.x86: cls.BIT32.value, CpuType.x86_64: cls.BIT64.value, CpuType.aarch64: cls.ARM.value}


########################################################################################################
# EXCEL
########################################################################################################


class ExcelField(EnhanceEnum):
INNER_IPV4 = "inner_ip"
INNER_IPV6 = "inner_ipv6"
OS_TYPE = "os_type"
INSTALL_CHANNEL = "install_channel_id"
LOGIN_PORT = "port"
LOGIN_ACCOUNT = "account"
AUTH_TYPE = "auth_type"
CREDENTIALS = "credentials"
OUTER_IP = "outer_ip"
LOGIN_IP = "login_ip"
BIZ = "bk_biz_id"
CLOUD = "bk_cloud_id"
AP = "ap_id"
TRANSFER_SPEED_LIMIT = "bt_speed_limit"
ADDRESS_TYPE = "bk_addressing"
DATA_COMPRESSION = "enable_compression"

@classmethod
def _get_member__alias_map(cls) -> Dict[Enum, str]:
return {
cls.INNER_IPV4: _("内网 IPv4"),
cls.INNER_IPV6: _("内网 IPv6"),
cls.OS_TYPE: _("操作系统"),
cls.INSTALL_CHANNEL: _("安装通道"),
cls.LOGIN_PORT: _("登录端口"),
cls.LOGIN_ACCOUNT: _("登录账号"),
cls.AUTH_TYPE: _("认证方式"),
cls.CREDENTIALS: _("凭证"),
cls.OUTER_IP: _("外网 IP"),
cls.LOGIN_IP: _("登录 IP"),
cls.BIZ: _("业务"),
cls.CLOUD: _("管控区域"),
cls.AP: _("接入点"),
cls.TRANSFER_SPEED_LIMIT: _("传输限速"),
cls.ADDRESS_TYPE: _("寻址方式"),
cls.DATA_COMPRESSION: _("数据压缩"),
}


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,
}

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消耗。",
}


class ExcelAuthType(EnhanceEnum):
PASSWORD = "PASSWORD"
KEY = "KEY"

@classmethod
def _get_member__alias_map(cls) -> Dict[Enum, str]:
return {cls.PASSWORD: _("密码"), cls.KEY: _("密钥")}
191 changes: 191 additions & 0 deletions apps/node_man/handlers/excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import logging
import re
from typing import Any, Dict, List

from django.core.files.uploadedfile import InMemoryUploadedFile
from openpyxl import Workbook, load_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")

MAIN_SHEET_NAME = "bk_nodeman_info"

ANALYZE_ERROR_MSG = "第{}行:{}不能为空或格式错误,请依照模板填写,不要修改模板"


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.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_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()]
all_enable_compression = ["True", "False"]

# 生成excel模板
excel = Workbook()
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):
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:
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])
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)
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.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 row in excel_sheet.iter_rows(min_row=5, values_only=True):

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(row, "IP"))

for key in required_list:
if row_data[key] is None:
row_err_msg.append(ANALYZE_ERROR_MSG.format(row, 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(row, 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(row, 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(row, 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(row, 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
19 changes: 19 additions & 0 deletions apps/node_man/serializers/excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""

from rest_framework import serializers


class ExcelDownloadSerializer(serializers.Serializer):
pass


class ExcelUploadSerializer(serializers.Serializer):
file = serializers.FileField()
1 change: 1 addition & 0 deletions apps/node_man/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
防止handlers互调导致循环依赖
"""

from .excel import ExcelTools # noqa
from .host import HostTools # noqa
from .host_v2 import HostV2Tools # noqa
from .job import JobTools # noqa
Expand Down
Loading

0 comments on commit 396c61c

Please sign in to comment.