界面
1.程序可以手动启动
2.程序可以设置定时任务
3.程序可以常驻托盘
4.报错会自动弹出错误信息
配置文件如下格式
1.支持域名和ipv4,ipv6地址 2.可以配置多个分组
[服务器]
server1 = 192.168.*.**|3389|remote
server2 = 192.168.*.**|22|remote
[程序]
server1 = 192.168.*,**|1521|oracle
server2 = 192.168.*.**|3306|mysql
代码片段
class IPTesterWindow(QMainWindow):
def __init__(self):
super().__init__()
self.test_thread = None
self.scheduler_timer = QTimer(self) # 定时执行器
self.all_items_visible = True # 记录是否所有项目都可见
self.nodes_data = [] # 存储所有节点数据: [(节点名, [(target, port, remark), ...]), ...]
self.node_status = [] # 存储每个项目的状态: [[状态1, 状态2, ...], ...]
self.current_report = "" # 存储当前报告内容
self.item_mapping = {} # 用于快速查找项目: (node_name, target) -> tree_item
self.is_scheduled = False # 是否启用定时执行
self.error_dialog = None # 错误对话框实例
self.init_ui()
self.init_tray()
# 配置定时器
self.scheduler_timer.timeout.connect(self.run_scheduled_test)
self.scheduler_timer.moveToThread(QApplication.instance().thread())
def init_ui(self):
"""初始化主界面"""
self.setWindowTitle("IP/域名端口连通性测试工具-diff") # 更新标题以反映新功能
self.setGeometry(100, 100, 1350, 800)
# 设置中文字体
font = QFont()
font.setFamily("SimHei")
self.setFont(font)
# 中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(5, 5, 5, 5)
main_layout.setSpacing(5)
# 配置区域
config_frame = QFrame()
config_frame.setFrameShape(QFrame.StyledPanel)
config_layout = QVBoxLayout(config_frame)
config_layout.setContentsMargins(5, 5, 5, 5)
config_layout.setSpacing(5)
# 第一行:INI文件选择和基本设置
top_layout = QHBoxLayout()
top_layout.setSpacing(8)
# INI文件选择部分
file_group = QHBoxLayout()
file_group.addWidget(QLabel("INI文件:"))
self.ini_path_edit = QLineEdit()
self.ini_path_edit.setMinimumWidth(200)
self.browse_btn = QPushButton("浏览...")
self.browse_btn.clicked.connect(self.browse_ini_file)
file_group.addWidget(self.ini_path_edit)
file_group.addWidget(self.browse_btn)
# 基本设置部分
settings_group = QHBoxLayout()
self.timeout_spin = QDoubleSpinBox()
self.timeout_spin.setRange(0.5, 10.0)
self.timeout_spin.setSingleStep(0.5)
self.timeout_spin.setValue(3.0)
self.timeout_spin.setMaximumWidth(100)
self.retry_spin = QDoubleSpinBox()
self.retry_spin.setRange(1, 5)
self.retry_spin.setSingleStep(1)
self.retry_spin.setValue(3)
self.retry_spin.setDecimals(0)
self.retry_spin.setMaximumWidth(80)
# 定时设置部分
schedule_group = QHBoxLayout()
self.schedule_check = QCheckBox("启用定时执行")
self.schedule_check.stateChanged.connect(self.toggle_schedule)
self.interval_spin = QDoubleSpinBox()
self.interval_spin.setRange(1, 3600) # 1-3600秒
self.interval_spin.setSingleStep(1)
self.interval_spin.setValue(30) # 默认30秒
self.interval_spin.setDecimals(0) # 整数秒
self.interval_spin.setMaximumWidth(100)
self.interval_spin.setEnabled(False) # 初始禁用
# 定时刷新按钮
self.refresh_schedule_btn = QPushButton("刷新定时")
self.refresh_schedule_btn.clicked.connect(self.refresh_schedule)
self.refresh_schedule_btn.setEnabled(False) # 初始禁用
# 错误提示设置
self.error_dialog_check = QCheckBox("测试失败时显示错误提示框")
self.error_dialog_check.setChecked(True) # 默认启用
schedule_group.addWidget(self.schedule_check)
schedule_group.addWidget(self.interval_spin)
schedule_group.addWidget(QLabel("秒/次"))
schedule_group.addWidget(self.refresh_schedule_btn) # 添加刷新按钮
schedule_group.addSpacing(15)
schedule_group.addWidget(self.error_dialog_check)
settings_group.addWidget(QLabel("超时:"))
settings_group.addWidget(self.timeout_spin)
settings_group.addWidget(QLabel("秒"))
settings_group.addWidget(QLabel("重试:"))
settings_group.addWidget(self.retry_spin)
settings_group.addWidget(QLabel("次"))
settings_group.addSpacing(20)
settings_group.addLayout(schedule_group)
top_layout.addLayout(file_group, 3)
top_layout.addLayout(settings_group, 3)
top_layout.addStretch(1)
# 第二行:筛选区域和按钮区域
bottom_config_layout = QHBoxLayout()
bottom_config_layout.setSpacing(8)
# 筛选区域
filter_group = QHBoxLayout()
filter_group.setSpacing(6)
# 新增:目标类型筛选(IPv4/IPv6/域名)
self.target_type_filter = QComboBox()
self.target_type_filter.addItems(["所有类型", "IPv4", "IPv6", "域名"])
self.target_type_filter.currentIndexChanged.connect(self.apply_filters)
self.target_type_filter.setMinimumWidth(100)
# 状态筛选
self.status_filter = QComboBox()
self.status_filter.addItems(["所有状态", "成功", "失败"])
self.status_filter.currentIndexChanged.connect(self.apply_filters)
self.status_filter.setMinimumWidth(100)
# 备注筛选
self.remark_filter = QLineEdit()
self.remark_filter.setPlaceholderText("备注关键词...")
self.remark_filter.textChanged.connect(self.apply_filters)
self.remark_filter.setMinimumWidth(150)
# 区分大小写
self.case_sensitive = QCheckBox("区分大小写")
self.case_sensitive.stateChanged.connect(self.apply_filters)
filter_group.addWidget(QLabel("类型:"))
filter_group.addWidget(self.target_type_filter)
filter_group.addWidget(QLabel("状态:"))
filter_group.addWidget(self.status_filter)
filter_group.addWidget(QLabel("备注:"))
filter_group.addWidget(self.remark_filter)
filter_group.addWidget(self.case_sensitive)
# 按钮区域
btn_group = QHBoxLayout()
btn_group.setSpacing(5)
self.test_btn = QPushButton("开始测试")
self.test_btn.clicked.connect(self.start_test)
self.stop_btn = QPushButton("停止测试")
self.stop_btn.clicked.connect(self.stop_test)
self.stop_btn.setEnabled(False) # 初始禁用
self.clear_btn = QPushButton("清空结果")
self.clear_btn.clicked.connect(self.clear_results)
self.reset_filter_btn = QPushButton("重置筛选")
self.reset_filter_btn.clicked.connect(self.reset_filters)
self.save_report_btn = QPushButton("保存报告")
self.save_report_btn.clicked.connect(self.save_report)
self.save_report_btn.setEnabled(False) # 初始禁用
# 减小按钮大小
for btn in [self.test_btn, self.stop_btn, self.clear_btn,
self.reset_filter_btn, self.save_report_btn]:
btn.setMinimumHeight(24)
btn.setStyleSheet("padding: 2px 8px;")
btn_group.addWidget(self.test_btn)
btn_group.addWidget(self.stop_btn)
btn_group.addWidget(self.clear_btn)
btn_group.addWidget(self.reset_filter_btn)
btn_group.addWidget(self.save_report_btn)
bottom_config_layout.addLayout(btn_group, 2)
bottom_config_layout.addLayout(filter_group, 3)
# 添加到配置区域
config_layout.addLayout(top_layout)
config_layout.addLayout(bottom_config_layout)
# 主内容区域
main_splitter = QSplitter(Qt.Vertical)
# 上部:测试结果区域
result_splitter = QSplitter(Qt.Horizontal)
# 侧边节点列表
node_group = QGroupBox("节点列表")
node_vbox = QVBoxLayout(node_group)
node_vbox.setContentsMargins(5, 5, 5, 5)
node_vbox.setSpacing(3)
# 节点操作按钮
node_btn_layout = QHBoxLayout()
node_btn_layout.setSpacing(3)
self.check_all_btn = QPushButton("勾选全部")
self.check_all_btn.clicked.connect(self.check_all_nodes)
self.uncheck_all_btn = QPushButton("取消全部")
self.uncheck_all_btn.clicked.connect(self.uncheck_all_nodes)
# 设置小按钮样式
for btn in [self.check_all_btn, self.uncheck_all_btn]:
btn.setMinimumHeight(24)
btn.setStyleSheet("padding: 2px; font-size: 12px;")
btn.setMaximumHeight(26)
node_btn_layout.addWidget(self.check_all_btn)
node_btn_layout.addWidget(self.uncheck_all_btn)
# 节点列表
self.node_list = QListWidget()
self.node_list.setMinimumWidth(140)
self.node_list.setMaximumWidth(200)
self.node_list.setSelectionMode(QListWidget.ExtendedSelection)
self.node_list.itemChanged.connect(self.on_node_selection_changed)
# 添加到侧边栏布局
node_vbox.addLayout(node_btn_layout)
node_vbox.addWidget(self.node_list)
result_splitter.addWidget(node_group)
# 结果表格 - 增加"类型"列以显示IPv4/IPv6/域名
self.result_tree = QTreeWidget()
self.result_tree.setColumnCount(9) # 增加一列用于显示类型
self.result_tree.setHeaderLabels(["节点", "目标(IP/域名)", "类型", "端口", "备注", "状态", "消息", "耗时(秒)", "重试次数"])
# 优化列宽分配
self.result_tree.setColumnWidth(0, 110)
self.result_tree.setColumnWidth(1, 180) # 加宽目标列以适应IPv6
self.result_tree.setColumnWidth(2, 70) # 新增的类型列
self.result_tree.setColumnWidth(3, 70)
self.result_tree.setColumnWidth(4, 140)
self.result_tree.setColumnWidth(5, 70)
self.result_tree.setColumnWidth(6, 190)
self.result_tree.setColumnWidth(7, 90)
self.result_tree.setColumnWidth(8, 70)
result_splitter.addWidget(self.result_tree)
result_splitter.setSizes([180, 1000])
main_splitter.addWidget(result_splitter)
# 下部:报告区域
report_group = QGroupBox("测试报告")
report_vbox = QVBoxLayout(report_group)
# 报告文本框
self.report_text = QTextEdit()
self.report_text.setReadOnly(True)
report_vbox.addWidget(self.report_text)
main_splitter.addWidget(report_group)
# 设置上下区域初始比例
main_splitter.setSizes([500, 300])
# 状态栏
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
self.statusBar.showMessage("就绪")
# 添加到主布局
main_layout.addWidget(config_frame)
main_layout.addWidget(main_splitter, 1)
# 存储测试项信息
self.all_nodes_selected = True