完善nginx标签。使用unix套接字。增加远程命令的运行和显示。

This commit is contained in:
2025-08-31 22:16:45 +08:00
parent 47f3669dc4
commit 14e69c2bfd
7 changed files with 1561 additions and 161 deletions

Binary file not shown.

1171
app.log

File diff suppressed because it is too large Load Diff

View File

@@ -436,6 +436,116 @@ class GunicornLogThread(QThread):
self.result_ready.emit(False, error_msg) self.result_ready.emit(False, error_msg)
logger.error(f"Gunicorn服务日志查看异常: {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): class ServerControlThread(QThread):
"""控制服务器设置的线程""" """控制服务器设置的线程"""
result_ready = Signal(bool, str) result_ready = Signal(bool, str)
@@ -578,6 +688,11 @@ class GunicornTab(QWidget):
self.view_logs_btn.clicked.connect(self.view_service_logs) self.view_logs_btn.clicked.connect(self.view_service_logs)
service_btn_layout.addWidget(self.view_logs_btn) 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) service_layout.addLayout(service_btn_layout)
layout.addLayout(service_layout) layout.addLayout(service_layout)
@@ -628,27 +743,19 @@ class GunicornTab(QWidget):
def init_service_content(self): def init_service_content(self):
"""初始化服务文件内容""" """初始化服务文件内容"""
default_content = "[Unit]\n" 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 += "After=network.target\n\n"
default_content += "[Service]\n" default_content += "[Service]\n"
default_content += "User=【用户名】\n" default_content += "# 以xiaji用户运行确保对/home/xiaji有完全权限\n"
default_content += "Group=【用户名】\n" default_content += "User=xiaji\n"
default_content += "WorkingDirectory=【Django路径】\n" default_content += "Group=xiaji\n"
default_content += "# 所有Gunicorn参数直接在这里配置\n" default_content += "# 项目工作目录\n"
default_content += "ExecStart=/usr/bin/gunicorn \\ \n" default_content += "WorkingDirectory=/home/xiaji/webstatus\n\n"
default_content += " --pythonpath 【Django路径】 \\ \n" default_content += "# 启动前预处理先删除旧socket避免残留再创建目录如果不存在\n"
default_content += " --bind 127.0.0.1:8000 \\ \n" default_content += "ExecStartPre=/bin/rm -f /home/xiaji/webstatus/sock/gunicorn.sock\n"
default_content += " --workers $(nproc) \\ \n" default_content += "ExecStartPre=/bin/mkdir -p /home/xiaji/webstatus/sock\n\n"
default_content += " --worker-class sync \\ \n" default_content += "# 单行ExecStart无反斜杠避免格式错误\n"
default_content += " --timeout 60 \\ \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 += " --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 += "[Install]\n" default_content += "[Install]\n"
default_content += "WantedBy=multi-user.target" default_content += "WantedBy=multi-user.target"
@@ -964,6 +1071,55 @@ class GunicornTab(QWidget):
logger.error(f"Gunicorn服务日志查看失败: {message}") logger.error(f"Gunicorn服务日志查看失败: {message}")
QMessageBox.warning(self, "错误", 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): def on_control_result(self, success, message):
"""处理控制结果""" """处理控制结果"""
if success: if success:

View File

