568 lines
22 KiB
Python
568 lines
22 KiB
Python
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()) |