Files
central-task/client/windows-client.py

568 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys
import json
import requests
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
QGroupBox, QLabel, QLineEdit, QPushButton, QTextEdit, QTableWidget,
QTableWidgetItem, QComboBox, QSpinBox, QFileDialog, QMessageBox,
QStatusBar, QHeaderView, QFormLayout, QCheckBox
)
from PySide6.QtCore import Qt, QTimer, QThread, Signal
from PySide6.QtGui import QIcon
# API通信类
class ApiClient:
def __init__(self):
self.base_url = "http://127.0.0.1:8000/api"
self.token = ""
def set_server(self, base_url):
self.base_url = base_url.rstrip('/')
def set_token(self, token):
self.token = token
def _get_headers(self):
return {
"Authorization": f"Token {self.token}",
"Content-Type": "application/json"
}
def get_tasks(self):
try:
response = requests.get(f"{self.base_url}/tasks/", headers=self._get_headers())
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"获取任务失败: {str(e)}")
def create_task(self, name, client_name, script, timeout_seconds):
try:
data = {
"name": name,
"client_name": client_name or None,
"script": script or None,
"timeout_seconds": timeout_seconds
}
response = requests.post(f"{self.base_url}/tasks/", headers=self._get_headers(), json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"创建任务失败: {str(e)}")
def claim_task(self, client_name):
try:
data = {"client_name": client_name}
response = requests.post(f"{self.base_url}/tasks/claim/", headers=self._get_headers(), json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"认领任务失败: {str(e)}")
def start_task(self, task_id):
try:
response = requests.post(f"{self.base_url}/tasks/{task_id}/start/", headers=self._get_headers())
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"开始任务失败: {str(e)}")
def complete_task(self, task_id, status, message):
try:
data = {
"status": status,
"message": message
}
response = requests.post(f"{self.base_url}/tasks/{task_id}/complete/", headers=self._get_headers(), json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"完成任务失败: {str(e)}")
def upload_result(self, task_id, client_id, status, message, result_file=None):
try:
headers = {
"Authorization": f"Token {self.token}"
}
data = {
"task": task_id,
"client": client_id,
"status": status,
"message": message
}
files = {}
if result_file:
files["result_file"] = open(result_file, "rb")
response = requests.post(f"{self.base_url}/task_results/", headers=headers, data=data, files=files)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"上传结果失败: {str(e)}")
# 任务列表更新线程
class TaskUpdateThread(QThread):
update_signal = Signal(list)
error_signal = Signal(str)
def __init__(self, api_client):
super().__init__()
self.api_client = api_client
self.running = True
def run(self):
while self.running:
try:
tasks = self.api_client.get_tasks()
self.update_signal.emit(tasks)
except Exception as e:
self.error_signal.emit(str(e))
self.sleep(5) # 每5秒更新一次
def stop(self):
self.running = False
self.wait()
# 主应用窗口
class TaskCenterClient(QMainWindow):
def __init__(self):
super().__init__()
self.api_client = ApiClient()
self.task_update_thread = None
self.init_ui()
def init_ui(self):
self.setWindowTitle("任务中心客户端")
self.setGeometry(100, 100, 1000, 700)
# 创建中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout(central_widget)
# 创建标签页
self.tabs = QTabWidget()
main_layout.addWidget(self.tabs)
# 添加配置标签页
self.create_config_tab()
# 添加任务列表标签页
self.create_task_list_tab()
# 添加任务创建标签页
self.create_task_create_tab()
# 添加任务操作标签页
self.create_task_operation_tab()
# 创建状态栏
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("已连接到服务器")
def create_config_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
# 服务器配置
server_group = QGroupBox("服务器配置")
server_layout = QFormLayout()
self.server_url_edit = QLineEdit("http://127.0.0.1:8000/api")
server_layout.addRow(QLabel("服务器地址:"), self.server_url_edit)
self.token_edit = QLineEdit()
self.token_edit.setEchoMode(QLineEdit.Password)
server_layout.addRow(QLabel("API Token:"), self.token_edit)
# 显示/隐藏Token按钮
self.token_visibility_btn = QPushButton("显示")
self.token_visibility_btn.clicked.connect(self.toggle_token_visibility)
server_layout.addRow(QLabel(" "), self.token_visibility_btn)
self.save_config_btn = QPushButton("保存配置")
self.save_config_btn.clicked.connect(self.save_config)
server_layout.addRow(QLabel(" "), self.save_config_btn)
server_group.setLayout(server_layout)
layout.addWidget(server_group)
# 测试连接按钮
self.test_connection_btn = QPushButton("测试连接")
self.test_connection_btn.clicked.connect(self.test_connection)
layout.addWidget(self.test_connection_btn)
# 连接状态
self.connection_status = QLabel("未连接")
self.connection_status.setStyleSheet("color: red;")
layout.addWidget(self.connection_status, alignment=Qt.AlignCenter)
self.tabs.addTab(tab, "配置")
def create_task_list_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
# 刷新按钮
refresh_layout = QHBoxLayout()
self.refresh_tasks_btn = QPushButton("刷新任务列表")
self.refresh_tasks_btn.clicked.connect(self.refresh_tasks)
refresh_layout.addWidget(self.refresh_tasks_btn)
self.auto_refresh_checkbox = QCheckBox("自动刷新")
self.auto_refresh_checkbox.stateChanged.connect(self.toggle_auto_refresh)
refresh_layout.addWidget(self.auto_refresh_checkbox)
layout.addLayout(refresh_layout)
# 任务列表
self.task_table = QTableWidget()
self.task_table.setColumnCount(8)
self.task_table.setHorizontalHeaderLabels([
"ID", "名称", "指定客户端", "实际客户端", "状态",
"创建时间", "开始时间", "完成时间"
])
self.task_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.task_table.horizontalHeader().setStretchLastSection(True)
self.task_table.cellClicked.connect(self.on_task_selected)
layout.addWidget(self.task_table)
# 任务详情
self.task_detail = QTextEdit()
self.task_detail.setReadOnly(True)
self.task_detail.setPlaceholderText("点击任务查看详情")
layout.addWidget(QLabel("任务详情:"))
layout.addWidget(self.task_detail)
self.tabs.addTab(tab, "任务列表")
def create_task_create_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
# 任务创建表单
form_layout = QFormLayout()
self.task_name_edit = QLineEdit()
form_layout.addRow(QLabel("任务名称:"), self.task_name_edit)
self.task_client_edit = QLineEdit()
form_layout.addRow(QLabel("指定客户端:"), self.task_client_edit)
self.task_script_edit = QTextEdit()
self.task_script_edit.setPlaceholderText("输入执行脚本(可选)")
form_layout.addRow(QLabel("执行脚本:"), self.task_script_edit)
self.task_timeout_spin = QSpinBox()
self.task_timeout_spin.setMinimum(60)
self.task_timeout_spin.setMaximum(86400)
self.task_timeout_spin.setValue(3600)
form_layout.addRow(QLabel("超时时间(秒):"), self.task_timeout_spin)
self.create_task_btn = QPushButton("创建任务")
self.create_task_btn.clicked.connect(self.create_task)
form_layout.addRow(QLabel(" "), self.create_task_btn)
layout.addLayout(form_layout)
# 创建结果
self.create_result = QTextEdit()
self.create_result.setReadOnly(True)
self.create_result.setPlaceholderText("创建任务结果将显示在这里")
layout.addWidget(QLabel("创建结果:"))
layout.addWidget(self.create_result)
self.tabs.addTab(tab, "创建任务")
def create_task_operation_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
# 任务选择
task_select_layout = QHBoxLayout()
self.selected_task_id = QLineEdit()
self.selected_task_id.setPlaceholderText("输入任务ID")
task_select_layout.addWidget(QLabel("任务ID:"))
task_select_layout.addWidget(self.selected_task_id)
self.select_from_list_btn = QPushButton("从列表选择")
self.select_from_list_btn.clicked.connect(self.select_task_from_list)
task_select_layout.addWidget(self.select_from_list_btn)
layout.addLayout(task_select_layout)
# 操作按钮组
operations_layout = QHBoxLayout()
self.claim_task_btn = QPushButton("认领任务")
self.claim_task_btn.clicked.connect(self.claim_task)
operations_layout.addWidget(self.claim_task_btn)
self.start_task_btn = QPushButton("开始任务")
self.start_task_btn.clicked.connect(self.start_task)
operations_layout.addWidget(self.start_task_btn)
self.complete_task_btn = QPushButton("完成任务")
self.complete_task_btn.clicked.connect(self.complete_task)
operations_layout.addWidget(self.complete_task_btn)
layout.addLayout(operations_layout)
# 完成任务表单
complete_layout = QFormLayout()
self.complete_status_combo = QComboBox()
self.complete_status_combo.addItems(["success", "failed"])
complete_layout.addRow(QLabel("执行状态:"), self.complete_status_combo)
self.complete_message_edit = QTextEdit()
self.complete_message_edit.setPlaceholderText("输入执行结果消息")
complete_layout.addRow(QLabel("执行消息:"), self.complete_message_edit)
# 文件上传
self.result_file_edit = QLineEdit()
self.result_file_edit.setReadOnly(True)
browse_btn = QPushButton("浏览")
browse_btn.clicked.connect(self.browse_result_file)
complete_layout.addRow(QLabel("结果文件:"), self.result_file_edit)
complete_layout.addRow(QLabel(" "), browse_btn)
layout.addLayout(complete_layout)
# 操作结果
self.operation_result = QTextEdit()
self.operation_result.setReadOnly(True)
self.operation_result.setPlaceholderText("操作结果将显示在这里")
layout.addWidget(QLabel("操作结果:"))
layout.addWidget(self.operation_result)
self.tabs.addTab(tab, "任务操作")
def toggle_token_visibility(self):
if self.token_edit.echoMode() == QLineEdit.Password:
self.token_edit.setEchoMode(QLineEdit.Normal)
self.token_visibility_btn.setText("隐藏")
else:
self.token_edit.setEchoMode(QLineEdit.Password)
self.token_visibility_btn.setText("显示")
def save_config(self):
server_url = self.server_url_edit.text()
token = self.token_edit.text()
if not server_url or not token:
QMessageBox.warning(self, "警告", "服务器地址和API Token不能为空")
return
# 保存配置到文件
try:
config = {
"server_url": server_url,
"token": token
}
with open("client_config.json", "w") as f:
json.dump(config, f)
QMessageBox.information(self, "成功", "配置已保存!")
self.api_client.set_server(server_url)
self.api_client.set_token(token)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存配置失败: {str(e)}")
def test_connection(self):
server_url = self.server_url_edit.text()
token = self.token_edit.text()
if not server_url or not token:
QMessageBox.warning(self, "警告", "服务器地址和API Token不能为空")
return
try:
# 测试连接
temp_client = ApiClient()
temp_client.set_server(server_url)
temp_client.set_token(token)
tasks = temp_client.get_tasks()
QMessageBox.information(self, "成功", f"连接成功!找到 {len(tasks)} 个任务")
self.connection_status.setText("已连接")
self.connection_status.setStyleSheet("color: green;")
except Exception as e:
QMessageBox.critical(self, "错误", f"连接失败: {str(e)}")
self.connection_status.setText(f"连接失败: {str(e)}")
self.connection_status.setStyleSheet("color: red;")
def refresh_tasks(self):
try:
tasks = self.api_client.get_tasks()
self.update_task_table(tasks)
self.status_bar.showMessage(f"刷新完成,共 {len(tasks)} 个任务")
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
self.status_bar.showMessage(f"刷新失败: {str(e)}")
def update_task_table(self, tasks):
self.task_table.setRowCount(len(tasks))
for row, task in enumerate(tasks):
self.task_table.setItem(row, 0, QTableWidgetItem(str(task["id"])))
self.task_table.setItem(row, 1, QTableWidgetItem(task["name"]))
self.task_table.setItem(row, 2, QTableWidgetItem(task["client_name"] or "未指定"))
self.task_table.setItem(row, 3, QTableWidgetItem(task["assigned_to"] or "未分配"))
self.task_table.setItem(row, 4, QTableWidgetItem(task["status"]))
self.task_table.setItem(row, 5, QTableWidgetItem(task["created_at"]))
self.task_table.setItem(row, 6, QTableWidgetItem(task["started_at"] or "-"))
self.task_table.setItem(row, 7, QTableWidgetItem(task["completed_at"] or "-"))
def toggle_auto_refresh(self, state):
if state == Qt.Checked:
# 启动自动刷新线程
self.task_update_thread = TaskUpdateThread(self.api_client)
self.task_update_thread.update_signal.connect(self.update_task_table)
self.task_update_thread.error_signal.connect(self.show_thread_error)
self.task_update_thread.start()
self.status_bar.showMessage("已启动自动刷新")
else:
# 停止自动刷新线程
if self.task_update_thread:
self.task_update_thread.stop()
self.task_update_thread = None
self.status_bar.showMessage("已停止自动刷新")
def show_thread_error(self, error_msg):
self.status_bar.showMessage(f"自动刷新失败: {error_msg}")
def on_task_selected(self, row, column):
task_id = self.task_table.item(row, 0).text()
self.selected_task_id.setText(task_id)
# 显示任务详情
task_name = self.task_table.item(row, 1).text()
client_name = self.task_table.item(row, 2).text()
assigned_to = self.task_table.item(row, 3).text()
status = self.task_table.item(row, 4).text()
created_at = self.task_table.item(row, 5).text()
started_at = self.task_table.item(row, 6).text()
completed_at = self.task_table.item(row, 7).text()
detail = f"任务ID: {task_id}\n"
detail += f"任务名称: {task_name}\n"
detail += f"指定客户端: {client_name}\n"
detail += f"实际客户端: {assigned_to}\n"
detail += f"状态: {status}\n"
detail += f"创建时间: {created_at}\n"
detail += f"开始时间: {started_at}\n"
detail += f"完成时间: {completed_at}\n"
self.task_detail.setPlainText(detail)
def toggle_token_visibility(self):
if self.token_edit.echoMode() == QLineEdit.Password:
self.token_edit.setEchoMode(QLineEdit.Normal)
self.token_visibility_btn.setText("隐藏")
else:
self.token_edit.setEchoMode(QLineEdit.Password)
self.token_visibility_btn.setText("显示")
def create_task(self):
name = self.task_name_edit.text()
client_name = self.task_client_edit.text()
script = self.task_script_edit.toPlainText()
timeout = self.task_timeout_spin.value()
if not name:
QMessageBox.warning(self, "警告", "任务名称不能为空!")
return
try:
result = self.api_client.create_task(name, client_name, script, timeout)
self.create_result.setPlainText(json.dumps(result, indent=2, ensure_ascii=False))
QMessageBox.information(self, "成功", "任务创建成功!")
# 清空表单
self.task_name_edit.clear()
self.task_client_edit.clear()
self.task_script_edit.clear()
self.task_timeout_spin.setValue(3600)
except Exception as e:
self.create_result.setPlainText(str(e))
QMessageBox.critical(self, "错误", str(e))
def claim_task(self):
task_id = self.selected_task_id.text()
if not task_id:
QMessageBox.warning(self, "警告", "请输入任务ID")
return
try:
result = self.api_client.claim_task(self.task_client_edit.text() or "default_client")
self.operation_result.setPlainText(json.dumps(result, indent=2, ensure_ascii=False))
QMessageBox.information(self, "成功", "任务认领成功!")
except Exception as e:
self.operation_result.setPlainText(str(e))
QMessageBox.critical(self, "错误", str(e))
def start_task(self):
task_id = self.selected_task_id.text()
if not task_id:
QMessageBox.warning(self, "警告", "请输入任务ID")
return
try:
result = self.api_client.start_task(task_id)
self.operation_result.setPlainText(json.dumps(result, indent=2, ensure_ascii=False))
QMessageBox.information(self, "成功", "任务开始成功!")
except Exception as e:
self.operation_result.setPlainText(str(e))
QMessageBox.critical(self, "错误", str(e))
def complete_task(self):
task_id = self.selected_task_id.text()
if not task_id:
QMessageBox.warning(self, "警告", "请输入任务ID")
return
status = self.complete_status_combo.currentText()
message = self.complete_message_edit.toPlainText()
try:
result = self.api_client.complete_task(task_id, status, message)
self.operation_result.setPlainText(json.dumps(result, indent=2, ensure_ascii=False))
QMessageBox.information(self, "成功", "任务完成成功!")
except Exception as e:
self.operation_result.setPlainText(str(e))
QMessageBox.critical(self, "错误", str(e))
def select_task_from_list(self):
selected_rows = self.task_table.selectedItems()
if selected_rows:
task_id = selected_rows[0].text()
self.selected_task_id.setText(task_id)
def browse_result_file(self):
file_path, _ = QFileDialog.getOpenFileName(self, "选择结果文件")
if file_path:
self.result_file_edit.setText(file_path)
def load_config(self):
try:
with open("client_config.json", "r") as f:
config = json.load(f)
self.server_url_edit.setText(config.get("server_url", "http://127.0.0.1:8000/api"))
self.token_edit.setText(config.get("token", ""))
self.api_client.set_server(config.get("server_url", "http://127.0.0.1:8000/api"))
self.api_client.set_token(config.get("token", ""))
except FileNotFoundError:
pass # 配置文件不存在,使用默认值
except Exception as e:
QMessageBox.warning(self, "警告", f"加载配置失败: {str(e)}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TaskCenterClient()
window.load_config()
window.show()
sys.exit(app.exec())