readme
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import smtplib
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmailSettings(BaseSettings):
|
||||
"""邮件配置"""
|
||||
SMTP_HOST: str = "localhost"
|
||||
SMTP_PORT: int = 25
|
||||
SMTP_USER: Optional[str] = None
|
||||
SMTP_PASSWORD: Optional[str] = None
|
||||
SMTP_USE_TLS: bool = False
|
||||
SMTP_USE_SSL: bool = False
|
||||
SMTP_FROM: str = "ipam@localhost"
|
||||
|
||||
class Config:
|
||||
env_prefix = "EMAIL_"
|
||||
|
||||
|
||||
class EmailService:
|
||||
"""邮件服务"""
|
||||
|
||||
def __init__(self, settings: EmailSettings):
|
||||
self.settings = settings
|
||||
|
||||
def send_email(self, to: str, subject: str, html_content: str) -> bool:
|
||||
"""
|
||||
发送HTML邮件
|
||||
|
||||
Args:
|
||||
to: 收件人邮箱
|
||||
subject: 邮件主题
|
||||
html_content: HTML内容
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
try:
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['From'] = self.settings.SMTP_FROM
|
||||
msg['To'] = to
|
||||
msg['Subject'] = subject
|
||||
|
||||
html_part = MIMEText(html_content, 'html', 'utf-8')
|
||||
msg.attach(html_part)
|
||||
|
||||
# 连接SMTP服务器
|
||||
if self.settings.SMTP_USE_SSL:
|
||||
server = smtplib.SMTP_SSL(
|
||||
host=self.settings.SMTP_HOST,
|
||||
port=self.settings.SMTP_PORT,
|
||||
timeout=10
|
||||
)
|
||||
else:
|
||||
server = smtplib.SMTP(
|
||||
host=self.settings.SMTP_HOST,
|
||||
port=self.settings.SMTP_PORT,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
# 如果需要TLS加密
|
||||
if self.settings.SMTP_USE_TLS and not self.settings.SMTP_USE_SSL:
|
||||
server.starttls()
|
||||
|
||||
# 如果需要认证
|
||||
if self.settings.SMTP_USER and self.settings.SMTP_PASSWORD:
|
||||
server.login(self.settings.SMTP_USER, self.settings.SMTP_PASSWORD)
|
||||
|
||||
# 发送邮件
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
|
||||
logger.info(f"邮件发送成功: {to} - {subject}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"邮件发送失败: {str(e)}", exc_info=True)
|
||||
return False
|
||||
|
||||
def send_alert_notification(self, to: str, alert_data: dict) -> bool:
|
||||
"""发送告警通知邮件"""
|
||||
severity_colors = {
|
||||
'critical': '#dc2626',
|
||||
'error': '#ef4444',
|
||||
'warning': '#f59e0b',
|
||||
'info': '#3b82f6'
|
||||
}
|
||||
|
||||
severity_texts = {
|
||||
'critical': '严重',
|
||||
'error': '错误',
|
||||
'warning': '警告',
|
||||
'info': '信息'
|
||||
}
|
||||
|
||||
alert_type_texts = {
|
||||
'ip_conflict': 'IP 地址冲突',
|
||||
'unauthorized_access': '未授权设备接入',
|
||||
'subnet_full': '网段容量不足',
|
||||
'device_offline': '设备离线',
|
||||
'new_device_detected': '新设备发现',
|
||||
'mac_changed': 'MAC 地址变更'
|
||||
}
|
||||
|
||||
severity = alert_data.get('severity', 'warning')
|
||||
color = severity_colors.get(severity, '#6b7280')
|
||||
severity_text = severity_texts.get(severity, severity)
|
||||
alert_type_text = alert_type_texts.get(alert_data.get('alert_type'), alert_data.get('alert_type'))
|
||||
|
||||
html_content = f"""
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; }}
|
||||
.header {{ background-color: {color}; color: white; padding: 20px; border-radius: 5px; }}
|
||||
.content {{ padding: 20px; background-color: #f9fafb; border-radius: 5px; margin-top: 10px; }}
|
||||
.alert-item {{ margin-bottom: 15px; }}
|
||||
.label {{ font-weight: bold; color: #6b7280; display: inline-block; width: 100px; }}
|
||||
.footer {{ margin-top: 20px; padding-top: 20px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #9ca3af; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>⚠️ IPAM 告警通知</h2>
|
||||
<p>告警级别: <strong>{severity_text}</strong></p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="alert-item">
|
||||
<span class="label">告警类型:</span> {alert_type_text}
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="label">告警标题:</span> {alert_data.get('title', '-')}
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="label">详细信息:</span> {alert_data.get('message', '-')}
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="label">相关IP:</span> {alert_data.get('ip_address_str', '-')}
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="label">相关MAC:</span> {alert_data.get('mac_address', '-')}
|
||||
</div>
|
||||
<div class="alert-item">
|
||||
<span class="label">告警时间:</span> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由 IPAM 地址管理系统自动发送,请勿直接回复。</p>
|
||||
<p>如需处理该告警,请登录 IPAM 管理控制台: <a href="#">点击访问</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
subject = f"【{severity_text}】IPAM 告警 - {alert_data.get('title', '新告警')}"
|
||||
|
||||
return self.send_email(to, subject, html_content)
|
||||
|
||||
def send_scan_report(self, to: str, report_data: dict) -> bool:
|
||||
"""发送扫描报告邮件"""
|
||||
html_content = f"""
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; }}
|
||||
.header {{ background-color: #3b82f6; color: white; padding: 20px; border-radius: 5px; }}
|
||||
.content {{ padding: 20px; background-color: #f9fafb; border-radius: 5px; margin-top: 10px; }}
|
||||
.stat-item {{ display: inline-block; width: 22%; text-align: center; padding: 15px; background: white; border-radius: 5px; margin: 1%; }}
|
||||
.stat-number {{ font-size: 28px; font-weight: bold; color: #3b82f6; }}
|
||||
.stat-label {{ color: #6b7280; font-size: 14px; }}
|
||||
.success {{ color: #10b981; }}
|
||||
.warning {{ color: #f59e0b; }}
|
||||
.footer {{ margin-top: 20px; padding-top: 20px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #9ca3af; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2>📊 IPAM 网段扫描报告</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h3>扫描结果概览</h3>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{report_data.get('total_ips', 0)}</div>
|
||||
<div class="stat-label">扫描IP总数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number success">{report_data.get('online_ips', 0)}</div>
|
||||
<div class="stat-label">在线设备</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{report_data.get('with_mac', 0)}</div>
|
||||
<div class="stat-label">发现MAC地址</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number warning">{report_data.get('new_devices', 0)}</div>
|
||||
<div class="stat-label">新发现设备</div>
|
||||
</div>
|
||||
|
||||
<div style="clear: both; margin-top: 20px;">
|
||||
<p><strong>扫描网段:</strong> {report_data.get('network_cidr', '-')}</p>
|
||||
<p><strong>扫描时间:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由 IPAM 地址管理系统自动发送,请勿直接回复。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
subject = f"IPAM 扫描报告 - {report_data.get('network_cidr', '')}"
|
||||
|
||||
return self.send_email(to, subject, html_content)
|
||||
|
||||
|
||||
# 全局邮件服务实例
|
||||
_email_service = None
|
||||
|
||||
def get_email_service() -> EmailService:
|
||||
"""获取邮件服务实例"""
|
||||
global _email_service
|
||||
if _email_service is None:
|
||||
settings = EmailSettings()
|
||||
_email_service = EmailService(settings)
|
||||
return _email_service
|
||||
|
||||
|
||||
def init_email_service(settings: EmailSettings):
|
||||
"""初始化邮件服务"""
|
||||
global _email_service
|
||||
_email_service = EmailService(settings)
|
||||
Reference in New Issue
Block a user