完善nginx标签。使用unix套接字。增加远程命令的运行和显示。
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
194
gunicorn_tab.py
194
gunicorn_tab.py
@@ -436,6 +436,116 @@ class GunicornLogThread(QThread):
|
||||
self.result_ready.emit(False, error_msg)
|
||||
logger.error(f"Gunicorn服务日志查看异常: {error_msg}")
|
||||
|
||||
class GunicornLogCheckThread(QThread):
|
||||
"""检查并创建Gunicorn日志文件目录和文件的线程"""
|
||||
result_ready = Signal(bool, str)
|
||||
|
||||
def __init__(self, ssh_client, username, git_url, password):
|
||||
super().__init__()
|
||||
self.ssh_client = ssh_client
|
||||
self.username = username
|
||||
self.git_url = git_url
|
||||
self.password = password
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
# 从git_url中提取项目名(去掉.git后缀)
|
||||
project_name = self.git_url.split('/')[-1].replace('.git', '')
|
||||
logger.info(f"从git_url提取的项目名: {project_name}")
|
||||
|
||||
# 构建日志目录路径
|
||||
log_dir = f"/home/{self.username}/{project_name}/logs"
|
||||
access_log_path = f"{log_dir}/gunicorn_access.log"
|
||||
error_log_path = f"{log_dir}/gunicorn_error.log"
|
||||
|
||||
logger.info(f"检查日志目录: {log_dir}")
|
||||
|
||||
# 检查并创建日志目录
|
||||
check_dir_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S mkdir -p {log_dir}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(check_dir_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"创建日志目录失败: {error}")
|
||||
logger.error(f"创建日志目录失败: {error}")
|
||||
return
|
||||
|
||||
# 设置日志目录权限
|
||||
chmod_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S chmod 755 {log_dir}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(chmod_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"设置日志目录权限失败: {error}")
|
||||
logger.error(f"设置日志目录权限失败: {error}")
|
||||
return
|
||||
|
||||
# 检查并创建访问日志文件
|
||||
check_access_log_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S touch {access_log_path}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(check_access_log_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"创建访问日志文件失败: {error}")
|
||||
logger.error(f"创建访问日志文件失败: {error}")
|
||||
return
|
||||
|
||||
# 设置访问日志文件权限
|
||||
chmod_access_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S chmod 644 {access_log_path}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(chmod_access_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"设置访问日志文件权限失败: {error}")
|
||||
logger.error(f"设置访问日志文件权限失败: {error}")
|
||||
return
|
||||
|
||||
# 检查并创建错误日志文件
|
||||
check_error_log_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S touch {error_log_path}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(check_error_log_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"创建错误日志文件失败: {error}")
|
||||
logger.error(f"创建错误日志文件失败: {error}")
|
||||
return
|
||||
|
||||
# 设置错误日志文件权限
|
||||
chmod_error_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S chmod 644 {error_log_path}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(chmod_error_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"设置错误日志文件权限失败: {error}")
|
||||
logger.error(f"设置错误日志文件权限失败: {error}")
|
||||
return
|
||||
|
||||
# 设置日志文件所有者为用户
|
||||
chown_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S chown -R {self.username}:{self.username} {log_dir}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(chown_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if exit_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"设置日志文件所有者失败: {error}")
|
||||
logger.error(f"设置日志文件所有者失败: {error}")
|
||||
return
|
||||
|
||||
result_msg = f"日志目录和文件创建成功:\n目录: {log_dir}\n访问日志: {access_log_path}\n错误日志: {error_log_path}"
|
||||
self.result_ready.emit(True, result_msg)
|
||||
logger.info("日志目录和文件创建成功")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self.result_ready.emit(False, error_msg)
|
||||
logger.error(f"检查并创建日志文件异常: {error_msg}")
|
||||
|
||||
class ServerControlThread(QThread):
|
||||
"""控制服务器设置的线程"""
|
||||
result_ready = Signal(bool, str)
|
||||
@@ -578,6 +688,11 @@ class GunicornTab(QWidget):
|
||||
self.view_logs_btn.clicked.connect(self.view_service_logs)
|
||||
service_btn_layout.addWidget(self.view_logs_btn)
|
||||
|
||||
# 检查日志文件按钮
|
||||
self.check_log_files_btn = QPushButton("检查日志文件")
|
||||
self.check_log_files_btn.clicked.connect(self.check_log_files)
|
||||
service_btn_layout.addWidget(self.check_log_files_btn)
|
||||
|
||||
service_layout.addLayout(service_btn_layout)
|
||||
layout.addLayout(service_layout)
|
||||
|
||||
@@ -628,27 +743,19 @@ class GunicornTab(QWidget):
|
||||
def init_service_content(self):
|
||||
"""初始化服务文件内容"""
|
||||
default_content = "[Unit]\n"
|
||||
default_content += "Description=Gunicorn daemon for myproject\n"
|
||||
default_content += "Description=Gunicorn Daemon for statuspage Project\n"
|
||||
default_content += "After=network.target\n\n"
|
||||
default_content += "[Service]\n"
|
||||
default_content += "User=【用户名】\n"
|
||||
default_content += "Group=【用户名】\n"
|
||||
default_content += "WorkingDirectory=【Django路径】\n"
|
||||
default_content += "# 所有Gunicorn参数直接在这里配置\n"
|
||||
default_content += "ExecStart=/usr/bin/gunicorn \\ \n"
|
||||
default_content += " --pythonpath 【Django路径】 \\ \n"
|
||||
default_content += " --bind 127.0.0.1:8000 \\ \n"
|
||||
default_content += " --workers $(nproc) \\ \n"
|
||||
default_content += " --worker-class sync \\ \n"
|
||||
default_content += " --timeout 60 \\ \n"
|
||||
default_content += " --name 【项目名】 \\ \n"
|
||||
default_content += " --access-logfile 【Django路径】/logs/gunicorn_access.log \\ \n"
|
||||
default_content += " --error-logfile 【Django路径】/logs/gunicorn_error.log \\ \n"
|
||||
default_content += " --log-level info \\ \n"
|
||||
default_content += " 【项目名】.wsgi:application\n"
|
||||
default_content += "Restart=on-failure\n"
|
||||
default_content += "RestartSec=5s\n"
|
||||
default_content += "PrivateTmp=true\n\n"
|
||||
default_content += "# 以xiaji用户运行(确保对/home/xiaji有完全权限)\n"
|
||||
default_content += "User=xiaji\n"
|
||||
default_content += "Group=xiaji\n"
|
||||
default_content += "# 项目工作目录\n"
|
||||
default_content += "WorkingDirectory=/home/xiaji/webstatus\n\n"
|
||||
default_content += "# 启动前预处理:先删除旧socket(避免残留),再创建目录(如果不存在)\n"
|
||||
default_content += "ExecStartPre=/bin/rm -f /home/xiaji/webstatus/sock/gunicorn.sock\n"
|
||||
default_content += "ExecStartPre=/bin/mkdir -p /home/xiaji/webstatus/sock\n\n"
|
||||
default_content += "# 单行ExecStart(无反斜杠,避免格式错误)\n"
|
||||
default_content += "ExecStart=/usr/bin/gunicorn --pythonpath /home/xiaji/webstatus --workers 3 --bind unix:/home/xiaji/webstatus/sock/gunicorn.sock --access-logfile /home/xiaji/webstatus/logs/gunicorn_access.log --error-logfile /home/xiaji/webstatus/logs/gunicorn_error.log statuspage.wsgi:application\n\n"
|
||||
default_content += "[Install]\n"
|
||||
default_content += "WantedBy=multi-user.target"
|
||||
|
||||
@@ -964,6 +1071,55 @@ class GunicornTab(QWidget):
|
||||
logger.error(f"Gunicorn服务日志查看失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"Gunicorn服务日志查看失败: {message}")
|
||||
|
||||
def check_log_files(self):
|
||||
"""检查并创建Gunicorn日志文件目录和文件"""
|
||||
if not self.ssh_client:
|
||||
self.append_output("错误: 未连接到服务器")
|
||||
return
|
||||
|
||||
if not self.username:
|
||||
self.append_output("错误: 未设置用户名")
|
||||
return
|
||||
|
||||
# 读取config.json获取git_url
|
||||
try:
|
||||
import json
|
||||
with open('config.json', 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
# 获取第一个服务器的配置
|
||||
server_config = next(iter(config.values()))
|
||||
git_url = server_config.get('git_url', '')
|
||||
if not git_url:
|
||||
self.append_output("错误: config.json中未找到git_url")
|
||||
return
|
||||
except Exception as e:
|
||||
self.append_output(f"错误: 读取config.json失败: {str(e)}")
|
||||
return
|
||||
|
||||
# 请求用户输入sudo密码
|
||||
dialog = PasswordDialog(self)
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
password = dialog.get_password()
|
||||
self.append_output("正在检查并创建Gunicorn日志文件目录和文件...")
|
||||
|
||||
# 创建并启动日志检查线程
|
||||
self.log_check_thread = GunicornLogCheckThread(self.ssh_client, self.username, git_url, password)
|
||||
self.log_check_thread.result_ready.connect(self.on_log_check_result)
|
||||
self.log_check_thread.start()
|
||||
else:
|
||||
self.append_output("用户取消了密码输入")
|
||||
|
||||
def on_log_check_result(self, success, message):
|
||||
"""处理日志检查结果"""
|
||||
if success:
|
||||
self.append_output(message)
|
||||
logger.info("Gunicorn日志文件检查和创建成功")
|
||||
QMessageBox.information(self, "成功", "Gunicorn日志文件检查和创建成功")
|
||||
else:
|
||||
self.append_output(f"检查并创建日志文件失败: {message}")
|
||||
logger.error(f"Gunicorn日志文件检查和创建失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"Gunicorn日志文件检查和创建失败: {message}")
|
||||
|
||||
def on_control_result(self, success, message):
|
||||
"""处理控制结果"""
|
||||
if success:
|
||||
|
||||
141
nginx_tab.py
141
nginx_tab.py
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QTextEdit, QFileDialog, QMessageBox,
|
||||
QLineEdit, QDialog, QDialogButtonBox)
|
||||
@@ -202,6 +203,51 @@ class NginxControlThread(QThread):
|
||||
self.result_ready.emit(False, error_msg)
|
||||
logger.error(f"Nginx服务控制异常: {error_msg}")
|
||||
|
||||
class NginxPermissionsThread(QThread):
|
||||
"""设置Nginx权限的线程"""
|
||||
result_ready = Signal(bool, str)
|
||||
|
||||
def __init__(self, ssh_client, commands, password):
|
||||
super().__init__()
|
||||
self.ssh_client = ssh_client
|
||||
self.commands = commands # 命令列表
|
||||
self.password = password
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
logger.info("开始执行Nginx权限设置操作")
|
||||
|
||||
# 执行所有命令
|
||||
for i, command in enumerate(self.commands):
|
||||
logger.info(f"执行命令 {i+1}/{len(self.commands)}: {command}")
|
||||
|
||||
# 使用bash -c将命令组合在一起,确保密码对所有sudo命令有效
|
||||
# 使用-S选项让sudo从标准输入读取密码
|
||||
full_command = f"bash -c 'echo \"{self.password}\" | sudo -S {command}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(full_command)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
|
||||
output = stdout.read().decode()
|
||||
error = stderr.read().decode()
|
||||
|
||||
if exit_status != 0:
|
||||
error_msg = f"命令执行失败: {command}\n错误信息: {error}"
|
||||
self.result_ready.emit(False, error_msg)
|
||||
logger.error(error_msg)
|
||||
return
|
||||
|
||||
logger.info(f"命令执行成功: {command}")
|
||||
|
||||
# 所有命令执行成功
|
||||
success_msg = "所有权限设置命令执行成功"
|
||||
self.result_ready.emit(True, success_msg)
|
||||
logger.info(success_msg)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self.result_ready.emit(False, error_msg)
|
||||
logger.error(f"Nginx权限设置异常: {error_msg}")
|
||||
|
||||
class NginxSiteThread(QThread):
|
||||
"""处理Nginx站点配置的线程"""
|
||||
result_ready = Signal(bool, str)
|
||||
@@ -250,6 +296,24 @@ class NginxSiteThread(QThread):
|
||||
|
||||
elif self.operation == "enable":
|
||||
# 启用站点配置
|
||||
# 首先检查目标链接是否存在,如果存在则先删除
|
||||
check_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S test -f /etc/nginx/sites-enabled/{self.site_name}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(check_cmd)
|
||||
check_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if check_status == 0:
|
||||
# 目标链接存在,先删除
|
||||
remove_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S rm -f /etc/nginx/sites-enabled/{self.site_name}'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(remove_cmd)
|
||||
remove_status = stdout.channel.recv_exit_status()
|
||||
|
||||
if remove_status != 0:
|
||||
error = stderr.read().decode()
|
||||
self.result_ready.emit(False, f"删除现有符号链接失败: {error}")
|
||||
logger.error(f"删除现有符号链接失败: {error}")
|
||||
return
|
||||
|
||||
# 创建符号链接
|
||||
enable_cmd = f"bash -c 'echo \"{self.password}\" | sudo -S ln -s /etc/nginx/sites-available/{self.site_name} /etc/nginx/sites-enabled/'"
|
||||
stdin, stdout, stderr = self.ssh_client.exec_command(enable_cmd)
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
@@ -352,6 +416,11 @@ class NginxTab(QWidget):
|
||||
self.check_nginx_status_btn.clicked.connect(self.check_nginx_status)
|
||||
control_layout.addWidget(self.check_nginx_status_btn)
|
||||
|
||||
# 一键赋予权限按钮
|
||||
self.set_permissions_btn = QPushButton("一键赋予权限")
|
||||
self.set_permissions_btn.clicked.connect(self.set_permissions)
|
||||
control_layout.addWidget(self.set_permissions_btn)
|
||||
|
||||
layout.addLayout(control_layout)
|
||||
|
||||
# 输出区域
|
||||
@@ -782,4 +851,74 @@ http {
|
||||
else:
|
||||
self.append_output(f"操作失败: {message}")
|
||||
logger.error(f"Nginx服务控制失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"Nginx服务控制失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"Nginx服务控制失败: {message}")
|
||||
|
||||
def set_permissions(self):
|
||||
"""一键赋予权限"""
|
||||
if not self.ssh_client:
|
||||
self.append_output("错误: 未连接到服务器")
|
||||
return
|
||||
|
||||
# 读取config.json文件获取username和git_url
|
||||
try:
|
||||
with open('config.json', 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# 获取第一个服务器配置(假设只有一个服务器配置)
|
||||
server_config = next(iter(config.values()))
|
||||
username = server_config.get('username', '')
|
||||
git_url = server_config.get('git_url', '')
|
||||
|
||||
# 从git_url中提取项目名(.git前的值)
|
||||
if git_url:
|
||||
# 提取git_url中最后一个/和.git之间的部分
|
||||
if '/' in git_url:
|
||||
project_name = git_url.split('/')[-1]
|
||||
if project_name.endswith('.git'):
|
||||
project_name = project_name[:-4] # 移除.git后缀
|
||||
else:
|
||||
project_name = 'webstatus' # 默认值
|
||||
else:
|
||||
project_name = 'webstatus' # 默认值
|
||||
|
||||
logger.info(f"从配置文件获取用户名: {username}, 项目名: {project_name}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self.append_output(f"读取配置文件失败: {error_msg}")
|
||||
logger.error(f"读取配置文件失败: {error_msg}")
|
||||
QMessageBox.warning(self, "错误", f"读取配置文件失败: {error_msg}")
|
||||
return
|
||||
|
||||
# 请求用户输入sudo密码
|
||||
dialog = PasswordDialog(self)
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
password = dialog.get_password()
|
||||
self.append_output("正在设置权限...")
|
||||
|
||||
# 构建命令序列
|
||||
commands = [
|
||||
f"sudo chmod g+x /home/{username}/",
|
||||
f"sudo chown -R {username}:www-data /home/{username}/{project_name}",
|
||||
f"sudo chmod g+x /home/{username}/{project_name}",
|
||||
f"sudo chown -R {username}:www-data /home/{username}/{project_name}/sock",
|
||||
f"sudo chmod -R 770 /home/{username}/{project_name}/sock"
|
||||
]
|
||||
|
||||
# 创建并启动权限设置线程
|
||||
self.permissions_thread = NginxPermissionsThread(self.ssh_client, commands, password)
|
||||
self.permissions_thread.result_ready.connect(self.on_permissions_result)
|
||||
self.permissions_thread.start()
|
||||
else:
|
||||
self.append_output("用户取消了密码输入")
|
||||
|
||||
def on_permissions_result(self, success, message):
|
||||
"""处理权限设置结果"""
|
||||
if success:
|
||||
self.append_output(f"权限设置成功: {message}")
|
||||
logger.info(f"权限设置成功: {message}")
|
||||
QMessageBox.information(self, "成功", message)
|
||||
else:
|
||||
self.append_output(f"权限设置失败: {message}")
|
||||
logger.error(f"权限设置失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"权限设置失败: {message}")
|
||||
@@ -79,27 +79,33 @@ class RemoteCommandThread(QThread):
|
||||
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()
|
||||
# 如果预先设置了密码,直接发送
|
||||
if self.password:
|
||||
logger.info("使用预先设置的密码")
|
||||
stdin.write(self.password + "\n")
|
||||
stdin.flush()
|
||||
else:
|
||||
# 检查是否需要密码
|
||||
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
|
||||
|
||||
# 等待密码输入
|
||||
while self.waiting_for_password:
|
||||
self.msleep(100)
|
||||
|
||||
# 发送密码
|
||||
if self.password:
|
||||
stdin.write(self.password + "\n")
|
||||
stdin.flush()
|
||||
# 如果需要密码,请求用户输入
|
||||
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)
|
||||
|
||||
@@ -194,26 +200,24 @@ class RemoteCommandsTab(QWidget):
|
||||
system_group = QGroupBox("系统管理")
|
||||
system_layout = QVBoxLayout()
|
||||
|
||||
# 时区设置
|
||||
timezone_layout = QHBoxLayout()
|
||||
self.set_timezone_button = QPushButton("设置时区为北京时区")
|
||||
self.set_timezone_button.clicked.connect(self.set_timezone)
|
||||
timezone_layout.addWidget(self.set_timezone_button)
|
||||
timezone_layout.addStretch()
|
||||
system_layout.addLayout(timezone_layout)
|
||||
# 自定义命令执行区域
|
||||
custom_command_layout = QVBoxLayout()
|
||||
custom_command_layout.addWidget(QLabel("自定义命令:"))
|
||||
|
||||
# 服务器重启
|
||||
reboot_layout = QHBoxLayout()
|
||||
self.reboot_button = QPushButton("重启服务器")
|
||||
self.reboot_button.clicked.connect(self.reboot_server)
|
||||
reboot_layout.addWidget(self.reboot_button)
|
||||
reboot_layout.addStretch()
|
||||
system_layout.addLayout(reboot_layout)
|
||||
# 命令输入框
|
||||
self.custom_command_input = QTextEdit()
|
||||
self.custom_command_input.setMaximumHeight(100)
|
||||
custom_command_layout.addWidget(self.custom_command_input)
|
||||
|
||||
# 配置sudo免密按钮
|
||||
self.sudo_nopasswd_button = QPushButton("配置sudo免密")
|
||||
self.sudo_nopasswd_button.clicked.connect(self.configure_sudo_nopasswd)
|
||||
system_layout.addWidget(self.sudo_nopasswd_button)
|
||||
# 执行按钮
|
||||
execute_button_layout = QHBoxLayout()
|
||||
self.execute_command_button = QPushButton("执行命令")
|
||||
self.execute_command_button.clicked.connect(self.execute_custom_command)
|
||||
execute_button_layout.addWidget(self.execute_command_button)
|
||||
execute_button_layout.addStretch()
|
||||
custom_command_layout.addLayout(execute_button_layout)
|
||||
|
||||
system_layout.addLayout(custom_command_layout)
|
||||
|
||||
system_group.setLayout(system_layout)
|
||||
main_layout.addWidget(system_group)
|
||||
@@ -519,92 +523,57 @@ class RemoteCommandsTab(QWidget):
|
||||
logger.error(f"刷新目录列表失败: {message}")
|
||||
QMessageBox.warning(self, "错误", f"刷新目录列表失败: {message}")
|
||||
|
||||
def set_timezone(self):
|
||||
logger.info("设置时区为北京时区")
|
||||
def execute_custom_command(self):
|
||||
logger.info("执行自定义命令")
|
||||
|
||||
if not self.ssh_client:
|
||||
QMessageBox.warning(self, "警告", "请先连接到服务器")
|
||||
return
|
||||
|
||||
# 确认操作
|
||||
reply = QMessageBox.question(self, "确认设置时区",
|
||||
"确定要将服务器时区设置为北京时区吗?",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.No:
|
||||
command = self.custom_command_input.toPlainText().strip()
|
||||
if not command:
|
||||
QMessageBox.warning(self, "警告", "请输入要执行的命令")
|
||||
return
|
||||
|
||||
self.output_text.clear()
|
||||
self.status_label.setText("正在设置时区...")
|
||||
self.status_label.setText("正在执行命令...")
|
||||
|
||||
# 如果命令以sudo开头,预先请求密码
|
||||
if command.startswith("sudo "):
|
||||
logger.info("检测到sudo命令,预先请求密码")
|
||||
# 创建密码输入对话框
|
||||
dialog = PasswordDialog(self)
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
password = dialog.get_password()
|
||||
if not password:
|
||||
QMessageBox.warning(self, "警告", "未输入密码,取消执行命令")
|
||||
return
|
||||
else:
|
||||
QMessageBox.warning(self, "警告", "已取消输入密码")
|
||||
return
|
||||
|
||||
# 创建并启动线程执行命令
|
||||
command = "sudo timedatectl set-timezone Asia/Shanghai"
|
||||
self.command_thread = RemoteCommandThread(self.ssh_client, command)
|
||||
self.command_thread.output_signal.connect(self.append_output)
|
||||
self.command_thread.finished_signal.connect(self.on_timezone_set)
|
||||
self.command_thread.finished_signal.connect(self.on_custom_command_finished)
|
||||
self.command_thread.password_request_signal.connect(self.request_password)
|
||||
|
||||
# 如果是sudo命令且已获取密码,预先设置密码
|
||||
if command.startswith("sudo ") and password:
|
||||
self.command_thread.set_password(password)
|
||||
self.output_text.append("密码已设置\n")
|
||||
|
||||
self.command_thread.start()
|
||||
|
||||
def on_timezone_set(self, success, message):
|
||||
def on_custom_command_finished(self, success, message):
|
||||
if success:
|
||||
self.status_label.setText("时区设置成功")
|
||||
self.status_label.setText("命令执行成功")
|
||||
self.status_label.setStyleSheet("color: green;")
|
||||
QMessageBox.information(self, "成功", "服务器时区已设置为北京时区")
|
||||
|
||||
# 显示当前时区
|
||||
self.show_current_timezone()
|
||||
self.output_text.append(f"\n=== 命令执行成功 ===\n{message}")
|
||||
else:
|
||||
self.status_label.setText("时区设置失败")
|
||||
self.status_label.setText("命令执行失败")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
QMessageBox.warning(self, "错误", f"设置时区失败: {message}")
|
||||
|
||||
def show_current_timezone(self):
|
||||
logger.info("显示当前时区")
|
||||
|
||||
if not self.ssh_client:
|
||||
return
|
||||
|
||||
command = "timedatectl status"
|
||||
self.command_thread = RemoteCommandThread(self.ssh_client, command)
|
||||
self.command_thread.output_signal.connect(self.append_output)
|
||||
self.command_thread.finished_signal.connect(lambda success, message: None)
|
||||
self.command_thread.start()
|
||||
|
||||
def reboot_server(self):
|
||||
logger.info("重启服务器")
|
||||
|
||||
if not self.ssh_client:
|
||||
QMessageBox.warning(self, "警告", "请先连接到服务器")
|
||||
return
|
||||
|
||||
# 确认操作
|
||||
reply = QMessageBox.question(self, "确认重启",
|
||||
"确定要重启服务器吗?\n此操作将导致服务器临时不可用!",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.No:
|
||||
return
|
||||
|
||||
self.output_text.clear()
|
||||
self.status_label.setText("正在重启服务器...")
|
||||
|
||||
# 创建并启动线程执行命令
|
||||
command = "sudo reboot"
|
||||
self.command_thread = RemoteCommandThread(self.ssh_client, command)
|
||||
self.command_thread.output_signal.connect(self.append_output)
|
||||
self.command_thread.finished_signal.connect(self.on_reboot_initiated)
|
||||
self.command_thread.password_request_signal.connect(self.request_password)
|
||||
self.command_thread.start()
|
||||
|
||||
def on_reboot_initiated(self, success, message):
|
||||
if success:
|
||||
self.status_label.setText("服务器重启命令已发送")
|
||||
self.status_label.setStyleSheet("color: green;")
|
||||
QMessageBox.information(self, "重启已启动", "服务器重启命令已发送,服务器将在几秒后重启。\n请等待服务器重启完成后重新连接。")
|
||||
else:
|
||||
self.status_label.setText("服务器重启失败")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
QMessageBox.warning(self, "错误", f"服务器重启失败: {message}")
|
||||
self.output_text.append(f"\n=== 命令执行失败 ===\n{message}")
|
||||
|
||||
def delete_directory(self):
|
||||
logger.info("删除目录")
|
||||
@@ -640,42 +609,7 @@ class RemoteCommandsTab(QWidget):
|
||||
self.command_thread.finished_signal.connect(self.on_command_finished)
|
||||
self.command_thread.start()
|
||||
|
||||
def configure_sudo_nopasswd(self):
|
||||
logger.info("配置sudo免密")
|
||||
|
||||
if not self.ssh_client:
|
||||
QMessageBox.warning(self, "警告", "请先连接到服务器")
|
||||
return
|
||||
|
||||
# 确认操作
|
||||
reply = QMessageBox.question(self, "确认配置sudo免密",
|
||||
"确定要配置sudo免密吗?\n此操作将允许当前用户无需密码执行sudo命令,请谨慎操作!",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
|
||||
if reply == QMessageBox.No:
|
||||
return
|
||||
|
||||
self.output_text.clear()
|
||||
self.status_label.setText("正在配置sudo免密...")
|
||||
|
||||
# 创建并启动线程执行命令
|
||||
# 首先获取当前用户名,然后配置sudo免密
|
||||
command = "whoami && echo '\n' | sudo -S visudo -c && (echo '\n' | sudo -S visudo -f /etc/sudoers.d/nopasswd && echo '$(whoami) ALL=(ALL) NOPASSWD: ALL' | sudo -S tee /etc/sudoers.d/nopasswd && sudo -S chmod 440 /etc/sudoers.d/nopasswd)"
|
||||
self.command_thread = RemoteCommandThread(self.ssh_client, command)
|
||||
self.command_thread.output_signal.connect(self.append_output)
|
||||
self.command_thread.finished_signal.connect(self.on_sudo_nopasswd_configured)
|
||||
self.command_thread.password_request_signal.connect(self.request_password)
|
||||
self.command_thread.start()
|
||||
|
||||
def on_sudo_nopasswd_configured(self, success, message):
|
||||
if success:
|
||||
self.status_label.setText("sudo免密配置成功")
|
||||
self.status_label.setStyleSheet("color: green;")
|
||||
QMessageBox.information(self, "成功", "sudo免密配置成功!\n当前用户现在可以无需密码执行sudo命令。")
|
||||
else:
|
||||
self.status_label.setText("sudo免密配置失败")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
QMessageBox.warning(self, "错误", f"sudo免密配置失败: {message}")
|
||||
|
||||
|
||||
def check_ssh_connection(self):
|
||||
"""检查SSH连接是否有效"""
|
||||
|
||||
Reference in New Issue
Block a user