Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 优化excel模板(closed #2437) #2485

Open
wants to merge 2 commits into
base: v2.4.8-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions apps/node_man/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,3 +1209,117 @@ 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: _("数据压缩"),
}

@classmethod
def get_excel_optional_map(cls) -> Dict[str, str]:
excel_optional_map = ExcelOptionalType._get_member__alias_map()

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

@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):
PASSWORD = "PASSWORD"
KEY = "KEY"

@classmethod
def _get_member__alias_map(cls) -> Dict[Enum, str]:
return {cls.PASSWORD: _("密码"), cls.KEY: _("密钥")}
94 changes: 94 additions & 0 deletions apps/node_man/handlers/excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- 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
from typing import Any, Dict

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

logger = logging.getLogger("app")

MAIN_SHEET_NAME = "bk_nodeman_info"

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


class ExcelHandler:
@classmethod
def generate_excel_template(cls):

# 整合数据转为下拉框所需列表
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_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()]
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__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)

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=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:
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, 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
15 changes: 15 additions & 0 deletions apps/node_man/serializers/excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
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
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
85 changes: 85 additions & 0 deletions apps/node_man/tools/excel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- 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.
"""
from typing import List

from openpyxl import Workbook
from openpyxl.cell.cell import Cell
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.styles.fills import FILL_SOLID
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.worksheet.worksheet import Worksheet

DEFAULT_DROP_DOWN_ROW = 1000


class ExcelTools:
@classmethod
def fill_color(
cls,
excel: Worksheet,
start_row: int,
end_row: int,
start_col: int,
end_col: int,
color: str,
fill_type: str = FILL_SOLID,
):
fill = PatternFill(start_color=color, end_color=color, fill_type=fill_type)

for row in range(start_row, end_row + 1):
for col in range(start_col, end_col + 1):
excel.cell(row=row, column=col).fill = fill

@classmethod
def create_dropdown(
cls, excel: Workbook, start_row: int, col: int, src_sheet: str, dst_sheet: str, options: List[str]
):
sheet = excel.create_sheet(title=src_sheet)
main_sheet = excel[dst_sheet]
for i, option in enumerate(options, start=1):
sheet[f"A{i}"] = str(option)

dv = DataValidation(type="list", formula1=f"={src_sheet}!$A$1:$A${len(options)}", allow_blank=True)

# 默认提供1000行数据下拉
main_sheet.add_data_validation(dv)
dv.add(f"{chr(64 + col)}{start_row}:{chr(64 + col)}{DEFAULT_DROP_DOWN_ROW}")

@classmethod
def adjust_row_height(cls, excel: Worksheet, start_row: int, end_row: int, height: float):
for row in range(start_row, end_row + 1):
excel.row_dimensions[row].height = height

@classmethod
def adjust_col_width(cls, excel: Worksheet, start_col: int, end_col: int, width: float):
for col in range(start_col, end_col + 1):
excel.column_dimensions[chr(64 + col)].width = width

@classmethod
def set_alignment(cls, excel: Worksheet, vertical: str, horizontal: str):
alignment = Alignment(wrap_text=True, vertical=vertical, horizontal=horizontal)
for row in excel.iter_rows():
for cell in row:
cell.alignment = alignment

@classmethod
def set_font_style(
cls,
cell: Cell,
font_size: int,
color: str = "000000",
name: str = "SimSun",
bold: bool = False,
italic: bool = False,
strike: bool = False,
):
font = Font(size=font_size, color=color, name=name, bold=bold, italic=italic, strike=strike)
cell.font = font
22 changes: 22 additions & 0 deletions apps/node_man/tools/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,25 @@ def export_all_cloud_area_colon_ip(cloud_id_ip_type: Dict[str, bool], hosts_stat
else:
result = []
return result

@classmethod
def encrypt_with_friendly_exc_handle(
cls, cipher: BaseAsymmetricCipher, unencrypt_message: str, raise_exec: Type[Exception]
) -> str:
"""
加密友好提示处理
:param cipher: 密码器
:param unencrypt_message:
:param raise_exec:
:return:
"""

try:
encrypt_message: str = cipher.encrypt(unencrypt_message)
except ValueError as e:
raise raise_exec(_("密文无法加密,请检查是否按规则使用密钥加密:{err_msg}").format(err_msg=e))
except Exception as e:
raise raise_exec(_("密文加密失败:{err_msg").format(err_msg=e))

# 加密
return encrypt_message
Loading
Loading