初始化工程

This commit is contained in:
2025-08-31 11:00:32 +08:00
commit e9c0e13341
8 changed files with 588 additions and 0 deletions

Binary file not shown.

Binary file not shown.

66
app.log Normal file
View File

@@ -0,0 +1,66 @@
2025-08-31 10:55:11.750 | INFO | __main__:<module>: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__:<module>: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__:<module>: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__:<module>: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

9
config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"测试机": {
"ip": "192.168.3.157",
"username": "xiaji",
"password": "xiaji",
"port": 22,
"project": "statuspage"
}
}

49
main.py Normal file
View File

@@ -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())

247
remote_commands_tab.py Normal file
View File

@@ -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)

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
PySide6>=6.0.0
paramiko>=2.7.0
loguru>=0.5.0

214
server_connection_tab.py Normal file
View File

@@ -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