使用python开发了一个服务器自动巡检工具

50 阅读3分钟

界面

image.png 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