@@ -1,5 +1,6 @@
import os import os
import sys import sys
import json
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QTextEdit, QFileDialog, QMessageBox, QLabel, QTextEdit, QFileDialog, QMessageBox,
QLineEdit, QDialog, QDialogButtonBox) QLineEdit, QDialog, QDialogButtonBox)
@@ -202,6 +203,51 @@ class NginxControlThread(QThread):
self.result_ready.emit(False, error_msg) self.result_ready.emit(False, error_msg)
logger.error(f"Nginx服务控制异常: {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): class NginxSiteThread(QThread):
"""处理Nginx站点配置的线程""" """处理Nginx站点配置的线程"""
result_ready = Signal(bool, str) result_ready = Signal(bool, str)
@@ -250,6 +296,24 @@ class NginxSiteThread(QThread):
elif self.operation == "enable": 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/'" 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) stdin, stdout, stderr = self.ssh_client.exec_command(enable_cmd)
exit_status = stdout.channel.recv_exit_status() 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) self.check_nginx_status_btn.clicked.connect(self.check_nginx_status)
control_layout.addWidget(self.check_nginx_status_btn) 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) layout.addLayout(control_layout)
# 输出区域 # 输出区域
@@ -783,3 +852,73 @@ http {
self.append_output(f"操作失败: {message}") self.append_output(f"操作失败: {message}")
logger.error(f"Nginx服务控制失败: {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}")

View File

@@ -79,27 +79,33 @@ class RemoteCommandThread(QThread):
command_with_sudo = self.command.replace("sudo", "sudo -S") command_with_sudo = self.command.replace("sudo", "sudo -S")
stdin, stdout, stderr = self.ssh_client.exec_command(command_with_sudo) stdin, stdout, stderr = self.ssh_client.exec_command(command_with_sudo)
# 检查是否需要密码 # 如果预先设置了密码,直接发送
password_prompt = False if self.password:
for line in stderr: logger.info("使用预先设置的密码")
self.output_signal.emit(line) stdin.write(self.password + "\n")
if "password for" in line.lower() or "密码" in line: stdin.flush()
password_prompt = True else:
break # 检查是否需要密码
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: if password_prompt:
self.waiting_for_password = True self.waiting_for_password = True
self.password_request_signal.emit() self.password_request_signal.emit()
# 等待密码输入 # 等待密码输入
while self.waiting_for_password: while self.waiting_for_password:
self.msleep(100) self.msleep(100)
# 发送密码 # 发送密码
if self.password: if self.password:
stdin.write(self.password + "\n") stdin.write(self.password + "\n")
stdin.flush() stdin.flush()
else: else:
stdin, stdout, stderr = self.ssh_client.exec_command(self.command) stdin, stdout, stderr = self.ssh_client.exec_command(self.command)
@@ -194,26 +200,24 @@ class RemoteCommandsTab(QWidget):
system_group = QGroupBox("系统管理") system_group = QGroupBox("系统管理")
system_layout = QVBoxLayout() system_layout = QVBoxLayout()
# 时区设置 # 自定义命令执行区域
timezone_layout = QHBoxLayout() custom_command_layout = QVBoxLayout()
self.set_timezone_button = QPushButton("设置时区为北京时区") custom_command_layout.addWidget(QLabel("自定义命令:"))
self.set_timezone_button.clicked.connect(self.set_timezone)
timezone_layout.addWidget(self.set_timezone_button)
timezone_layout.addStretch()
system_layout.addLayout(timezone_layout)
# 服务器重启 # 命令输入框
reboot_layout = QHBoxLayout() self.custom_command_input = QTextEdit()
self.reboot_button = QPushButton("重启服务器") self.custom_command_input.setMaximumHeight(100)
self.reboot_button.clicked.connect(self.reboot_server) custom_command_layout.addWidget(self.custom_command_input)
reboot_layout.addWidget(self.reboot_button)
reboot_layout.addStretch()
system_layout.addLayout(reboot_layout)
# 配置sudo免密按钮 # 执行按钮
self.sudo_nopasswd_button = QPushButton("配置sudo免密") execute_button_layout = QHBoxLayout()
self.sudo_nopasswd_button.clicked.connect(self.configure_sudo_nopasswd) self.execute_command_button = QPushButton("执行命令")
system_layout.addWidget(self.sudo_nopasswd_button) 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) system_group.setLayout(system_layout)
main_layout.addWidget(system_group) main_layout.addWidget(system_group)
@@ -519,92 +523,57 @@ class RemoteCommandsTab(QWidget):
logger.error(f"刷新目录列表失败: {message}") logger.error(f"刷新目录列表失败: {message}")
QMessageBox.warning(self, "错误", f"刷新目录列表失败: {message}") QMessageBox.warning(self, "错误", f"刷新目录列表失败: {message}")
def set_timezone(self): def execute_custom_command(self):
logger.info("设置时区为北京时区") logger.info("执行自定义命令")
if not self.ssh_client: if not self.ssh_client:
QMessageBox.warning(self, "警告", "请先连接到服务器") QMessageBox.warning(self, "警告", "请先连接到服务器")
return return
# 确认操作 command = self.custom_command_input.toPlainText().strip()
reply = QMessageBox.question(self, "确认设置时区", if not command:
"确定要将服务器时区设置为北京时区吗?", QMessageBox.warning(self, "警告", "请输入要执行的命令")
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.No:
return return
self.output_text.clear() 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 = RemoteCommandThread(self.ssh_client, command)
self.command_thread.output_signal.connect(self.append_output) 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) 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() self.command_thread.start()
def on_timezone_set(self, success, message): def on_custom_command_finished(self, success, message):
if success: if success:
self.status_label.setText("时区设置成功") self.status_label.setText("命令执行成功")
self.status_label.setStyleSheet("color: green;") self.status_label.setStyleSheet("color: green;")
QMessageBox.information(self, "成功", "服务器时区已设置为北京时区") self.output_text.append(f"\n=== 命令执行成功 ===\n{message}")
# 显示当前时区
self.show_current_timezone()
else: else:
self.status_label.setText("时区设置失败") self.status_label.setText("命令执行失败")
self.status_label.setStyleSheet("color: red;") self.status_label.setStyleSheet("color: red;")
QMessageBox.warning(self, "错误", f"设置时区失败: {message}") self.output_text.append(f"\n=== 命令执行失败 ===\n{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}")
def delete_directory(self): def delete_directory(self):
logger.info("删除目录") logger.info("删除目录")
@@ -640,42 +609,7 @@ class RemoteCommandsTab(QWidget):
self.command_thread.finished_signal.connect(self.on_command_finished) self.command_thread.finished_signal.connect(self.on_command_finished)
self.command_thread.start() 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): def check_ssh_connection(self):
"""检查SSH连接是否有效""" """检查SSH连接是否有效"""