【开源工具】Python打造智能IP监控系统:邮件告警+可视化界面+配置持久化
🌐【开源工具】Python打造智能IP监控系统:邮件告警+可视化界面+配置持久化
🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦
📌 概述:为什么需要IP监控系统?
在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求。传统的人工巡检方式效率低下,而商业监控工具又往往价格昂贵。本文将带你用Python从零打造一个高可用IP监控系统,具备以下核心功能:
- ✅ 多目标监控:同时监测多个IP+端口组合状态
- ✅ 智能告警:异常状态自动触发邮件通知(支持多收件人)
- ✅ 可视化界面:基于Tkinter的现代化UI,操作直观
- ✅ 配置持久化:所有设置自动保存,重启不丢失
- ✅ 断线重试机制:避免网络抖动导致的误报
🔍 技术栈:Python 3 + Tkinter + smtplib + socket + 多线程
🛠️ 使用步骤说明
1. 环境准备
# 所需库(Python内置,无需额外安装)
import tkinter
import threading
import smtplib
import socket
2. 系统部署
-
下载完整代码(文末提供)
-
配置
config.json
(首次运行会自动生成) -
运行主程序:
python ip_monitor.py
3. 核心功能配置
📝 目标配置
- 通过表格形式管理监控目标
- 复选框控制是否启用监测
- 支持双击编辑现有条目
📧 邮件设置
服务商 | SMTP地址 | 端口 | 授权码获取方式 |
---|---|---|---|
网易163 | smtp.163.com | 465/994 | 邮箱设置→POP3/SMTP服务 |
QQ邮箱 | smtp.qq.com | 465 | 设置→账户→POP3服务 |
Gmail | smtp.gmail.com | 587 | Google账号→应用密码 |
# 配置示例(支持SSL/TLS)
SMTP服务器: smtp.163.com:465
邮箱账户: yourname@163.com
授权码: xxxxxx # 需在邮箱设置中获取
接收邮箱: admin@company.com,backup@company.com
⚠️ 注意事项:
- 必须开启SMTP服务
- 部分邮箱需要使用授权码而非密码
- Gmail需开启"低安全性应用访问"
⚙️ 监控参数
检测模式说明
纯IP检测模式
- 留空端口字段
- 使用ICMP协议Ping检测
- 适用场景:网络设备监控
组合检测模式
# 同时验证IP可达性和端口开放状态
if ping_success and port_open:return ONLINE
参数名 | 默认值 | 说明 |
---|---|---|
监测间隔 | 10秒 | 两次检测的时间间隔 |
超时时间 | 2秒 | 判定离线的超时阈值 |
重试次数 | 3次 | 连续失败次数触发告警 |
重试间隔 | 5秒 | 失败后的快速重试间隔 |
🖥️ 系统效果展示
实时监控面板
邮件告警示例
主题:[告警] IP状态变更: 192.168.1.1:80 - 离线
IP地址: 192.168.1.1
端口: 80
备注: 主数据库服务器
状态变更为: 离线
检测时间: 2023-08-20 14:30:45
日志记录
[2023-08-20 14:30:45] 检测到 192.168.1.1:80 状态变更为离线
[2023-08-20 14:31:00] 已发送告警邮件给3个收件人
[2023-08-20 15:00:00] 已发送每日状态报告
💻 核心源码解析
1. 状态检测引擎
def check_target(self, ip, port=None, timeout=2):"""智能检测IP/端口状态"""try:if port: # 端口检测模式with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.settimeout(timeout)s.connect((ip, port))return IPStatus.ONLINEelse: # 纯Ping模式# 使用ICMP协议实现Pingsock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)sock.settimeout(timeout)sock.connect((ip, 1))return IPStatus.ONLINEexcept socket.timeout:return IPStatus.OFFLINEexcept Exception:return IPStatus.UNKNOWN
2. 多线程监控架构
class MonitorThread(threading.Thread):def __init__(self, app):super().__init__(daemon=True)self.app = appself.running = Truedef run(self):while self.running:# 1. 执行所有目标的检测# 2. 触发状态变更通知# 3. 智能休眠控制CPU占用time.sleep(self.calculate_sleep_time())def stop(self):self.running = False
3. 配置持久化实现
def save_config(self):"""JSON格式保存所有配置"""config = {'targets': [self.tree.item(item, 'values')for item in self.tree.get_children()],'email_settings': {'smtp': self.smtp_entry.get(),'user': self.user_entry.get(),'pass': self.pass_entry.get(),'receivers': self.receiver_entry.get()}}with open('config.json', 'w') as f:json.dump(config, f, indent=2)
🚀 高级功能扩展建议
1. 微信/钉钉机器人告警
# 示例:企业微信机器人API
import requests
def send_wechat_alert(message):webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"requests.post(webhook, json={"text": {"content": message}})
2. 数据库存储历史记录
# 使用SQLite记录状态变化
import sqlite3
conn = sqlite3.connect('monitor.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS status_log(ip TEXT, port INT, status TEXT, check_time TIMESTAMP)''')
3. 可视化图表展示
# 使用Matplotlib绘制可用率曲线
plt.plot(dates, availability_rates)
plt.title('月度服务可用率')
plt.ylabel('百分比(%)')
plt.savefig('report.png')
📝 总结与资源下载
项目亮点
- 工业级可靠性:断线重试+智能休眠机制
- 开箱即用:无需复杂配置,5分钟快速部署
- 高度可扩展:代码结构清晰,便于二次开发
完整源码下载
👉
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import time
import smtplib
import ipaddress
import re
import json
import socket
from email.message import EmailMessage
from enum import Enumclass IPStatus(Enum):ONLINE = 1OFFLINE = 2UNKNOWN = 3class IPMonitorApp(tk.Tk):def __init__(self):super().__init__()self.title("IP状态监测系统")self.geometry("1300x850")try:self.iconbitmap('monitor.ico')except:passself.style = ttk.Style()self.configure_style()self.stop_event = threading.Event()self.monitor_thread = Noneself.ip_status = {}self.next_report_time = self.calculate_next_report_time()self.create_widgets()self.load_config()def configure_style(self):self.style.theme_create('ipmonitor', parent='alt', settings={'TFrame': {'configure': {'background': '#f5f5f5'}},'TLabelFrame': {'configure': {'background': '#f5f5f5','foreground': '#1e3d59','font': ('微软雅黑', 10, 'bold')}},'TLabel': {'configure': {'background': '#f5f5f5','foreground': '#1e3d59','font': ('微软雅黑', 10)}},'TButton': {'configure': {'background': '#1e3d59','foreground': 'white','font': ('微软雅黑', 10),'padding': 5},'map': {'background': [('active', '#3a6ea5')],'foreground': [('disabled', '#888888')]}},'TCheckbutton': {'configure': {'background': '#f5f5f5','font': ('微软雅黑', 10)}}})self.style.theme_use('ipmonitor')def create_widgets(self):# 主容器main_frame = ttk.Frame(self, padding=10)main_frame.grid(row=0, column=0, sticky="nsew")self.grid_columnconfigure(0, weight=1)self.grid_rowconfigure(0, weight=1)# 配置区域config_frame = ttk.Frame(main_frame)config_frame.grid(row=0, column=0, sticky="nsew")# IP配置区域ip_frame = ttk.LabelFrame(config_frame, text="监测目标配置", padding=10)ip_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")ip_frame.columnconfigure(0, weight=1)# IP列表Treeviewself.ip_tree = ttk.Treeview(ip_frame, columns=("check", "ip", "port", "remark"), show="headings",height=8,selectmode="browse")self.ip_tree.grid(row=0, column=0, sticky="nsew")# 配置列self.ip_tree.heading("check", text="检测")self.ip_tree.heading("ip", text="IP地址")self.ip_tree.heading("port", text="端口")self.ip_tree.heading("remark", text="备注")self.ip_tree.column("check", width=50, anchor="center")self.ip_tree.column("ip", width=150, anchor="w")self.ip_tree.column("port", width=80, anchor="center")self.ip_tree.column("remark", width=250, anchor="w")# 添加复选框self.ip_tree.tag_configure("checked", background="#e6f7ff")self.ip_tree.tag_configure("unchecked", background="#f5f5f5")# 编辑区域edit_frame = ttk.Frame(ip_frame)edit_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))ttk.Label(edit_frame, text="IP:").grid(row=0, column=0, sticky="e")self.ip_entry = ttk.Entry(edit_frame, width=18)self.ip_entry.grid(row=0, column=1, padx=2, sticky="w")ttk.Label(edit_frame, text="端口:").grid(row=0, column=2, sticky="e")self.port_entry = ttk.Entry(edit_frame, width=8)self.port_entry.grid(row=0, column=3, padx=2, sticky="w")ttk.Label(edit_frame, text="备注:").grid(row=0, column=4, sticky="e")self.remark_entry = ttk.Entry(edit_frame, width=20)self.remark_entry.grid(row=0, column=5, padx=2, sticky="w")# 操作按钮btn_frame = ttk.Frame(edit_frame)btn_frame.grid(row=0, column=6, padx=5)ttk.Button(btn_frame, text="添加", command=self.add_ip).grid(row=0, column=0, padx=2)ttk.Button(btn_frame, text="更新", command=self.update_ip).grid(row=0, column=1, padx=2)ttk.Button(btn_frame, text="删除", command=self.remove_ip).grid(row=0, column=2, padx=2)# 邮件配置区域mail_frame = ttk.LabelFrame(config_frame, text="邮件通知配置", padding=10)mail_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")# 邮件配置项mail_config = [("SMTP服务器:端口", "smtp_server", "smtp.163.com:465"),("邮箱账户", "email_user", "yourname@163.com"),("邮箱授权码", "email_pass", ""),("接收邮箱", "email_receiver", "多个邮箱用逗号分隔")]for i, (label, attr, ph) in enumerate(mail_config):ttk.Label(mail_frame, text=label).grid(row=i, column=0, sticky="e", pady=3)entry = ttk.Entry(mail_frame, width=25)entry.grid(row=i, column=1, padx=5, pady=3, sticky="ew")setattr(self, attr+"_entry", entry)ttk.Label(mail_frame, text=ph, foreground="#888888").grid(row=i, column=2, sticky="w", padx=5)# 监控参数param_frame = ttk.LabelFrame(config_frame, text="监控参数", padding=10)param_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))params = [("监测间隔(秒):", "interval", "10"),("超时(秒):", "timeout", "2"),("重试次数:", "retry", "3"),("重试间隔(秒):", "retry_interval", "5")]for i, (label, attr, default) in enumerate(params):ttk.Label(param_frame, text=label).grid(row=0, column=i*2, sticky="e")entry = ttk.Entry(param_frame, width=8)entry.insert(0, default)entry.grid(row=0, column=i*2+1, padx=5, sticky="w")setattr(self, attr+"_entry", entry)# 控制按钮ctrl_frame = ttk.Frame(config_frame)ctrl_frame.grid(row=2, column=0, columnspan=2, pady=(10, 0), sticky="e")ttk.Button(ctrl_frame, text="开始监测", command=self.start_monitoring).grid(row=0, column=0, padx=5)ttk.Button(ctrl_frame, text="停止监测", command=self.stop_monitoring, state=tk.DISABLED).grid(row=0, column=1, padx=5)self.start_btn = ctrl_frame.grid_slaves(row=0, column=0)[0]self.stop_btn = ctrl_frame.grid_slaves(row=0, column=1)[0]ttk.Button(ctrl_frame, text="保存配置", command=self.save_config).grid(row=0, column=2, padx=5)ttk.Button(ctrl_frame, text="清空日志", command=self.clear_logs).grid(row=0, column=3, padx=5)# 状态监控区域status_frame = ttk.LabelFrame(main_frame, text="状态监控", padding=10)status_frame.grid(row=1, column=0, sticky="nsew", pady=(5, 0))self.status_tree = ttk.Treeview(status_frame, columns=("ip", "port", "remark", "status", "last_check"), show="headings",height=8)self.status_tree.grid(row=0, column=0, sticky="nsew")# 状态列配置for col, width in [("ip", 150), ("port", 80), ("remark", 200), ("status", 100), ("last_check", 180)]:self.status_tree.column(col, width=width, anchor="center")self.status_tree.heading(col, text=col)# 日志区域log_frame = ttk.LabelFrame(main_frame, text="系统日志", padding=10)log_frame.grid(row=2, column=0, sticky="nsew", pady=(5, 0))self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=('微软雅黑', 9), bg='white', fg='#333333',height=10)self.log_text.pack(fill=tk.BOTH, expand=True)# 配置权重main_frame.columnconfigure(0, weight=1)main_frame.rowconfigure(1, weight=1)main_frame.rowconfigure(2, weight=1)config_frame.columnconfigure(1, weight=1)ip_frame.rowconfigure(0, weight=1)ip_frame.columnconfigure(0, weight=1)status_frame.rowconfigure(0, weight=1)status_frame.columnconfigure(0, weight=1)# 绑定事件self.ip_tree.bind("<Button-1>", self.on_tree_click)self.ip_tree.bind("<Double-1>", self.on_tree_double_click)def on_tree_click(self, event):"""处理树状图点击事件"""region = self.ip_tree.identify("region", event.x, event.y)if region == "cell":column = self.ip_tree.identify_column(event.x)item = self.ip_tree.identify_row(event.y)if column == "#1": # 复选框列current_val = self.ip_tree.item(item, "values")[0]new_val = "✓" if current_val == "" else ""values = list(self.ip_tree.item(item, "values"))values[0] = new_valself.ip_tree.item(item, values=values, tags=("checked" if new_val else "unchecked"))def on_tree_double_click(self, event):"""处理树状图双击事件"""item = self.ip_tree.selection()if item:values = self.ip_tree.item(item, "values")self.ip_entry.delete(0, tk.END)self.ip_entry.insert(0, values[1])self.port_entry.delete(0, tk.END)self.port_entry.insert(0, values[2])self.remark_entry.delete(0, tk.END)self.remark_entry.insert(0, values[3])def add_ip(self):"""添加IP到列表"""ip = self.ip_entry.get().strip()port = self.port_entry.get().strip()remark = self.remark_entry.get().strip()if not ip:messagebox.showwarning("错误", "IP地址不能为空")returnif not self.is_valid_ip(ip):messagebox.showwarning("错误", "无效的IP地址格式")returnif port:try:port = int(port)if not (1 <= port <= 65535):messagebox.showwarning("错误", "端口必须在1-65535之间")returnexcept ValueError:messagebox.showwarning("错误", "端口必须是数字")returnself.ip_tree.insert("", tk.END, values=("✓", ip, port, remark), tags=("checked",))# 清空输入框self.ip_entry.delete(0, tk.END)self.port_entry.delete(0, tk.END)self.remark_entry.delete(0, tk.END)def update_ip(self):"""更新选中的IP"""item = self.ip_tree.selection()if not item:messagebox.showwarning("错误", "请先选择要更新的项")returnip = self.ip_entry.get().strip()port = self.port_entry.get().strip()remark = self.remark_entry.get().strip()if not ip:messagebox.showwarning("错误", "IP地址不能为空")returnif not self.is_valid_ip(ip):messagebox.showwarning("错误", "无效的IP地址格式")returnif port:try:port = int(port)if not (1 <= port <= 65535):messagebox.showwarning("错误", "端口必须在1-65535之间")returnexcept ValueError:messagebox.showwarning("错误", "端口必须是数字")return# 保留原来的复选框状态current_check = self.ip_tree.item(item, "values")[0]self.ip_tree.item(item, values=(current_check, ip, port, remark))def remove_ip(self):"""删除选中的IP"""items = self.ip_tree.selection()if not items:messagebox.showwarning("错误", "请先选择要删除的项")returnfor item in items:self.ip_tree.delete(item)def is_valid_ip(self, ip):"""验证IP地址格式"""try:ipaddress.ip_address(ip)return Trueexcept ValueError:return Falsedef start_monitoring(self):"""开始监控"""if not self.validate_inputs():return# 获取要监控的目标targets = []for item in self.ip_tree.get_children():check, ip, port, remark = self.ip_tree.item(item, "values")if check == "✓": # 只监控选中的项targets.append((ip, int(port) if port else None, remark))if not targets:messagebox.showwarning("错误", "没有选中要监控的目标")return# 初始化状态字典self.ip_status = {}for ip, port, remark in targets:self.ip_status[(ip, port)] = {"status": IPStatus.UNKNOWN,"last_notified": None,"remark": remark,"retries": 0,"next_retry_time": 0,"last_check": "从未检测"}# 更新状态显示self.update_status_display()# 更新按钮状态self.start_btn.config(state=tk.DISABLED)self.stop_btn.config(state=tk.NORMAL)self.stop_event.clear()# 获取监控参数interval = int(self.interval_entry.get())timeout = int(self.timeout_entry.get())max_retries = int(self.retry_entry.get())retry_interval = int(self.retry_interval_entry.get())# 创建监控线程self.monitor_thread = threading.Thread(target=self.monitor_targets,args=(interval, timeout, max_retries, retry_interval),daemon=True)self.monitor_thread.start()self.log_message("监控已启动")def stop_monitoring(self):"""停止监控"""self.stop_event.set()self.start_btn.config(state=tk.NORMAL)self.stop_btn.config(state=tk.DISABLED)self.log_message("监控已停止")def monitor_targets(self, interval, timeout, max_retries, retry_interval):"""监控主循环"""while not self.stop_event.is_set():current_time = time.time()# 每日报告检查if current_time >= self.next_report_time:self.send_daily_report()self.next_report_time = self.calculate_next_report_time()# 检查所有目标for (ip, port), data in list(self.ip_status.items()):if self.stop_event.is_set():breakif current_time < data['next_retry_time']:continue# 执行检测if port: # 如果有端口则检测端口current_status = self.check_port(ip, port, timeout)else: # 否则只ping IPcurrent_status = self.ping_ip(ip, timeout)previous_status = data["status"]data["last_check"] = time.strftime("%Y-%m-%d %H:%M:%S")# 处理状态变化if current_status == IPStatus.OFFLINE:if data['retries'] < max_retries:data['retries'] += 1data['next_retry_time'] = current_time + retry_intervalself.log_message(f"{ip}:{port or '无端口'} 检测失败,正在进行第 {data['retries']} 次重试...")else:if previous_status != current_status:self.send_alert(ip, port, data['remark'], current_status)data["status"] = current_statusdata['retries'] = 0data['next_retry_time'] = current_time + intervalelse:if previous_status != current_status:self.send_alert(ip, port, data['remark'], current_status)data["status"] = current_statusdata['retries'] = 0data['next_retry_time'] = current_time + interval# 更新UIself.after(0, self.update_status_display)# 计算睡眠时间next_checks = [data['next_retry_time'] for data in self.ip_status.values()]next_check = min(next_checks) if next_checks else current_time + intervalsleep_time = max(min(next_check - time.time(), interval), 0.1)time.sleep(sleep_time)def ping_ip(self, ip, timeout):"""Ping IP地址"""try:# 使用socket创建ICMP pingsock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)sock.settimeout(timeout)sock.connect((ip, 1)) # 端口号不重要return IPStatus.ONLINEexcept socket.timeout:return IPStatus.OFFLINEexcept Exception:return IPStatus.UNKNOWNfinally:try:sock.close()except:passdef check_port(self, ip, port, timeout):"""检查端口"""try:with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.settimeout(timeout)s.connect((ip, port))return IPStatus.ONLINEexcept socket.timeout:return IPStatus.OFFLINEexcept ConnectionRefusedError:return IPStatus.OFFLINEexcept Exception as e:self.log_message(f"检测异常: {str(e)}")return IPStatus.UNKNOWNdef update_status_display(self):"""更新状态显示"""# 清空现有显示for item in self.status_tree.get_children():self.status_tree.delete(item)# 添加新状态for (ip, port), data in self.ip_status.items():status = data["status"]status_text = "在线" if status == IPStatus.ONLINE else "离线" if status == IPStatus.OFFLINE else "未知"status_color = "green" if status == IPStatus.ONLINE else "red" if status == IPStatus.OFFLINE else "orange"item = self.status_tree.insert("", tk.END, values=(ip, port if port else "无", data["remark"], status_text, data["last_check"]))self.status_tree.tag_configure(status_color, foreground=status_color)self.status_tree.item(item, tags=(status_color,))def send_alert(self, ip, port, remark, status):"""发送状态变更通知"""status_text = "在线" if status == IPStatus.ONLINE else "离线"subject = f"IP状态变更: {remark or ip}:{port if port else '无端口'} - {status_text}"content = "\n".join([f"IP地址: {ip}",f"端口: {port if port else '无'}",f"备注: {remark or '无备注信息'}",f"状态变更为: {status_text}",f"检测时间: {time.strftime('%Y-%m-%d %H:%M:%S')}"])try:self.send_email(subject, content)self.log_message(f"已发送状态变更通知: {ip}:{port if port else '无'} -> {status_text}")except Exception as e:self.log_message(f"邮件发送失败: {str(e)}")def send_email(self, subject, content):"""发送邮件"""smtp_server = self.smtp_server_entry.get().strip()user = self.email_user_entry.get().strip()password = self.email_pass_entry.get().strip()receiver_str = self.email_receiver_entry.get().strip()if not all([smtp_server, user, password, receiver_str]):raise Exception("邮件配置不完整")receivers = [addr.strip() for addr in receiver_str.split(',') if addr.strip()]msg = EmailMessage()msg['Subject'] = subjectmsg['From'] = usermsg['To'] = receiversmsg.set_content(content)server, port = smtp_server.split(":")port = int(port)try:if port == 465:with smtplib.SMTP_SSL(server, port) as smtp:smtp.login(user, password)smtp.send_message(msg)else:with smtplib.SMTP(server, port) as smtp:smtp.starttls()smtp.login(user, password)smtp.send_message(msg)except Exception as e:raise Exception(f"SMTP错误: {str(e)}")def send_daily_report(self):"""发送每日报告"""report_lines = []for (ip, port), data in self.ip_status.items():status = data["status"]status_text = "在线" if status == IPStatus.ONLINE else "离线" if status == IPStatus.OFFLINE else "未知"report_lines.append(f"IP: {ip}:{port if port else '无'} 备注: {data['remark']} 状态: {status_text}")current_time = time.strftime("%Y-%m-%d %H:%M:%S")content = "每日IP状态报告\n\n" + "\n".join(report_lines) + f"\n\n报告时间: {current_time}"subject = "每日IP状态报告"try:self.send_email(subject, content)self.log_message("已发送每日状态报告")except Exception as e:self.log_message(f"发送每日报告失败: {str(e)}")def calculate_next_report_time(self):"""计算下次报告时间"""now = time.localtime()today_10am = time.mktime((now.tm_year, now.tm_mon, now.tm_mday, 10, 0, 0, 0, 0, -1))current_time = time.time()return today_10am + 86400 if current_time >= today_10am else today_10amdef log_message(self, message):"""记录日志"""timestamp = time.strftime("%Y-%m-%d %H:%M:%S")log_line = f"[{timestamp}] {message}\n"self.log_text.insert(tk.END, log_line)self.log_text.see(tk.END)with open("monitor.log", "a", encoding="utf-8") as f:f.write(log_line)def clear_logs(self):"""清空日志"""self.log_text.delete(1.0, tk.END)def validate_inputs(self):"""验证输入"""# 检查邮件配置smtp_server = self.smtp_server_entry.get().strip()user = self.email_user_entry.get().strip()password = self.email_pass_entry.get().strip()receiver_str = self.email_receiver_entry.get().strip()if not smtp_server:messagebox.showwarning("错误", "SMTP服务器不能为空")return Falseif ":" not in smtp_server:messagebox.showwarning("错误", "SMTP服务器格式应为 host:port")return Falseif not user:messagebox.showwarning("错误", "邮箱账户不能为空")return Falseif not password:messagebox.showwarning("错误", "邮箱授权码不能为空")return Falseif not receiver_str:messagebox.showwarning("错误", "接收邮箱不能为空")return False# 验证监控参数try:interval = int(self.interval_entry.get())timeout = int(self.timeout_entry.get())retries = int(self.retry_entry.get())retry_interval = int(self.retry_interval_entry.get())if interval < 5:messagebox.showwarning("错误", "监测间隔不能小于5秒")return Falseif timeout < 1:messagebox.showwarning("错误", "超时时间不能小于1秒")return Falseif retries < 0:messagebox.showwarning("错误", "重试次数不能为负数")return Falseif retry_interval < 1:messagebox.showwarning("错误", "重试间隔不能小于1秒")return Falseexcept ValueError:messagebox.showwarning("错误", "请输入有效的数字")return Falsereturn Truedef save_config(self):"""保存配置"""config = {"targets": [self.ip_tree.item(item, "values")for item in self.ip_tree.get_children()],"smtp_server": self.smtp_server_entry.get(),"email_user": self.email_user_entry.get(),"email_pass": self.email_pass_entry.get(),"email_receiver": self.email_receiver_entry.get(),"interval": self.interval_entry.get(),"timeout": self.timeout_entry.get(),"retry": self.retry_entry.get(),"retry_interval": self.retry_interval_entry.get()}try:with open("config.json", "w", encoding="utf-8") as f:json.dump(config, f, indent=2)self.log_message("配置已保存")messagebox.showinfo("成功", "配置已保存到config.json")except Exception as e:self.log_message(f"保存配置失败: {str(e)}")messagebox.showerror("错误", f"保存配置失败: {str(e)}")def load_config(self):"""加载配置"""try:with open("config.json", encoding="utf-8") as f:config = json.load(f)# 加载目标for item in self.ip_tree.get_children():self.ip_tree.delete(item)for values in config.get("targets", []):self.ip_tree.insert("", tk.END, values=values, tags=("checked" if values[0] == "✓" else "unchecked"))# 加载邮件配置self.smtp_server_entry.delete(0, tk.END)self.smtp_server_entry.insert(0, config.get("smtp_server", ""))self.email_user_entry.delete(0, tk.END)self.email_user_entry.insert(0, config.get("email_user", ""))self.email_pass_entry.delete(0, tk.END)self.email_pass_entry.insert(0, config.get("email_pass", ""))self.email_receiver_entry.delete(0, tk.END)self.email_receiver_entry.insert(0, config.get("email_receiver", ""))# 加载监控参数self.interval_entry.delete(0, tk.END)self.interval_entry.insert(0, config.get("interval", "10"))self.timeout_entry.delete(0, tk.END)self.timeout_entry.insert(0, config.get("timeout", "2"))self.retry_entry.delete(0, tk.END)self.retry_entry.insert(0, config.get("retry", "3"))self.retry_interval_entry.delete(0, tk.END)self.retry_interval_entry.insert(0, config.get("retry_interval", "5"))self.log_message("配置已加载")except FileNotFoundError:self.log_message("未找到配置文件,使用默认配置")except Exception as e:self.log_message(f"加载配置失败: {str(e)}")if __name__ == "__main__":app = IPMonitorApp()app.mainloop()
🎯 适合人群:网络管理员、运维工程师、Python中级开发者
如果觉得项目有用,欢迎Star🌟和Fork!你的支持是我持续优化的动力~
💬 常见问题解答
Q:如何修改每日报告发送时间?
A:修改calculate_next_report_time()
方法中的小时数(默认10:00)
Q:支持监控IPv6地址吗?
A:当前版本需要稍作修改,建议使用ipaddress
库进行验证
Q:最大支持监控多少个IP?
A:理论上无限制,但建议不超过100个以保证性能
📚 延伸阅读:
- 我的专栏[🐍《Python开源项目实战》]
相关文章:
【开源工具】Python打造智能IP监控系统:邮件告警+可视化界面+配置持久化
🌐【开源工具】Python打造智能IP监控系统:邮件告警可视化界面配置持久化 🌈 个人主页:创客白泽 - CSDN博客 🔥 系列专栏:🐍《Python开源项目实战》 💡 热爱不止于代码,热…...
kotlin 过滤 filter 函数的作用和使用场景
1. filter 函数的作用 filter 是 Kotlin 集合操作中的一个高阶函数,用于根据指定条件从集合中筛选出符合条件的元素。 作用:遍历集合中的每个元素,并通过给定的 lambda 表达式判断是否保留该元素。返回值:一个新的集合ÿ…...
Java泛型(补档)
核心概念 Java 泛型是 Java SE 1.5 引入的一项重要特性,它的核心思想是 参数化类型(Parameterized Types),即通过将数据类型作为参数传递给类、接口或方法,使代码能够灵活地处理多种类型,同时保证类型安全性…...
C语言发展史:从Unix起源到现代标准演进
C语言发展史:从Unix起源到现代标准演进 C语言的诞生与早期发展 C语言的起源可以追溯到上世纪70年代初期,但其真正的萌芽始于1969年的夏天。在计算机发展史上,这是一个具有划时代意义的时刻。 当时,Ken Thompson和Dennis Ritchi…...
nginx 代理时怎么更改 Remote Address 请求头
今天工作中遇到用 localhost 访问网站能访问后台 api,但是用本机IP地址后就拒绝访问,我怀疑是后台获取 Remote Address 然后设置白名单了只能 localhost 访问。 想用 nginx 更改 Remote Address server {listen 8058;server_name localhost;loca…...
解决STM32待机模式无法下载程序问题的深度探讨
在现代嵌入式系统开发中,STM32系列微控制器因其高性能、低功耗和丰富的外设资源而广受欢迎。然而,开发者在使用STM32时可能会遇到一个问题:当微控制器进入待机模式后,无法通过调试接口(如SWD或JTAG)下载程序…...
进程、线程、进程间通信Unix Domain Sockets (UDS)
进程、线程、UDS 进程和线程进程间通信Unix Domain Sockets (UDS)UDS的核心适用场景和用途配置UDS的几种主要方式socketpair() 基本配置流程socketpair() 进阶——传递文件描述符 补充socketpair() 函数struct msghdr 结构体struct iovecstruct cmsghdrstruct iovec 、struct m…...
大数据平台与数据仓库的核心差异是什么?
随着数据量呈指数级增长,企业面临着如何有效管理、存储和分析这些数据的挑战。 大数据平台和 数据仓库作为两种主流的数据管理工具,常常让企业在选型时感到困惑,它们之间的界限似乎越来越模糊,功能也有所重叠。本文旨在厘清这两种…...
Hadoop虚拟机中配置hosts
( 一)修改虚拟机的主机名 默认情况下,本机的名称叫:localhost。 我们进入linux系统之后,显示出来的就是[rootlocalhost ~]# 。为了方便后面我们更加便捷地访问这台主机,而不是通过ip地址,我们要…...
a-upload组件实现文件的上传——.pdf,.ppt,.pptx,.doc,.docx,.xls,.xlsx,.txt
实现下面的上传/下载/删除功能:要求支持:【.pdf,.ppt,.pptx,.doc,.docx,.xls,.xlsx,.txt】 分析上面的效果图,分为【上传】按钮和【文件列表】功能: 解决步骤1:上传按钮 直接上代码: <a-uploadmultip…...
QCefView应用和网页的交互
一、demo的主要项目文件 结合QCefView自带的demo代码 main.cpp #include #include <QCefContext.h> #include “MainWindow.h” int main(int argc, char* argv[]) { QApplication a(argc, argv); // build QCefConfig QCefConfig config; config.setUserAgent(“QCef…...
C++,设计模式,【建造者模式】
文章目录 通俗易懂的建造者模式:手把手教你造电脑一、现实中的建造者困境二、建造者模式核心思想三、代码实战:组装电脑1. 产品类 - 电脑2. 抽象建造者 - 装机师傅3. 具体建造者 - 电竞主机版4. 具体建造者 - 办公主机版5. 指挥官 - 装机总控6. 客户端使…...
Axure疑难杂症:中继器制作下拉菜单(多级中继器高级交互)
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 本文视频课程记录于上述地址第五章中继器专题第11节 课程主题:中继器制作下拉菜单 主要内容:创建条件选区、多级中继器…...
科研 | 光子技术为人工智能注入新动力
译《Nature》25.4.9 发表文章《A photonic processing boost for AI》 ▶ 基于人工智能(artificial intelligence, AI)的系统正被越来越广泛地应用于从基因数据解码到自动驾驶的各类任务。但随着AI模型的规模和应用的扩大,性能天花板与能耗壁…...
SQL语句练习 自学SQL网 多表查询
目录 Day 6 用JOINs进行多表联合查询 Day 7 外连接 OUTER JOINs Day 8 外连接 特殊关键字 NULLs Day 6 用JOINs进行多表联合查询 SELECT * FROM Boxoffice INNER JOIN movies ON movies.idboxoffice.Movie_id;SELECT * FROM Boxoffice INNER JOIN moviesON movies.idboxoffi…...
北京亦庄机器人马拉松:人机共跑背后的技术突破与产业启示
2025年4月19日,北京亦庄举办了一场具有里程碑意义的科技赛事——全球首个人形机器人半程马拉松。这场人类与20支机器人战队共同参与的21.0975公里竞速,不仅创造了人形机器人连续运动的最长纪录,更成为中国智能制造领域的综合性技术验证平台。…...
大连理工大学选修课——机器学习笔记(6):决策树
决策树 决策树概述 决策树——非参数机器学习方法 参数方法: 参数估计是定义在整个空间的模型 所有训练数据参与估算 所有的检验输入都用相同的模型和参数 非参数方法: 非参数估计采用局部模型 输入空间被分裂为一系列可以用距离度量的局部空间…...
现代前端工具链深度解析:从包管理到构建工具的完整指南
前言 在当今快速发展的前端生态中,高效的工具链已经成为开发者的必备利器。一个优秀的前端工具链可以显著提升开发效率、优化项目性能并改善团队协作体验。本文将深入探讨现代前端开发中最核心的两大工具类别:包管理工具(npm/yarn)和构建工具(Webpack/V…...
[C语言]猜数字游戏
文章目录 一、游戏思路揭秘二、随机数生成大法1、初探随机数:rand函数的魔力2、随机数种子:时间的魔法3、抓住时间的精髓:time函数 三、完善程序四、游戏成果1、游戏效果2、源代码 一、游戏思路揭秘 猜数字游戏,这个听起来就让人…...
【Linux】g++安装教程
Linux上安装g教程 实现c语言在Linux上编译运行 1. 更新软件包列表 打开终端,先更新软件包列表以确保获取最新版本信息: sudo apt update2. 安装 build-essential 工具包 build-essential 包含 g、gcc、make 和其他编译所需的工具: sudo…...
MQTT - Android MQTT 编码实战(MQTT 客户端创建、MQTT 客户端事件、MQTT 客户端连接配置、MQTT 客户端主题)
Android MQTT 编码实战 1、Settting 在项目级 build.gradle 目录下导入 MQTT 客户端依赖 implementation org.eclipse.paho:org.eclipse.paho.mqttv5.client:1.2.5 implementation org.eclipse.paho:org.eclipse.paho.android.service:1.1.1AndroidManifest.xml,…...
Redis分布式锁使用以及对接支付宝,paypal,strip跨境支付
本章重点在于如何使用redis的分布式锁来锁定库存。减少超卖,同时也对接了支付宝,paypal,strip跨境支付 第一步先建立一个商品表 CREATE TABLE sys_product (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,code varchar(60) DEFAUL…...
沙箱逃逸(Python沙盒逃逸深度解析)
沙箱逃逸(Python沙盒逃逸深度解析) 一、沙盒逃逸的核心目标 执行系统命令 通过调用os.system、subprocess.Popen等函数执行Shell命令,例如读取文件或反弹Shell。 文件操作 读取敏感文件(如/etc/passwd)、写入后门文件…...
k8s-Pod生命周期
初始化容器 初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,它具有两大特征: 1. 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成 2. 初…...
基于Springboot + vue实现的中医院问诊系统
项目描述 本系统包含管理员、医生、用户三个角色。 管理员角色: 用户管理:管理系统中所有用户的信息,包括添加、删除和修改用户。 配置管理:管理系统配置参数,如上传图片的路径等。 权限管理:分配和管理…...
computed计算值为什么还可以依赖另外一个computed计算值?
在 Vue(或类似的响应式框架)中,computed 计算属性之所以可以依赖另一个 computed 属性,是因为: ✅ 本质上 computed 是响应式依赖的“派生值” 每个 computed 本质上就是一个 基于其他响应式数据计算出来的值。 当你在…...
近期实践总结
一、计算机二级考试到底教会了我们什么? 1、概况 根据本人复习、考试的经验,不难发现里面的试题或多或少有些死板(甚至可以说落后于时代),当今时代已经不是二十年前什么都需要手搓的时代了,引擎、集成类软…...
Arduion 第一天,变量的详细解析
Arduino变量详解与嵌入式开发扩展 一、变量基础篇 1.1 变量声明与初始化 <ARDUINO>int ledPin 13; // 声明并初始化float sensorValue; // 先声明后赋值unsigned long startTime; // 无符号长整型void setup() {sensorValue analogRead(A0) *…...
【每日八股】复习 MySQL Day3:锁
文章目录 昨日内容复习MySQL 使用 B 树作为索引的优势是什么?索引有哪几种?什么是最左匹配原则?索引区分度?联合索引如何排序?使用索引有哪些缺陷?什么时候需要建立索引,什么时候不需要…...
2025年KBS新算法 SCI1区TOP:长颖燕麦优化算法AOO,深度解析+性能实测
目录 1.摘要2.算法原理3.结果展示4.参考文献5.文章&代码获取 1.摘要 本文提出了一种新颖的元启发式算法——长颖燕麦优化算法(AOO),该算法灵感来自动画燕麦在环境中的自然行为。AOO模拟了长颖燕麦的三种独特行为:(i) 通过自然…...
1.4 点云数据获取方式——结构光相机
图1-4-1结构光相机 结构光相机作为获取三维点云数据的关键设备,其工作原理基于主动式测量技术。通过投射已知图案,如条纹、点阵、格雷码等,至物体表面,这些图案会因物体表面的高度变化而发生变形。与此同时,利用相机从特定...
2025.4.29总结
工作:最近手头活变得多起来了,毕竟要测两个版本,有时候觉得很奇怪,活少的时候,又想让别人多分点活,活多的时候,又会有些许不自然。这种反差往往伴随着项目的节奏,伴随着两个极端。所…...
初探RAG
源码 核心工作流程 读取文件的内容将内容保存在向量数据库检索向量数据库用户的问题用户问题 上下文【向量数据】 > LLM 读取文件内容【pdf为例】 from pdfminer.high_level import extract_pages from pdfminer.layout import LTTextContainerclass PDFFileLoader():d…...
AIGC(生成式AI)技术全景图:从文本到图像的革命
AIGC(生成式AI)技术全景图:从文本到图像的革命 前言 生成式人工智能(AIGC)正以惊人的速度重塑数字内容的生产方式。从GPT系列模型的文本生成,到Stable Diffusion的图像创作,再到Sora的视频合成…...
通信协议:数字世界的隐形语言——从基础认知到工程实践-优雅草卓伊凡
通信协议:数字世界的隐形语言——从基础认知到工程实践-优雅草卓伊凡 一、理解通信协议:数字世界的”隐形语法” 1.1 通信协议的不可见性与现实存在 通信协议如同空气中的无线电波,虽然看不见摸不着,却实实在在支撑着现代数字世…...
RPC复习
RPC复习 RPC (远程过程调用) 全面解析一、RPC 定义与核心作用1. 什么是RPC?2. 核心作用 二、主流RPC框架对比三、RPC适用场景四、RPC的缺陷五、RPC vs REST vs GraphQL六、Java实现案例:使用Dubbo框架案例描述1. 环境准备2. 定义服务接口3. 服务提供方实…...
Express 文件上传不迷路:req.files 一次性讲明白
前言 在开发后台接口的江湖中,文件上传堪称“隐藏副本”,难度不大但坑点极多。本来只想优雅接收一张图片,结果 undefined、报错、路径错乱轮番登场,逼得人想重拾卖烤红薯的梦想。别慌,本文将用轻松幽默的方式,深入拆解 req.files.file 的每个属性,从前端表单到后台处理…...
Leetcode 3530. Maximum Profit from Valid Topological Order in DAG
Leetcode 3530. Maximum Profit from Valid Topological Order in DAG 1. 解题思路2. 代码实现 题目链接:3530. Maximum Profit from Valid Topological Order in DAG 1. 解题思路 这一题的整体思路就是一个动态规划的思路,我们只需要在当前可以访问的…...
Mysql中索引的知识
Mysql中的索引的定义和种类 核心概念:索引是什么? 想象一下你有一本很厚的书,你想找到其中关于某个特定主题的内容。你有两种方法: 从头到尾翻阅整本书:这就像数据库中的全表扫描 (Full Table Scan)。如果书很长&…...
VSCode Verilog编辑仿真环境搭建
VSCode Verilog环境搭建 下载Iverilog安装Iverilog验证安装VS Code安装插件 下载Iverilog 官网下载Iverilog 安装Iverilog 一定要勾选这两项 建议勾选这两项 验证安装 运行Windows PowerShell输入命令:iverilog输入命令:Get-Command gtkwave …...
linux修改环境变量
添加环境变量注意事项。 vim ~/.bashrc 添加环境变量时,需要source ~/.bashrc后才能有效。同时只对当前shell窗口有效,当打开另外的shell窗口时,需要重新source才能起效。 1.修改bashrc文件后 2.source后打开另一个shell窗口则无效ÿ…...
为什么要学习《金刚经》
《金刚经》作为佛教般若经典的核心,以"缘起性空"为思想根基,通过佛陀与须菩提的对话,揭示了破除执著、见真实相的智慧。 以下从核心要义、精髓段落和现实应用三个维度进行解读: 一、核心思想精髓 1. "凡所有相&am…...
【阿里云大模型高级工程师ACP习题集】2.7 通过微调增强模型能力 (上篇)(⭐️⭐️⭐️ 重点章节!!!)
习题集: 【单选题】在大模型微调中,与提示工程和RAG相比,微调的独特优势在于( ) A. 无需外部工具即可提升模型表现 B. 能让模型学习特定领域知识,提升底层能力 C. 可以更高效地检索知识 D. 能直接提升模型的知识边界,无需训练 【多选题】以下关于机器学习和传统编程的说…...
Docker 容器双网卡访问物理雷达网络教程
作者: 陈梓洋 环境: ubuntu 22.04lts 时间: 2025年4月29日 Docker 容器双网卡访问物理雷达网络教程 这个教程适用于这样的场景:容器保留原有 ROS 通信网络(如 bridge 网络),同时需要访问一个物…...
C++:Lambda表达式
C:Lambda表达式 C中lambda的基本语法1. 捕获列表(Capture List)2. 示例代码示例 1:简单的lambda示例 2:捕获变量示例 3:按引用捕获示例 4:捕获所有变量示例 5:作为函数参数 3. lambd…...
Vim 中替换字符或文本
在 Vim 中替换字符或文本可以使用 替换命令(substitute),其基本语法为: :[range]s/old/new/[flags]1. 基本替换 命令说明:s/foo/bar/替换当前行的第一个 foo 为 bar:s/foo/bar/g替换当前行的 所有 foo 为 bar:%s/foo/bar/g替换 …...
Thinkphp开发自适应职业学生证书查询系统职业资格等级会员证书管理网站
主要功能介绍 1.PHP MYSQL开发,开源,方便二次开发。 2.后台管理界面清新 3.可批量导入导出数据,格式为:JsoN、CSV、 Excel等。 4.自适应手机端,PC端 5.数据修改,添加,删除非常方便,手机上就可以解决 6.可以增加管理员权限等 7.界面可以个性定制开发...
OpenAI Embedding 和密集检索(如 BERT/DPR)进行语义相似度搜索有什么区别和联系
OpenAI Embedding 和密集检索(如 BERT/DPR)其实是“同一种思想的不同实现”,它们都属于Dense Retrieval(密集向量检索),只不过使用的模型、部署方式和调用方式不同。 🧠 首先搞清楚:…...
C语言复习笔记--数据在内存中的存储
今天我们来复习一下数据在内存中的存储方式.话不多说进入正题. 整数在内存中的存储 整数的2进制表⽰⽅法有三种,即原码、反码和补码.三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,⽽数值位最⾼位…...
笔试专题(十二)
文章目录 主持人调度题解代码 小红的ABC题解代码 不相邻取数题解代码 空调遥控题解代码 主持人调度 题目链接 题解 1. 排序 2. 先按左端点的大小进行排序,保证时间是连续的,如果后一个点的左端点大于等于前一个点的右端点就是和法的,否则…...