420 lines
16 KiB
Python
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}")
|
||
|
|
|