Skip to content

Commit

Permalink
feat: 优化excel模板, 添加国际化相关翻译(closed #2437)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalice-1831 committed Nov 15, 2024
1 parent 87a6c17 commit f2f2a69
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 216 deletions.
100 changes: 58 additions & 42 deletions apps/node_man/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: _("业务"),
Expand All @@ -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):
Expand Down
177 changes: 40 additions & 137 deletions apps/node_man/handlers/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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()]
Expand All @@ -56,136 +47,48 @@ 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, key.value, MAIN_SHEET_NAME, all_os)
elif key == constants.ExcelField.INSTALL_CHANNEL:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_install_channel)
elif key == constants.ExcelField.AUTH_TYPE:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_auth_type)
elif key == constants.ExcelField.BIZ:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_biz)
elif key == constants.ExcelField.CLOUD:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_cloud)
elif key == constants.ExcelField.AP:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_ap)
elif key == constants.ExcelField.ADDRESS_TYPE:
ExcelTools.create_dropdown(excel, 4, col, key.value, MAIN_SHEET_NAME, all_addressing)
elif key == constants.ExcelField.DATA_COMPRESSION:
ExcelTools.create_dropdown(excel, 4, col, key.value, 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
4 changes: 0 additions & 4 deletions apps/node_man/serializers/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@

class ExcelDownloadSerializer(serializers.Serializer):
pass


class ExcelUploadSerializer(serializers.Serializer):
file = serializers.FileField()
2 changes: 1 addition & 1 deletion apps/node_man/tools/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading

0 comments on commit f2f2a69

Please sign in to comment.