TorqueWrench/frontend/device_manager.py

420 lines
16 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
扳手设备管理窗口
"""
import tkinter as tk
from tkinter import ttk, messagebox
import requests
import threading
import sys
import os
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from wrench_controller import WrenchController
class DeviceManagerWindow:
"""设备管理窗口"""
def __init__(self, parent, api_base_url, refresh_callback=None):
self.parent = parent
self.api_base_url = api_base_url
self.refresh_callback = refresh_callback
# 创建窗口
self.window = tk.Toplevel(parent)
self.window.title("扳手设备管理")
self.window.geometry("800x600")
self.window.transient(parent)
self.window.grab_set()
self.devices = []
self.selected_device_id = None
self._create_widgets()
self.load_devices()
def _create_widgets(self):
"""创建界面组件"""
# 标题
title_frame = tk.Frame(self.window, bg="#2c3e50", height=50)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
title_label = tk.Label(
title_frame,
text="扳手设备管理",
font=("微软雅黑", 16, "bold"),
fg="white",
bg="#2c3e50"
)
title_label.pack(pady=10)
# 设备列表
list_frame = tk.LabelFrame(self.window, text="设备列表", font=("微软雅黑", 12), padx=10, pady=10)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
columns = ("ID", "设备名称", "SN码", "IP地址", "端口", "地址码", "状态", "最后心跳")
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=10)
for col in columns:
self.tree.heading(col, text=col)
self.tree.column("ID", width=50, anchor=tk.CENTER)
self.tree.column("设备名称", width=120, anchor=tk.W)
self.tree.column("SN码", width=120, anchor=tk.CENTER)
self.tree.column("IP地址", width=120, anchor=tk.CENTER)
self.tree.column("端口", width=60, anchor=tk.CENTER)
self.tree.column("地址码", width=60, anchor=tk.CENTER)
self.tree.column("状态", width=80, anchor=tk.CENTER)
self.tree.column("最后心跳", width=150, anchor=tk.CENTER)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.bind("<<TreeviewSelect>>", self.on_device_select)
# 配置标签颜色
self.tree.tag_configure("online", foreground="#27ae60")
self.tree.tag_configure("offline", foreground="#e74c3c")
# 操作按钮
button_frame = tk.Frame(self.window, padx=10, pady=10)
button_frame.pack(fill=tk.X)
self.add_button = tk.Button(
button_frame,
text="添加设备",
font=("微软雅黑", 10),
bg="#27ae60",
fg="white",
command=self.add_device
)
self.add_button.pack(side=tk.LEFT, padx=5)
self.edit_button = tk.Button(
button_frame,
text="编辑设备",
font=("微软雅黑", 10),
bg="#3498db",
fg="white",
command=self.edit_device,
state=tk.DISABLED
)
self.edit_button.pack(side=tk.LEFT, padx=5)
self.delete_button = tk.Button(
button_frame,
text="删除设备",
font=("微软雅黑", 10),
bg="#e74c3c",
fg="white",
command=self.delete_device,
state=tk.DISABLED
)
self.delete_button.pack(side=tk.LEFT, padx=5)
self.test_connect_button = tk.Button(
button_frame,
text="测试连接",
font=("微软雅黑", 10),
bg="#9b59b6",
fg="white",
command=self.test_connection,
state=tk.DISABLED
)
self.test_connect_button.pack(side=tk.LEFT, padx=5)
self.refresh_button = tk.Button(
button_frame,
text="刷新列表",
font=("微软雅黑", 10),
bg="#95a5a6",
fg="white",
command=self.load_devices
)
self.refresh_button.pack(side=tk.RIGHT, padx=5)
def load_devices(self):
"""加载设备列表"""
try:
url = f"{self.api_base_url}/wrench-devices"
response = requests.get(url, timeout=5)
if response.status_code == 200:
data = response.json()
if data.get("success"):
self.devices = data.get("data", [])
self.update_device_list()
else:
messagebox.showerror("错误", f"加载设备列表失败: {data.get('message')}")
except Exception as e:
messagebox.showerror("错误", f"无法连接到后端服务器\n\n错误: {e}")
def update_device_list(self):
"""更新设备列表显示"""
self.tree.delete(*self.tree.get_children())
for device in self.devices:
status = device.get('status', 'offline')
status_text = "在线" if status == 'online' else "离线"
tag = "online" if status == 'online' else "offline"
self.tree.insert("", tk.END, values=(
device.get('id'),
device.get('device_name', '--'),
device.get('device_sn', '--'),
device.get('ip_address', '--'),
device.get('port', 7888),
device.get('address_code', 1),
status_text,
device.get('last_heartbeat', '--')
), tags=(tag,))
def on_device_select(self, event):
"""设备选择事件"""
selected = self.tree.selection()
if selected:
item = self.tree.item(selected[0])
values = item['values']
if values:
self.selected_device_id = values[0]
self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
self.test_connect_button.config(state=tk.NORMAL)
else:
self.selected_device_id = None
self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
self.test_connect_button.config(state=tk.DISABLED)
def add_device(self):
"""添加设备"""
DeviceEditDialog(self.window, self.api_base_url, None, self.load_devices)
def edit_device(self):
"""编辑设备"""
if not self.selected_device_id:
return
device = None
for d in self.devices:
if d.get('id') == self.selected_device_id:
device = d
break
if device:
DeviceEditDialog(self.window, self.api_base_url, device, self.load_devices)
def delete_device(self):
"""删除设备"""
if not self.selected_device_id:
return
device = None
for d in self.devices:
if d.get('id') == self.selected_device_id:
device = d
break
if not device:
return
result = messagebox.askyesno("确认删除", f"确定要删除设备 '{device.get('device_name')}' 吗?")
if result:
try:
url = f"{self.api_base_url}/wrench-devices/{self.selected_device_id}"
response = requests.delete(url, timeout=5)
if response.status_code == 200:
data = response.json()
if data.get("success"):
messagebox.showinfo("成功", "设备删除成功")
self.load_devices()
if self.refresh_callback:
self.refresh_callback()
else:
messagebox.showerror("错误", data.get('message', '删除失败'))
else:
messagebox.showerror("错误", f"删除失败: HTTP {response.status_code}")
except Exception as e:
messagebox.showerror("错误", f"删除失败: {e}")
def test_connection(self):
"""测试设备连接"""
if not self.selected_device_id:
return
device = None
for d in self.devices:
if d.get('id') == self.selected_device_id:
device = d
break
if not device:
return
def test_thread():
try:
device_config = {
"ip_address": device.get('ip_address'),
"port": device.get('port', 7888),
"address_code": device.get('address_code', 1)
}
wrench = WrenchController(device_config=device_config)
if wrench.connect():
wrench.disconnect()
# 更新设备状态为在线
self.window.after(0, lambda: self.update_device_status(device.get('id'), 'online'))
self.window.after(0, lambda: messagebox.showinfo("成功", "设备连接成功"))
else:
self.window.after(0, lambda: self.update_device_status(device.get('id'), 'offline'))
self.window.after(0, lambda: messagebox.showerror("失败", "设备连接失败"))
except Exception as e:
self.window.after(0, lambda: self.update_device_status(device.get('id'), 'offline'))
self.window.after(0, lambda: messagebox.showerror("错误", f"连接测试失败: {e}"))
threading.Thread(target=test_thread, daemon=True).start()
def update_device_status(self, device_id, status):
"""更新设备状态"""
try:
url = f"{self.api_base_url}/wrench-devices/{device_id}/status"
response = requests.post(url, json={"status": status}, timeout=5)
if response.status_code == 200:
self.load_devices()
if self.refresh_callback:
self.refresh_callback()
except:
pass
class DeviceEditDialog:
"""设备编辑对话框"""
def __init__(self, parent, api_base_url, device=None, callback=None):
self.parent = parent
self.api_base_url = api_base_url
self.device = device
self.callback = callback
self.dialog = tk.Toplevel(parent)
self.dialog.title("添加设备" if not device else "编辑设备")
self.dialog.geometry("400x300")
self.dialog.transient(parent)
self.dialog.grab_set()
self._create_widgets()
def _create_widgets(self):
"""创建界面组件"""
frame = tk.Frame(self.dialog, padx=20, pady=20)
frame.pack(fill=tk.BOTH, expand=True)
# 设备名称
tk.Label(frame, text="设备名称:", font=("微软雅黑", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
self.name_var = tk.StringVar(value=self.device.get('device_name', '') if self.device else '')
tk.Entry(frame, textvariable=self.name_var, width=30, font=("微软雅黑", 10)).grid(row=0, column=1, pady=10)
# SN码
tk.Label(frame, text="SN码:", font=("微软雅黑", 10)).grid(row=1, column=0, sticky=tk.W, pady=10)
self.sn_var = tk.StringVar(value=self.device.get('device_sn', '') if self.device else '')
sn_entry = tk.Entry(frame, textvariable=self.sn_var, width=30, font=("微软雅黑", 10))
sn_entry.grid(row=1, column=1, pady=10)
tk.Label(frame, text="(留空可自动查询)", font=("微软雅黑", 8), fg="#7f8c8d").grid(row=1, column=2, sticky=tk.W)
# IP地址
tk.Label(frame, text="IP地址:", font=("微软雅黑", 10)).grid(row=2, column=0, sticky=tk.W, pady=10)
self.ip_var = tk.StringVar(value=self.device.get('ip_address', '') if self.device else '')
tk.Entry(frame, textvariable=self.ip_var, width=30, font=("微软雅黑", 10)).grid(row=2, column=1, pady=10)
# 端口
tk.Label(frame, text="端口:", font=("微软雅黑", 10)).grid(row=3, column=0, sticky=tk.W, pady=10)
self.port_var = tk.StringVar(value=str(self.device.get('port', 7888)) if self.device else '7888')
tk.Entry(frame, textvariable=self.port_var, width=30, font=("微软雅黑", 10)).grid(row=3, column=1, pady=10)
# 地址码
tk.Label(frame, text="地址码:", font=("微软雅黑", 10)).grid(row=4, column=0, sticky=tk.W, pady=10)
self.address_var = tk.StringVar(value=str(self.device.get('address_code', 1)) if self.device else '1')
tk.Entry(frame, textvariable=self.address_var, width=30, font=("微软雅黑", 10)).grid(row=4, column=1, pady=10)
# 按钮
button_frame = tk.Frame(frame)
button_frame.grid(row=5, column=0, columnspan=2, pady=20)
tk.Button(
button_frame,
text="保存",
font=("微软雅黑", 10),
bg="#27ae60",
fg="white",
width=10,
command=self.save_device
).pack(side=tk.LEFT, padx=5)
tk.Button(
button_frame,
text="取消",
font=("微软雅黑", 10),
bg="#95a5a6",
fg="white",
width=10,
command=self.dialog.destroy
).pack(side=tk.LEFT, padx=5)
def save_device(self):
"""保存设备"""
device_name = self.name_var.get().strip()
ip_address = self.ip_var.get().strip()
if not device_name or not ip_address:
messagebox.showwarning("警告", "设备名称和IP地址不能为空")
return
try:
port = int(self.port_var.get().strip() or 7888)
address_code = int(self.address_var.get().strip() or 1)
except ValueError:
messagebox.showerror("错误", "端口和地址码必须是数字")
return
device_data = {
"device_name": device_name,
"device_sn": self.sn_var.get().strip() or None,
"ip_address": ip_address,
"port": port,
"address_code": address_code
}
try:
if self.device:
# 更新设备
url = f"{self.api_base_url}/wrench-devices/{self.device.get('id')}"
response = requests.put(url, json=device_data, timeout=5)
else:
# 创建设备
url = f"{self.api_base_url}/wrench-devices"
response = requests.post(url, json=device_data, timeout=5)
if response.status_code == 200:
data = response.json()
if data.get("success"):
messagebox.showinfo("成功", "设备保存成功")
self.dialog.destroy()
if self.callback:
self.callback()
else:
messagebox.showerror("错误", data.get('message', '保存失败'))
else:
messagebox.showerror("错误", f"保存失败: HTTP {response.status_code}")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")