This commit is contained in:
zj
2026-02-09 17:59:50 +08:00
parent 1a3a4746a3
commit 0112e4d3b1
11 changed files with 3223 additions and 13 deletions

386
dialogs_smart.py Normal file
View File

@@ -0,0 +1,386 @@
# dialogs_smart.py
"""SMART 监控相关对话框"""
import tkinter as tk
from tkinter import ttk, messagebox
from smart_monitor import SmartMonitor, HealthStatus
class SmartInfoDialog:
"""SMART 信息对话框"""
def __init__(self, parent, device_path, smart_info):
self.parent = parent
self.device_path = device_path
self.smart_info = smart_info
# 创建对话框
self.dialog = tk.Toplevel(parent)
self.dialog.title(f"SMART 信息 - {device_path}")
self.dialog.geometry("700x600")
self.dialog.transient(parent)
self.dialog.grab_set()
self._center_window()
self._create_widgets()
def _center_window(self):
"""居中显示"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
def _create_widgets(self):
"""创建界面"""
main_frame = ttk.Frame(self.dialog, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 基本信息区域
info_frame = ttk.LabelFrame(main_frame, text="基本信息", padding="10")
info_frame.pack(fill=tk.X, pady=(0, 10))
# 健康状态(大图标)
status_frame = ttk.Frame(info_frame)
status_frame.pack(fill=tk.X, pady=5)
status_colors = {
HealthStatus.GOOD: ("良好", "green"),
HealthStatus.WARNING: ("警告", "orange"),
HealthStatus.DANGER: ("危险", "red"),
HealthStatus.UNKNOWN: ("未知", "gray"),
}
status_text, status_color = status_colors.get(
self.smart_info.health_status, ("未知", "gray")
)
status_label = ttk.Label(
status_frame,
text=f"健康状态: {status_text}",
font=("Arial", 16, "bold"),
foreground=status_color
)
status_label.pack(side=tk.LEFT, padx=10)
# 健康评分
monitor = SmartMonitor()
score = monitor.get_health_score(self.smart_info)
score_label = ttk.Label(
status_frame,
text=f"健康评分: {score}/100",
font=("Arial", 14)
)
score_label.pack(side=tk.LEFT, padx=20)
# 设备详细信息
details_grid = ttk.Frame(info_frame)
details_grid.pack(fill=tk.X, pady=5)
details = [
("设备", self.smart_info.device),
("型号", self.smart_info.model or "N/A"),
("序列号", self.smart_info.serial or "N/A"),
("固件版本", self.smart_info.firmware or "N/A"),
("整体健康", self.smart_info.overall_health or "N/A"),
]
for i, (label, value) in enumerate(details):
ttk.Label(details_grid, text=f"{label}:", font=("Arial", 10, "bold")).grid(
row=i, column=0, sticky=tk.W, padx=5, pady=2
)
ttk.Label(details_grid, text=value).grid(
row=i, column=1, sticky=tk.W, padx=5, pady=2
)
# 使用统计
usage_frame = ttk.LabelFrame(main_frame, text="使用统计", padding="10")
usage_frame.pack(fill=tk.X, pady=(0, 10))
# 温度
temp = self.smart_info.temperature
if temp:
temp_status, temp_color = monitor.get_temperature_status(temp)
temp_text = f"{temp}°C ({temp_status})"
else:
temp_text = "N/A"
usage_info = [
("温度", temp_text),
("通电时间", f"{self.smart_info.power_on_hours} 小时" if self.smart_info.power_on_hours else "N/A"),
("通电次数", f"{self.smart_info.power_cycle_count}" if self.smart_info.power_cycle_count else "N/A"),
]
for i, (label, value) in enumerate(usage_info):
ttk.Label(usage_frame, text=f"{label}:", font=("Arial", 10, "bold")).grid(
row=i, column=0, sticky=tk.W, padx=5, pady=2
)
temp_label = ttk.Label(usage_frame, text=value)
if label == "温度" and temp and temp > 45:
temp_label.configure(foreground="red")
temp_label.grid(row=i, column=1, sticky=tk.W, padx=5, pady=2)
# SMART 属性表
attr_frame = ttk.LabelFrame(main_frame, text="SMART 属性", padding="10")
attr_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 创建 Treeview
columns = ("ID", "名称", "当前值", "最差值", "阈值", "原始值", "状态")
tree = ttk.Treeview(attr_frame, columns=columns, show="headings", height=10)
for col in columns:
tree.heading(col, text=col)
tree.column(col, width=80)
tree.column("名称", width=150)
tree.column("原始值", width=100)
# 添加滚动条
scrollbar = ttk.Scrollbar(attr_frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 填充数据
for attr in self.smart_info.attributes:
values = (
attr.id,
attr.name,
attr.value,
attr.worst,
attr.threshold,
attr.raw_value,
attr.status
)
# 根据状态设置颜色
tag = ""
if attr.status == "预警":
tag = "warning"
elif attr.id in monitor.CRITICAL_ATTRIBUTES and attr.raw_value != "0":
tag = "danger"
tree.insert("", tk.END, values=values, tags=(tag,))
# 设置标签颜色
tree.tag_configure("warning", foreground="orange")
tree.tag_configure("danger", foreground="red")
# 错误信息
if self.smart_info.errors:
error_frame = ttk.LabelFrame(main_frame, text="警告/错误", padding="10")
error_frame.pack(fill=tk.X, pady=(0, 10))
for error in self.smart_info.errors:
ttk.Label(error_frame, text=f"{error}", foreground="red").pack(
anchor=tk.W, padx=5, pady=2
)
# 关闭按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=10)
ttk.Button(btn_frame, text="关闭", command=self.dialog.destroy).pack(side=tk.RIGHT, padx=5)
ttk.Button(
btn_frame,
text="刷新",
command=self._refresh
).pack(side=tk.RIGHT, padx=5)
def _refresh(self):
"""刷新 SMART 信息"""
monitor = SmartMonitor()
new_info = monitor.get_disk_smart_info(self.device_path)
if new_info:
self.smart_info = new_info
# 重建界面
for widget in self.dialog.winfo_children():
widget.destroy()
self._create_widgets()
else:
messagebox.showerror("错误", "刷新 SMART 信息失败")
class SmartOverviewDialog:
"""SMART 总览对话框 - 显示所有磁盘的健康状态"""
def __init__(self, parent):
self.parent = parent
self.monitor = SmartMonitor()
self.dialog = tk.Toplevel(parent)
self.dialog.title("SMART 健康监控 - 总览")
self.dialog.geometry("800x500")
self.dialog.transient(parent)
self.dialog.grab_set()
self._center_window()
self._create_widgets()
self._refresh_data()
def _center_window(self):
"""居中显示"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
def _create_widgets(self):
"""创建界面"""
main_frame = ttk.Frame(self.dialog, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
title_label = ttk.Label(
main_frame,
text="磁盘 SMART 健康状态总览",
font=("Arial", 14, "bold")
)
title_label.pack(pady=(0, 10))
# 状态统计
self.stats_frame = ttk.Frame(main_frame)
self.stats_frame.pack(fill=tk.X, pady=(0, 10))
# 磁盘列表
list_frame = ttk.LabelFrame(main_frame, text="磁盘列表", padding="10")
list_frame.pack(fill=tk.BOTH, expand=True)
columns = ("设备", "型号", "健康状态", "评分", "温度", "通电时间", "详情")
self.tree = ttk.Treeview(list_frame, columns=columns, show="headings")
for col in columns:
self.tree.heading(col, text=col)
self.tree.column(col, width=100)
self.tree.column("设备", width=80)
self.tree.column("型号", width=200)
self.tree.column("详情", width=60)
# 滚动条
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("<Double-1>", self._on_double_click)
# 存储 item 数据的字典
self._item_data = {}
# 按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=10)
ttk.Button(btn_frame, text="关闭", command=self.dialog.destroy).pack(side=tk.RIGHT, padx=5)
ttk.Button(btn_frame, text="刷新", command=self._refresh_data).pack(side=tk.RIGHT, padx=5)
# 状态栏
self.status_label = ttk.Label(main_frame, text="准备就绪")
self.status_label.pack(anchor=tk.W)
def _refresh_data(self):
"""刷新数据"""
self.status_label.configure(text="正在获取 SMART 信息...")
self.dialog.update()
# 清空现有数据
for item in self.tree.get_children():
self.tree.delete(item)
# 获取所有磁盘 SMART 信息
smart_data = self.monitor.get_all_disks_smart()
# 统计
good_count = 0
warning_count = 0
danger_count = 0
for device_path, info in smart_data.items():
score = self.monitor.get_health_score(info)
# 状态文本和颜色
status_colors = {
HealthStatus.GOOD: ("良好", "green"),
HealthStatus.WARNING: ("警告", "orange"),
HealthStatus.DANGER: ("危险", "red"),
HealthStatus.UNKNOWN: ("未知", "gray"),
}
status_text, _ = status_colors.get(info.health_status, ("未知", "gray"))
# 统计
if info.health_status == HealthStatus.GOOD:
good_count += 1
elif info.health_status == HealthStatus.WARNING:
warning_count += 1
elif info.health_status == HealthStatus.DANGER:
danger_count += 1
# 温度
temp_text = f"{info.temperature}°C" if info.temperature else "N/A"
# 通电时间
hours_text = f"{info.power_on_hours}h" if info.power_on_hours else "N/A"
values = (
device_path,
info.model[:30] if info.model else "N/A",
status_text,
f"{score}/100",
temp_text,
hours_text,
"查看详情"
)
tag = ""
if info.health_status == HealthStatus.WARNING:
tag = "warning"
elif info.health_status == HealthStatus.DANGER:
tag = "danger"
item_id = self.tree.insert("", tk.END, values=values, tags=(tag,))
self._item_data[item_id] = {"device": device_path, "info": info}
# 设置颜色
self.tree.tag_configure("warning", foreground="orange")
self.tree.tag_configure("danger", foreground="red")
# 更新统计
for widget in self.stats_frame.winfo_children():
widget.destroy()
stats = [
("总磁盘", len(smart_data), "black"),
("良好", good_count, "green"),
("警告", warning_count, "orange"),
("危险", danger_count, "red"),
]
for label, count, color in stats:
lbl = ttk.Label(
self.stats_frame,
text=f"{label}: {count}",
font=("Arial", 11, "bold"),
foreground=color
)
lbl.pack(side=tk.LEFT, padx=10)
self.status_label.configure(text=f"已更新 {len(smart_data)} 个磁盘的信息")
def _on_double_click(self, event):
"""双击查看详情"""
item = self.tree.selection()[0]
data = self._item_data.get(item)
if data:
# 暂时释放 grab以便打开子对话框
self.dialog.grab_release()
try:
SmartInfoDialog(self.dialog, data["device"], data["info"])
finally:
# 恢复 grab
self.dialog.grab_set()