From e9c0e13341254f78275e9211ebdfdb69d7189514 Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 31 Aug 2025 11:00:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote_commands_tab.cpython-38.pyc | Bin 0 -> 6967 bytes .../server_connection_tab.cpython-38.pyc | Bin 0 -> 6020 bytes app.log | 66 +++++ config.json | 9 + main.py | 49 ++++ remote_commands_tab.py | 247 ++++++++++++++++++ requirements.txt | 3 + server_connection_tab.py | 214 +++++++++++++++ 8 files changed, 588 insertions(+) create mode 100644 __pycache__/remote_commands_tab.cpython-38.pyc create mode 100644 __pycache__/server_connection_tab.cpython-38.pyc create mode 100644 app.log create mode 100644 config.json create mode 100644 main.py create mode 100644 remote_commands_tab.py create mode 100644 requirements.txt create mode 100644 server_connection_tab.py diff --git a/__pycache__/remote_commands_tab.cpython-38.pyc b/__pycache__/remote_commands_tab.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a0ac300e21ee37d1b402619175ff05af5f5a7ae GIT binary patch literal 6967 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:44 - 启动应用程序 +2025-08-31 10:55:11.779 | INFO | __main__:__init__:13 - 初始化主窗口 +2025-08-31 10:55:11.781 | INFO | server_connection_tab:__init__:14 - 初始化服务器连接标签页 +2025-08-31 10:55:11.840 | INFO | server_connection_tab:init_ui:75 - 服务器连接标签页UI初始化完成 +2025-08-31 10:55:11.840 | INFO | server_connection_tab:load_config:78 - 加载配置文件 +2025-08-31 10:55:11.840 | INFO | server_connection_tab:load_config:87 - 配置文件不存在,将创建新文件: c:\Users\xiaji\Documents\个人文件夹\夏骥\桌面部署\config.json +2025-08-31 10:55:11.841 | INFO | remote_commands_tab:__init__:52 - 初始化远程命令标签页 +2025-08-31 10:55:11.842 | INFO | remote_commands_tab:init_ui:106 - 远程命令标签页UI初始化完成 +2025-08-31 10:55:11.843 | INFO | __main__:__init__:32 - 主窗口初始化完成 +2025-08-31 10:55:45.065 | INFO | __main__:on_tab_changed:35 - 切换到标签页: 1 +2025-08-31 10:55:45.065 | INFO | remote_commands_tab:set_ssh_client:109 - 设置SSH客户端 +2025-08-31 10:55:46.858 | INFO | __main__:on_tab_changed:35 - 切换到标签页: 0 +2025-08-31 10:56:00.654 | INFO | server_connection_tab:save_config:114 - 保存配置 +2025-08-31 10:56:31.724 | INFO | __main__::44 - 启动应用程序 +2025-08-31 10:56:31.750 | INFO | __main__:__init__:13 - 初始化主窗口 +2025-08-31 10:56:31.751 | INFO | server_connection_tab:__init__:14 - 初始化服务器连接标签页 +2025-08-31 10:56:31.757 | INFO | server_connection_tab:init_ui:87 - 服务器连接标签页UI初始化完成 +2025-08-31 10:56:31.758 | INFO | server_connection_tab:load_config:90 - 加载配置文件 +2025-08-31 10:56:31.758 | INFO | server_connection_tab:load_config:99 - 配置文件不存在,将创建新文件: c:\Users\xiaji\Documents\个人文件夹\夏骥\桌面部署\config.json +2025-08-31 10:56:31.759 | INFO | remote_commands_tab:__init__:52 - 初始化远程命令标签页 +2025-08-31 10:56:31.760 | INFO | remote_commands_tab:init_ui:106 - 远程命令标签页UI初始化完成 +2025-08-31 10:56:31.760 | INFO | __main__:__init__:32 - 主窗口初始化完成 +2025-08-31 10:56:42.212 | INFO | server_connection_tab:add_new_alias:126 - 添加新别名 +2025-08-31 10:56:42.214 | INFO | server_connection_tab:on_alias_changed:116 - 选择别名: 测试机 +2025-08-31 10:56:42.216 | INFO | server_connection_tab:add_new_alias:144 - 已添加新别名: 测试机 +2025-08-31 10:56:56.885 | INFO | server_connection_tab:save_config:147 - 保存配置 +2025-08-31 10:56:56.886 | INFO | server_connection_tab:save_config:171 - 配置已保存到: c:\Users\xiaji\Documents\个人文件夹\夏骥\桌面部署\config.json +2025-08-31 10:56:59.684 | INFO | server_connection_tab:connect_to_server:178 - 尝试连接服务器 +2025-08-31 10:56:59.855 | INFO | server_connection_tab:connect_to_server:197 - 成功连接到服务器: 192.168.3.157 +2025-08-31 10:57:02.873 | INFO | __main__:on_tab_changed:35 - 切换到标签页: 1 +2025-08-31 10:57:02.873 | INFO | remote_commands_tab:set_ssh_client:109 - 设置SSH客户端 +2025-08-31 10:57:03.659 | INFO | remote_commands_tab:install_git:120 - 安装Git +2025-08-31 10:57:03.664 | INFO | remote_commands_tab:run:18 - 执行远程命令: sudo apt update && sudo apt install -y git +2025-08-31 10:57:03.683 | ERROR | remote_commands_tab:run:40 - 命令执行失败,退出状态: 1 +2025-08-31 10:57:54.120 | INFO | __main__::44 - 启动应用程序 +2025-08-31 10:57:54.147 | INFO | __main__:__init__:13 - 初始化主窗口 +2025-08-31 10:57:54.148 | INFO | server_connection_tab:__init__:14 - 初始化服务器连接标签页 +2025-08-31 10:57:54.154 | INFO | server_connection_tab:init_ui:87 - 服务器连接标签页UI初始化完成 +2025-08-31 10:57:54.155 | INFO | server_connection_tab:load_config:90 - 加载配置文件 +2025-08-31 10:57:54.155 | INFO | server_connection_tab:load_config:97 - 成功加载配置文件: c:\Users\xiaji\Documents\个人文件夹\夏骥\桌面部署\config.json +2025-08-31 10:57:54.156 | INFO | server_connection_tab:on_alias_changed:116 - 选择别名: 测试机 +2025-08-31 10:57:54.157 | INFO | remote_commands_tab:__init__:116 - 初始化远程命令标签页 +2025-08-31 10:57:54.158 | INFO | remote_commands_tab:init_ui:170 - 远程命令标签页UI初始化完成 +2025-08-31 10:57:54.158 | INFO | __main__:__init__:32 - 主窗口初始化完成 +2025-08-31 10:57:58.773 | INFO | __main__::44 - 启动应用程序 +2025-08-31 10:57:58.792 | INFO | __main__:__init__:13 - 初始化主窗口 +2025-08-31 10:57:58.792 | INFO | server_connection_tab:__init__:14 - 初始化服务器连接标签页 +2025-08-31 10:57:58.799 | INFO | server_connection_tab:init_ui:87 - 服务器连接标签页UI初始化完成 +2025-08-31 10:57:58.799 | INFO | server_connection_tab:load_config:90 - 加载配置文件 +2025-08-31 10:57:58.800 | INFO | server_connection_tab:load_config:97 - 成功加载配置文件: c:\Users\xiaji\Documents\个人文件夹\夏骥\桌面部署\config.json +2025-08-31 10:57:58.800 | INFO | server_connection_tab:on_alias_changed:116 - 选择别名: 测试机 +2025-08-31 10:57:58.801 | INFO | remote_commands_tab:__init__:116 - 初始化远程命令标签页 +2025-08-31 10:57:58.802 | INFO | remote_commands_tab:init_ui:170 - 远程命令标签页UI初始化完成 +2025-08-31 10:57:58.802 | INFO | __main__:__init__:32 - 主窗口初始化完成 +2025-08-31 10:58:00.769 | INFO | server_connection_tab:connect_to_server:178 - 尝试连接服务器 +2025-08-31 10:58:00.859 | INFO | server_connection_tab:connect_to_server:197 - 成功连接到服务器: 192.168.3.157 +2025-08-31 10:58:02.607 | INFO | __main__:on_tab_changed:35 - 切换到标签页: 1 +2025-08-31 10:58:02.608 | INFO | remote_commands_tab:set_ssh_client:173 - 设置SSH客户端 +2025-08-31 10:58:03.712 | INFO | remote_commands_tab:install_git:184 - 安装Git +2025-08-31 10:58:03.714 | INFO | remote_commands_tab:run:53 - 执行远程命令: sudo apt update && sudo apt install -y git +2025-08-31 10:59:34.622 | INFO | remote_commands_tab:clone_repository:216 - 克隆仓库 +2025-08-31 10:59:34.624 | INFO | remote_commands_tab:run:53 - 执行远程命令: git clone http://192.168.3.241:3000/xiaji/django.remote.git +2025-08-31 10:59:34.684 | INFO | remote_commands_tab:run:101 - 命令执行成功: git clone http://192.168.3.241:3000/xiaji/django.remote.git +2025-08-31 11:00:00.984 | INFO | remote_commands_tab:clone_repository:216 - 克隆仓库 +2025-08-31 11:00:00.986 | INFO | remote_commands_tab:run:53 - 执行远程命令: git clone http://192.168.3.241:3000/xiaji/webstatus.git +2025-08-31 11:00:01.039 | INFO | remote_commands_tab:run:101 - 命令执行成功: git clone http://192.168.3.241:3000/xiaji/webstatus.git diff --git a/config.json b/config.json new file mode 100644 index 0000000..40607d9 --- /dev/null +++ b/config.json @@ -0,0 +1,9 @@ +{ + "测试机": { + "ip": "192.168.3.157", + "username": "xiaji", + "password": "xiaji", + "port": 22, + "project": "statuspage" + } +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..9d46503 --- /dev/null +++ b/main.py @@ -0,0 +1,49 @@ +import sys +from PySide6.QtWidgets import QApplication, QMainWindow, QTabWidget +from PySide6.QtCore import QSize +from loguru import logger + +from server_connection_tab import ServerConnectionTab +from remote_commands_tab import RemoteCommandsTab + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + + logger.info("初始化主窗口") + self.setWindowTitle("服务器管理工具") + self.setMinimumSize(QSize(800, 600)) + + # 创建标签页组件 + self.tabs = QTabWidget() + self.setCentralWidget(self.tabs) + + # 添加服务器连接标签页 + self.server_connection_tab = ServerConnectionTab() + self.tabs.addTab(self.server_connection_tab, "服务器连接") + + # 添加远程命令标签页 + self.remote_commands_tab = RemoteCommandsTab() + self.tabs.addTab(self.remote_commands_tab, "远程命令") + + # 连接标签页切换信号 + self.tabs.currentChanged.connect(self.on_tab_changed) + + logger.info("主窗口初始化完成") + + def on_tab_changed(self, index): + logger.info(f"切换到标签页: {index}") + + # 当切换到远程命令标签页时,传递SSH客户端 + if index == 1: # 远程命令标签页的索引 + ssh_client = self.server_connection_tab.get_ssh_client() + self.remote_commands_tab.set_ssh_client(ssh_client) + +if __name__ == "__main__": + logger.add("app.log", rotation="10 MB") + logger.info("启动应用程序") + + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/remote_commands_tab.py b/remote_commands_tab.py new file mode 100644 index 0000000..06bd5dc --- /dev/null +++ b/remote_commands_tab.py @@ -0,0 +1,247 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QLineEdit, QPushButton, QGroupBox, QTextEdit, + QMessageBox, QFormLayout, QDialog, QDialogButtonBox) +from PySide6.QtCore import Qt, QThread, Signal +from loguru import logger + +class PasswordDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("输入密码") + self.setMinimumWidth(300) + + layout = QVBoxLayout() + + # 提示标签 + label = QLabel("请输入sudo密码:") + layout.addWidget(label) + + # 密码输入框 + self.password_input = QLineEdit() + self.password_input.setEchoMode(QLineEdit.Password) + layout.addWidget(self.password_input) + + # 按钮 + button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + layout.addWidget(button_box) + + self.setLayout(layout) + + def get_password(self): + return self.password_input.text() + +class RemoteCommandThread(QThread): + output_signal = Signal(str) + finished_signal = Signal(bool, str) + password_request_signal = Signal() # 请求密码的信号 + + def __init__(self, ssh_client, command): + super().__init__() + self.ssh_client = ssh_client + self.command = command + self.password = None + self.waiting_for_password = False + + def set_password(self, password): + self.password = password + self.waiting_for_password = False + + def run(self): + try: + logger.info(f"执行远程命令: {self.command}") + + # 如果命令包含sudo,修改为使用-S选项从标准输入读取密码 + if "sudo" in self.command: + command_with_sudo = self.command.replace("sudo", "sudo -S") + stdin, stdout, stderr = self.ssh_client.exec_command(command_with_sudo) + + # 检查是否需要密码 + password_prompt = False + for line in stderr: + self.output_signal.emit(line) + if "password for" in line.lower() or "密码" in line: + password_prompt = True + break + + # 如果需要密码,请求用户输入 + if password_prompt: + self.waiting_for_password = True + self.password_request_signal.emit() + + # 等待密码输入 + while self.waiting_for_password: + self.msleep(100) + + # 发送密码 + if self.password: + stdin.write(self.password + "\n") + stdin.flush() + else: + stdin, stdout, stderr = self.ssh_client.exec_command(self.command) + + # 读取输出 + output = "" + for line in stdout: + output += line + self.output_signal.emit(line) + + # 读取错误 + error = "" + for line in stderr: + error += line + if "password for" not in line.lower() and "密码" not in line: # 避免重复显示密码提示 + self.output_signal.emit(f"错误: {line}") + + # 检查退出状态 + exit_status = stdout.channel.recv_exit_status() + + if exit_status == 0: + logger.info(f"命令执行成功: {self.command}") + self.finished_signal.emit(True, "命令执行成功") + else: + logger.error(f"命令执行失败,退出状态: {exit_status}") + self.finished_signal.emit(False, f"命令执行失败,退出状态: {exit_status}") + + except Exception as e: + logger.error(f"执行命令时发生错误: {str(e)}") + self.output_signal.emit(f"错误: {str(e)}") + self.finished_signal.emit(False, f"执行命令时发生错误: {str(e)}") + +class RemoteCommandsTab(QWidget): + def __init__(self): + super().__init__() + + logger.info("初始化远程命令标签页") + self.ssh_client = None + self.command_thread = None + self.init_ui() + + def init_ui(self): + # 主布局 + main_layout = QVBoxLayout() + + # Git操作组 + git_group = QGroupBox("Git操作") + git_layout = QVBoxLayout() + + # 安装Git按钮 + install_git_layout = QHBoxLayout() + self.install_git_button = QPushButton("安装Git") + self.install_git_button.clicked.connect(self.install_git) + install_git_layout.addWidget(self.install_git_button) + install_git_layout.addStretch() + git_layout.addLayout(install_git_layout) + + # 克隆项目 + clone_layout = QFormLayout() + self.repo_url_input = QLineEdit() + clone_layout.addRow("仓库URL:", self.repo_url_input) + + self.clone_button = QPushButton("克隆项目") + self.clone_button.clicked.connect(self.clone_repository) + clone_layout.addRow(self.clone_button) + + git_layout.addLayout(clone_layout) + git_group.setLayout(git_layout) + main_layout.addWidget(git_group) + + # 命令输出区域 + output_group = QGroupBox("命令输出") + output_layout = QVBoxLayout() + + self.output_text = QTextEdit() + self.output_text.setReadOnly(True) + output_layout.addWidget(self.output_text) + + output_group.setLayout(output_layout) + main_layout.addWidget(output_group) + + # 状态栏 + self.status_label = QLabel("就绪") + self.status_label.setAlignment(Qt.AlignCenter) + main_layout.addWidget(self.status_label) + + # 添加伸缩空间 + main_layout.addStretch() + + self.setLayout(main_layout) + logger.info("远程命令标签页UI初始化完成") + + def set_ssh_client(self, ssh_client): + logger.info("设置SSH客户端") + self.ssh_client = ssh_client + + if self.ssh_client: + self.status_label.setText("已连接到服务器") + self.status_label.setStyleSheet("color: green;") + else: + self.status_label.setText("未连接到服务器") + self.status_label.setStyleSheet("color: red;") + + def install_git(self): + logger.info("安装Git") + + if not self.ssh_client: + QMessageBox.warning(self, "警告", "请先连接到服务器") + return + + self.output_text.clear() + self.status_label.setText("正在安装Git...") + + # 创建并启动线程执行命令 + self.command_thread = RemoteCommandThread(self.ssh_client, "sudo apt update && sudo apt install -y git") + self.command_thread.output_signal.connect(self.append_output) + self.command_thread.finished_signal.connect(self.on_command_finished) + self.command_thread.password_request_signal.connect(self.request_password) + self.command_thread.start() + + def request_password(self): + logger.info("请求输入sudo密码") + + # 创建密码输入对话框 + dialog = PasswordDialog(self) + if dialog.exec() == QDialog.Accepted: + password = dialog.get_password() + if password: + self.command_thread.set_password(password) + self.output_text.append("密码已发送\n") + else: + self.output_text.append("已取消输入密码\n") + self.command_thread.set_password("") + self.command_thread.waiting_for_password = False + + def clone_repository(self): + logger.info("克隆仓库") + + if not self.ssh_client: + QMessageBox.warning(self, "警告", "请先连接到服务器") + return + + repo_url = self.repo_url_input.text().strip() + if not repo_url: + QMessageBox.warning(self, "警告", "请输入仓库URL") + return + + self.output_text.clear() + self.status_label.setText("正在克隆仓库...") + + # 创建并启动线程执行命令 + self.command_thread = RemoteCommandThread(self.ssh_client, f"git clone {repo_url}") + self.command_thread.output_signal.connect(self.append_output) + self.command_thread.finished_signal.connect(self.on_command_finished) + self.command_thread.start() + + def append_output(self, text): + self.output_text.append(text) + + def on_command_finished(self, success, message): + if success: + self.status_label.setText(message) + self.status_label.setStyleSheet("color: green;") + QMessageBox.information(self, "成功", message) + else: + self.status_label.setText(message) + self.status_label.setStyleSheet("color: red;") + QMessageBox.warning(self, "错误", message) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6a812f6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +PySide6>=6.0.0 +paramiko>=2.7.0 +loguru>=0.5.0 \ No newline at end of file diff --git a/server_connection_tab.py b/server_connection_tab.py new file mode 100644 index 0000000..752df06 --- /dev/null +++ b/server_connection_tab.py @@ -0,0 +1,214 @@ +import json +import os +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QLineEdit, QComboBox, QPushButton, QGroupBox, + QMessageBox, QFormLayout) +from PySide6.QtCore import Qt +import paramiko +from loguru import logger + +class ServerConnectionTab(QWidget): + def __init__(self): + super().__init__() + + logger.info("初始化服务器连接标签页") + self.ssh_client = None + self.config_data = {} + self.init_ui() + self.load_config() + + def init_ui(self): + # 主布局 + main_layout = QVBoxLayout() + + # 服务器配置组 + config_group = QGroupBox("服务器配置") + config_layout = QFormLayout() + + # 别名选择 + alias_layout = QHBoxLayout() + + self.alias_combo = QComboBox() + self.alias_combo.currentTextChanged.connect(self.on_alias_changed) + alias_layout.addWidget(self.alias_combo, 3) + + self.new_alias_input = QLineEdit() + self.new_alias_input.setPlaceholderText("输入新别名") + alias_layout.addWidget(self.new_alias_input, 2) + + self.add_alias_button = QPushButton("添加") + self.add_alias_button.clicked.connect(self.add_new_alias) + alias_layout.addWidget(self.add_alias_button, 1) + + config_layout.addRow("别名:", alias_layout) + + # 服务器信息输入 + self.ip_input = QLineEdit() + config_layout.addRow("IP地址:", self.ip_input) + + self.username_input = QLineEdit() + config_layout.addRow("用户名:", self.username_input) + + self.password_input = QLineEdit() + self.password_input.setEchoMode(QLineEdit.Password) + config_layout.addRow("密码:", self.password_input) + + self.port_input = QLineEdit("22") + config_layout.addRow("端口:", self.port_input) + + self.project_input = QLineEdit() + config_layout.addRow("项目名称:", self.project_input) + + config_group.setLayout(config_layout) + main_layout.addWidget(config_group) + + # 按钮区域 + button_layout = QHBoxLayout() + + self.save_button = QPushButton("保存配置") + self.save_button.clicked.connect(self.save_config) + button_layout.addWidget(self.save_button) + + self.connect_button = QPushButton("连接服务器") + self.connect_button.clicked.connect(self.connect_to_server) + button_layout.addWidget(self.connect_button) + + main_layout.addLayout(button_layout) + + # 连接状态 + self.status_label = QLabel("未连接") + self.status_label.setAlignment(Qt.AlignCenter) + main_layout.addWidget(self.status_label) + + # 添加伸缩空间 + main_layout.addStretch() + + self.setLayout(main_layout) + logger.info("服务器连接标签页UI初始化完成") + + def load_config(self): + logger.info("加载配置文件") + config_path = os.path.join(os.path.dirname(__file__), "config.json") + + try: + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + self.config_data = json.load(f) + logger.info(f"成功加载配置文件: {config_path}") + else: + logger.info(f"配置文件不存在,将创建新文件: {config_path}") + self.config_data = {} + + # 更新别名下拉框 + self.alias_combo.clear() + for alias in self.config_data.keys(): + self.alias_combo.addItem(alias) + + # 如果有配置,选择第一个别名 + if self.config_data: + self.alias_combo.setCurrentIndex(0) + + except Exception as e: + logger.error(f"加载配置文件失败: {str(e)}") + QMessageBox.warning(self, "错误", f"加载配置文件失败: {str(e)}") + + def on_alias_changed(self, alias): + logger.info(f"选择别名: {alias}") + if alias and alias in self.config_data: + server_info = self.config_data[alias] + self.ip_input.setText(server_info.get("ip", "")) + self.username_input.setText(server_info.get("username", "")) + self.password_input.setText(server_info.get("password", "")) + self.port_input.setText(str(server_info.get("port", "22"))) + self.project_input.setText(server_info.get("project", "")) + + def add_new_alias(self): + logger.info("添加新别名") + new_alias = self.new_alias_input.text().strip() + + if not new_alias: + QMessageBox.warning(self, "警告", "请输入新别名") + return + + if new_alias in self.config_data: + QMessageBox.warning(self, "警告", "该别名已存在") + return + + # 添加新别名到下拉框 + self.alias_combo.addItem(new_alias) + self.alias_combo.setCurrentText(new_alias) + + # 清空输入框 + self.new_alias_input.clear() + + logger.info(f"已添加新别名: {new_alias}") + + def save_config(self): + logger.info("保存配置") + alias = self.alias_combo.currentText() + + if not alias: + QMessageBox.warning(self, "警告", "请选择或添加别名") + return + + # 获取当前输入的值 + server_info = { + "ip": self.ip_input.text(), + "username": self.username_input.text(), + "password": self.password_input.text(), + "port": int(self.port_input.text()), + "project": self.project_input.text() + } + + # 更新配置数据 + self.config_data[alias] = server_info + + # 保存到文件 + config_path = os.path.join(os.path.dirname(__file__), "config.json") + try: + with open(config_path, "w", encoding="utf-8") as f: + json.dump(self.config_data, f, ensure_ascii=False, indent=4) + logger.info(f"配置已保存到: {config_path}") + QMessageBox.information(self, "成功", "配置已保存") + except Exception as e: + logger.error(f"保存配置失败: {str(e)}") + QMessageBox.warning(self, "错误", f"保存配置失败: {str(e)}") + + def connect_to_server(self): + logger.info("尝试连接服务器") + + # 获取连接信息 + ip = self.ip_input.text() + username = self.username_input.text() + password = self.password_input.text() + port = int(self.port_input.text()) + + if not all([ip, username, password]): + QMessageBox.warning(self, "警告", "请填写完整的连接信息") + return + + try: + # 创建SSH客户端 + self.ssh_client = paramiko.SSHClient() + self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # 连接服务器 + self.ssh_client.connect(ip, port=port, username=username, password=password) + logger.info(f"成功连接到服务器: {ip}") + + self.status_label.setText(f"已连接到 {ip}") + self.status_label.setStyleSheet("color: green;") + QMessageBox.information(self, "成功", f"成功连接到服务器: {ip}") + + except Exception as e: + logger.error(f"连接服务器失败: {str(e)}") + self.status_label.setText("连接失败") + self.status_label.setStyleSheet("color: red;") + QMessageBox.warning(self, "错误", f"连接服务器失败: {str(e)}") + + if self.ssh_client: + self.ssh_client.close() + self.ssh_client = None + + def get_ssh_client(self): + return self.ssh_client \ No newline at end of file