451 lines
17 KiB
Python
451 lines
17 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
汇总记录提交工具 - PySide6 GUI版本
|
|||
|
|
用于向家庭日报系统提交汇总记录
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import sys
|
|||
|
|
import socket
|
|||
|
|
import psutil
|
|||
|
|
import requests
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
from datetime import datetime
|
|||
|
|
from PySide6.QtWidgets import (
|
|||
|
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|||
|
|
QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QFormLayout,
|
|||
|
|
QMessageBox, QSystemTrayIcon, QMenu, QStatusBar, QProgressBar,
|
|||
|
|
QSpinBox, QComboBox
|
|||
|
|
)
|
|||
|
|
from PySide6.QtCore import QTimer, Qt, QSettings
|
|||
|
|
from PySide6.QtGui import QIcon, QAction
|
|||
|
|
|
|||
|
|
|
|||
|
|
class SubmitSummaryTool(QMainWindow):
|
|||
|
|
"""汇总记录提交工具主窗口"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__()
|
|||
|
|
self.setWindowTitle("汇总记录提交工具")
|
|||
|
|
self.setMinimumSize(600, 500)
|
|||
|
|
self.resize(700, 550)
|
|||
|
|
|
|||
|
|
self.settings = QSettings("DiaryFamily", "SubmitSummaryTool")
|
|||
|
|
self.load_settings()
|
|||
|
|
|
|||
|
|
self.init_ui()
|
|||
|
|
self.setup_tray()
|
|||
|
|
self.start_auto_refresh()
|
|||
|
|
|
|||
|
|
def load_settings(self):
|
|||
|
|
"""加载保存的设置"""
|
|||
|
|
self.server_url = self.settings.value("server_url", "http://localhost:8000")
|
|||
|
|
self.auto_submit = self.settings.value("auto_submit", False, type=bool)
|
|||
|
|
self.submit_interval = self.settings.value("submit_interval", 60, type=int)
|
|||
|
|
self.auto_start = self.settings.value("auto_start", True, type=bool)
|
|||
|
|
|
|||
|
|
def save_settings(self):
|
|||
|
|
"""保存设置"""
|
|||
|
|
self.settings.setValue("server_url", self.server_url)
|
|||
|
|
self.settings.setValue("auto_submit", self.auto_submit)
|
|||
|
|
self.settings.setValue("submit_interval", self.submit_interval)
|
|||
|
|
self.settings.setValue("auto_start", self.auto_start)
|
|||
|
|
|
|||
|
|
def init_ui(self):
|
|||
|
|
"""初始化UI"""
|
|||
|
|
central_widget = QWidget()
|
|||
|
|
self.setCentralWidget(central_widget)
|
|||
|
|
main_layout = QVBoxLayout(central_widget)
|
|||
|
|
main_layout.setSpacing(10)
|
|||
|
|
|
|||
|
|
self.create_server_config_group(main_layout)
|
|||
|
|
self.create_system_info_group(main_layout)
|
|||
|
|
self.create_manual_submit_group(main_layout)
|
|||
|
|
self.create_log_group(main_layout)
|
|||
|
|
self.create_status_bar()
|
|||
|
|
self.create_actions()
|
|||
|
|
|
|||
|
|
self.statusBar().showMessage("就绪")
|
|||
|
|
self.refresh_system_info()
|
|||
|
|
|
|||
|
|
def create_server_config_group(self, parent_layout):
|
|||
|
|
"""服务器配置组"""
|
|||
|
|
group = QGroupBox("服务器配置")
|
|||
|
|
layout = QFormLayout()
|
|||
|
|
layout.setSpacing(10)
|
|||
|
|
|
|||
|
|
self.server_url_edit = QLineEdit()
|
|||
|
|
self.server_url_edit.setPlaceholderText("http://your-server:8000")
|
|||
|
|
self.server_url_edit.setText(self.server_url)
|
|||
|
|
self.server_url_edit.textChanged.connect(self.on_url_changed)
|
|||
|
|
|
|||
|
|
self.auto_submit_check = QPushButton("自动提交")
|
|||
|
|
self.auto_submit_check.setCheckable(True)
|
|||
|
|
self.auto_submit_check.setChecked(self.auto_submit)
|
|||
|
|
self.auto_submit_check.toggled.connect(self.on_auto_submit_toggled)
|
|||
|
|
|
|||
|
|
self.interval_spin = QSpinBox()
|
|||
|
|
self.interval_spin.setRange(10, 3600)
|
|||
|
|
self.interval_spin.setValue(self.submit_interval)
|
|||
|
|
self.interval_spin.setSuffix(" 秒")
|
|||
|
|
self.interval_spin.valueChanged.connect(self.on_interval_changed)
|
|||
|
|
|
|||
|
|
self.test_connection_btn = QPushButton("测试连接")
|
|||
|
|
self.test_connection_btn.clicked.connect(self.test_connection)
|
|||
|
|
|
|||
|
|
interval_layout = QHBoxLayout()
|
|||
|
|
interval_layout.addWidget(self.auto_submit_check)
|
|||
|
|
interval_layout.addWidget(self.interval_spin)
|
|||
|
|
interval_layout.addStretch()
|
|||
|
|
|
|||
|
|
layout.addRow("服务器地址:", self.server_url_edit)
|
|||
|
|
layout.addRow("自动提交:", interval_layout)
|
|||
|
|
layout.addRow("", self.test_connection_btn)
|
|||
|
|
|
|||
|
|
group.setLayout(layout)
|
|||
|
|
parent_layout.addWidget(group)
|
|||
|
|
|
|||
|
|
def create_system_info_group(self, parent_layout):
|
|||
|
|
"""系统信息组"""
|
|||
|
|
group = QGroupBox("系统监控信息")
|
|||
|
|
layout = QFormLayout()
|
|||
|
|
layout.setSpacing(8)
|
|||
|
|
|
|||
|
|
self.cpu_label = QLabel("0%")
|
|||
|
|
self.memory_label = QLabel("0%")
|
|||
|
|
self.disk_label = QLabel("0%")
|
|||
|
|
self.hostname_label = QLabel()
|
|||
|
|
self.ip_label = QLabel()
|
|||
|
|
self.timestamp_label = QLabel()
|
|||
|
|
|
|||
|
|
self.cpu_progress = QProgressBar()
|
|||
|
|
self.cpu_progress.setRange(0, 100)
|
|||
|
|
self.memory_progress = QProgressBar()
|
|||
|
|
self.memory_progress.setRange(0, 100)
|
|||
|
|
self.disk_progress = QProgressBar()
|
|||
|
|
self.disk_progress.setRange(0, 100)
|
|||
|
|
|
|||
|
|
layout.addRow("CPU使用率:", self.cpu_progress)
|
|||
|
|
layout.addRow("内存使用率:", self.memory_progress)
|
|||
|
|
layout.addRow("磁盘使用率:", self.disk_progress)
|
|||
|
|
layout.addRow("主机名:", self.hostname_label)
|
|||
|
|
layout.addRow("IP地址:", self.ip_label)
|
|||
|
|
layout.addRow("更新时间:", self.timestamp_label)
|
|||
|
|
|
|||
|
|
refresh_btn = QPushButton("刷新")
|
|||
|
|
refresh_btn.clicked.connect(self.refresh_system_info)
|
|||
|
|
|
|||
|
|
btn_layout = QHBoxLayout()
|
|||
|
|
btn_layout.addWidget(refresh_btn)
|
|||
|
|
btn_layout.addStretch()
|
|||
|
|
layout.addRow("", btn_layout)
|
|||
|
|
|
|||
|
|
group.setLayout(layout)
|
|||
|
|
parent_layout.addWidget(group)
|
|||
|
|
|
|||
|
|
def create_manual_submit_group(self, parent_layout):
|
|||
|
|
"""手动提交组"""
|
|||
|
|
group = QGroupBox("手动提交")
|
|||
|
|
layout = QVBoxLayout()
|
|||
|
|
layout.setSpacing(10)
|
|||
|
|
|
|||
|
|
self.content_edit = QTextEdit()
|
|||
|
|
self.content_edit.setPlaceholderText("输入要提交的内容...")
|
|||
|
|
self.content_edit.setMaximumHeight(100)
|
|||
|
|
|
|||
|
|
submit_btn = QPushButton("提交汇总记录")
|
|||
|
|
submit_btn.clicked.connect(self.manual_submit)
|
|||
|
|
|
|||
|
|
layout.addWidget(self.content_edit)
|
|||
|
|
layout.addWidget(submit_btn)
|
|||
|
|
|
|||
|
|
group.setLayout(layout)
|
|||
|
|
parent_layout.addWidget(group)
|
|||
|
|
|
|||
|
|
def create_log_group(self, parent_layout):
|
|||
|
|
"""日志组"""
|
|||
|
|
group = QGroupBox("运行日志")
|
|||
|
|
layout = QVBoxLayout()
|
|||
|
|
layout.setSpacing(5)
|
|||
|
|
|
|||
|
|
self.log_edit = QTextEdit()
|
|||
|
|
self.log_edit.setReadOnly(True)
|
|||
|
|
self.log_edit.setMaximumHeight(150)
|
|||
|
|
|
|||
|
|
clear_btn = QPushButton("清除日志")
|
|||
|
|
clear_btn.clicked.connect(self.log_edit.clear)
|
|||
|
|
|
|||
|
|
layout.addWidget(self.log_edit)
|
|||
|
|
layout.addWidget(clear_btn, alignment=Qt.AlignRight)
|
|||
|
|
|
|||
|
|
group.setLayout(layout)
|
|||
|
|
parent_layout.addWidget(group)
|
|||
|
|
|
|||
|
|
def create_status_bar(self):
|
|||
|
|
"""创建状态栏"""
|
|||
|
|
self.status_label = QLabel("就绪")
|
|||
|
|
self.statusBar().addPermanentWidget(self.status_label)
|
|||
|
|
|
|||
|
|
def create_actions(self):
|
|||
|
|
"""创建菜单动作"""
|
|||
|
|
self.refresh_action = QAction("刷新", self)
|
|||
|
|
self.refresh_action.triggered.connect(self.refresh_system_info)
|
|||
|
|
|
|||
|
|
self.submit_action = QAction("提交", self)
|
|||
|
|
self.submit_action.triggered.connect(self.manual_submit)
|
|||
|
|
|
|||
|
|
self.quit_action = QAction("退出", self)
|
|||
|
|
self.quit_action.triggered.connect(self.close)
|
|||
|
|
|
|||
|
|
def setup_tray(self):
|
|||
|
|
"""设置系统托盘"""
|
|||
|
|
if QSystemTrayIcon.isSystemTrayAvailable():
|
|||
|
|
self.tray_icon = QSystemTrayIcon(self)
|
|||
|
|
self.tray_icon.setToolTip("汇总记录提交工具")
|
|||
|
|
|
|||
|
|
tray_menu = QMenu()
|
|||
|
|
tray_menu.addAction(self.refresh_action)
|
|||
|
|
tray_menu.addAction(self.submit_action)
|
|||
|
|
tray_menu.addSeparator()
|
|||
|
|
tray_menu.addAction(self.quit_action)
|
|||
|
|
|
|||
|
|
self.tray_icon.setContextMenu(tray_menu)
|
|||
|
|
self.tray_icon.show()
|
|||
|
|
|
|||
|
|
self.tray_icon.activated.connect(self.on_tray_activated)
|
|||
|
|
|
|||
|
|
def on_tray_activated(self, reason):
|
|||
|
|
"""托盘图标激活处理"""
|
|||
|
|
if reason == QSystemTrayIcon.DoubleClick:
|
|||
|
|
self.show()
|
|||
|
|
self.raise_()
|
|||
|
|
self.activateWindow()
|
|||
|
|
|
|||
|
|
def start_auto_refresh(self):
|
|||
|
|
"""启动自动刷新"""
|
|||
|
|
self.timer = QTimer(self)
|
|||
|
|
self.timer.timeout.connect(self.auto_refresh_cycle)
|
|||
|
|
self.timer.start(self.submit_interval * 1000 if self.auto_submit else 5000)
|
|||
|
|
|
|||
|
|
def auto_refresh_cycle(self):
|
|||
|
|
"""自动刷新周期"""
|
|||
|
|
self.refresh_system_info()
|
|||
|
|
if self.auto_submit:
|
|||
|
|
self.auto_submit_content()
|
|||
|
|
|
|||
|
|
def refresh_system_info(self):
|
|||
|
|
"""刷新系统信息"""
|
|||
|
|
try:
|
|||
|
|
cpu = psutil.cpu_percent(interval=None)
|
|||
|
|
memory = psutil.virtual_memory()
|
|||
|
|
disk = psutil.disk_usage('/')
|
|||
|
|
hostname = socket.gethostname()
|
|||
|
|
ip = socket.gethostbyname(hostname)
|
|||
|
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
|
|||
|
|
self.cpu_progress.setValue(int(cpu))
|
|||
|
|
self.memory_progress.setValue(int(memory.percent))
|
|||
|
|
self.disk_progress.setValue(int(disk.percent))
|
|||
|
|
|
|||
|
|
self.hostname_label.setText(hostname)
|
|||
|
|
self.ip_label.setText(ip)
|
|||
|
|
self.timestamp_label.setText(timestamp)
|
|||
|
|
|
|||
|
|
self.log(f"系统信息已刷新 - CPU: {cpu}%, 内存: {memory.percent}%, 磁盘: {disk.percent}%")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.log(f"获取系统信息失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def get_system_content(self):
|
|||
|
|
"""获取系统监控内容"""
|
|||
|
|
info = self.get_system_info()
|
|||
|
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
return f"[系统监控 {timestamp}] CPU使用率: {info['cpu']}%, 内存使用率: {info['memory']}%, 磁盘使用率: {info['disk']}%"
|
|||
|
|
|
|||
|
|
def auto_submit_content(self):
|
|||
|
|
"""自动提交系统监控内容"""
|
|||
|
|
content = self.get_system_content()
|
|||
|
|
self.submit_summary(content)
|
|||
|
|
|
|||
|
|
def manual_submit(self):
|
|||
|
|
"""手动提交"""
|
|||
|
|
content = self.content_edit.toPlainText().strip()
|
|||
|
|
if not content:
|
|||
|
|
QMessageBox.warning(self, "警告", "请输入要提交的内容")
|
|||
|
|
return
|
|||
|
|
self.submit_summary(content)
|
|||
|
|
self.content_edit.clear()
|
|||
|
|
|
|||
|
|
def get_csrf_token(self):
|
|||
|
|
"""获取CSRF token"""
|
|||
|
|
try:
|
|||
|
|
session = requests.Session()
|
|||
|
|
get_url = f"{self.server_url}/"
|
|||
|
|
self.log(f"正在访问首页获取CSRF token: {get_url}")
|
|||
|
|
response = session.get(get_url, timeout=10)
|
|||
|
|
self.log(f"首页响应状态码: {response.status_code}")
|
|||
|
|
|
|||
|
|
cookies = session.cookies.get_dict()
|
|||
|
|
self.log(f"收到的Cookies: {list(cookies.keys())}")
|
|||
|
|
|
|||
|
|
csrf_token = cookies.get('csrftoken')
|
|||
|
|
if csrf_token:
|
|||
|
|
self.log(f"获取CSRF token成功: {csrf_token[:20]}...")
|
|||
|
|
return session, csrf_token
|
|||
|
|
else:
|
|||
|
|
self.log("警告: 未获取到CSRF token,cookie中可能没有csrftoken")
|
|||
|
|
self.log(f"所有cookies: {cookies}")
|
|||
|
|
return session, None
|
|||
|
|
except Exception as e:
|
|||
|
|
self.log(f"获取CSRF token失败: {str(e)}")
|
|||
|
|
return None, None
|
|||
|
|
|
|||
|
|
def submit_summary(self, content):
|
|||
|
|
"""提交汇总记录到家庭日报系统"""
|
|||
|
|
submit_url = f"{self.server_url}/api/v1/summary/submit/"
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
self.status_label.setText("正在提交...")
|
|||
|
|
|
|||
|
|
session, csrf_token = self.get_csrf_token()
|
|||
|
|
if session is None:
|
|||
|
|
self.log("错误: 无法建立会话,连接可能失败")
|
|||
|
|
self.status_label.setText("连接失败")
|
|||
|
|
QMessageBox.critical(self, "错误", "无法连接到服务器,请检查URL是否正确")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
headers = {
|
|||
|
|
'Referer': self.server_url,
|
|||
|
|
}
|
|||
|
|
if csrf_token:
|
|||
|
|
headers['X-CSRFToken'] = csrf_token
|
|||
|
|
self.log(f"设置CSRF header: X-CSRFToken = {csrf_token[:20]}...")
|
|||
|
|
|
|||
|
|
self.log(f"发送POST请求到: {submit_url}")
|
|||
|
|
response = session.post(submit_url, data={'content': content}, headers=headers, timeout=10)
|
|||
|
|
|
|||
|
|
self.log(f"服务器响应状态码: {response.status_code}")
|
|||
|
|
self.log(f"响应Headers: {dict(response.headers)}")
|
|||
|
|
self.log(f"响应内容: {response.text[:200]}...")
|
|||
|
|
|
|||
|
|
if response.status_code != 200:
|
|||
|
|
error_msg = f"服务器返回错误: HTTP {response.status_code}"
|
|||
|
|
self.log(error_msg)
|
|||
|
|
self.status_label.setText(f"错误: {response.status_code}")
|
|||
|
|
QMessageBox.critical(self, "错误", f"{error_msg}\n\n响应内容:\n{response.text[:500]}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
result = response.json()
|
|||
|
|
except json.JSONDecodeError as e:
|
|||
|
|
error_msg = f"JSON解析失败: {str(e)}\n响应内容: {response.text[:200]}"
|
|||
|
|
self.log(error_msg)
|
|||
|
|
self.status_label.setText("JSON解析失败")
|
|||
|
|
QMessageBox.critical(self, "错误", error_msg)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if result['success']:
|
|||
|
|
self.log(f"提交成功,记录ID: {result['id']}")
|
|||
|
|
self.status_label.setText("提交成功")
|
|||
|
|
QMessageBox.information(self, "成功", f"提交成功,记录ID: {result['id']}")
|
|||
|
|
else:
|
|||
|
|
self.log(f"提交失败: {result['message']}")
|
|||
|
|
self.status_label.setText(f"失败: {result['message']}")
|
|||
|
|
QMessageBox.warning(self, "失败", f"提交失败: {result['message']}")
|
|||
|
|
except requests.exceptions.ConnectionError as e:
|
|||
|
|
error_msg = f"连接失败: 无法连接到 {submit_url}"
|
|||
|
|
self.log(error_msg)
|
|||
|
|
self.status_label.setText("连接失败")
|
|||
|
|
QMessageBox.critical(self, "错误", error_msg)
|
|||
|
|
except requests.exceptions.Timeout:
|
|||
|
|
error_msg = "请求超时,请检查服务器是否正常运行"
|
|||
|
|
self.log(error_msg)
|
|||
|
|
self.status_label.setText("连接超时")
|
|||
|
|
QMessageBox.critical(self, "错误", error_msg)
|
|||
|
|
except Exception as e:
|
|||
|
|
error_msg = f"请求异常: {str(e)}"
|
|||
|
|
self.log(error_msg)
|
|||
|
|
self.status_label.setText("请求异常")
|
|||
|
|
QMessageBox.critical(self, "错误", error_msg)
|
|||
|
|
|
|||
|
|
def test_connection(self):
|
|||
|
|
"""测试服务器连接"""
|
|||
|
|
url = self.server_url.rstrip('/') + "/api/v1/summary/submit/"
|
|||
|
|
try:
|
|||
|
|
self.status_label.setText("测试连接中...")
|
|||
|
|
response = requests.get(url.replace('/submit/', '/'), timeout=5)
|
|||
|
|
self.log(f"连接测试: 服务器响应状态码 {response.status_code}")
|
|||
|
|
self.status_label.setText("连接成功")
|
|||
|
|
QMessageBox.information(self, "成功", "服务器连接成功")
|
|||
|
|
except requests.exceptions.ConnectionError:
|
|||
|
|
self.log("连接测试: 无法连接到服务器")
|
|||
|
|
self.status_label.setText("连接失败")
|
|||
|
|
QMessageBox.warning(self, "失败", "无法连接到服务器,请检查URL是否正确")
|
|||
|
|
except Exception as e:
|
|||
|
|
self.log(f"连接测试: {str(e)}")
|
|||
|
|
self.status_label.setText("测试异常")
|
|||
|
|
QMessageBox.critical(self, "错误", f"测试失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def get_system_info(self):
|
|||
|
|
"""获取系统基本信息"""
|
|||
|
|
try:
|
|||
|
|
cpu_percent = psutil.cpu_percent(interval=1)
|
|||
|
|
memory = psutil.virtual_memory()
|
|||
|
|
disk = psutil.disk_usage('/')
|
|||
|
|
return {
|
|||
|
|
'cpu': cpu_percent,
|
|||
|
|
'memory': memory.percent,
|
|||
|
|
'disk': disk.percent
|
|||
|
|
}
|
|||
|
|
except Exception as e:
|
|||
|
|
self.log(f"获取系统信息失败: {str(e)}")
|
|||
|
|
return {'cpu': 0, 'memory': 0, 'disk': 0}
|
|||
|
|
|
|||
|
|
def log(self, message):
|
|||
|
|
"""记录日志"""
|
|||
|
|
timestamp = datetime.now().strftime('%H:%M:%S')
|
|||
|
|
log_message = f"[{timestamp}] {message}"
|
|||
|
|
self.log_edit.append(log_message)
|
|||
|
|
print(log_message)
|
|||
|
|
|
|||
|
|
def on_url_changed(self, url):
|
|||
|
|
"""URL改变处理"""
|
|||
|
|
self.server_url = url
|
|||
|
|
|
|||
|
|
def on_auto_submit_toggled(self, checked):
|
|||
|
|
"""自动提交开关处理"""
|
|||
|
|
self.auto_submit = checked
|
|||
|
|
if checked:
|
|||
|
|
self.log("已启用自动提交")
|
|||
|
|
else:
|
|||
|
|
self.log("已禁用自动提交")
|
|||
|
|
|
|||
|
|
def on_interval_changed(self, value):
|
|||
|
|
"""刷新间隔改变处理"""
|
|||
|
|
self.submit_interval = value
|
|||
|
|
self.timer.setInterval(value * 1000 if self.auto_submit else 5000)
|
|||
|
|
self.log(f"刷新间隔已设置为 {value} 秒")
|
|||
|
|
|
|||
|
|
def closeEvent(self, event):
|
|||
|
|
"""关闭窗口事件"""
|
|||
|
|
self.save_settings()
|
|||
|
|
self.log("正在保存设置...")
|
|||
|
|
event.accept()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
app = QApplication(sys.argv)
|
|||
|
|
app.setStyle('Fusion')
|
|||
|
|
|
|||
|
|
window = SubmitSummaryTool()
|
|||
|
|
|
|||
|
|
if window.auto_start:
|
|||
|
|
window.show()
|
|||
|
|
else:
|
|||
|
|
window.showMinimized()
|
|||
|
|
|
|||
|
|
sys.exit(app.exec())
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|