Files
diskmanager2/dialogs_smart.py
2026-02-10 02:54:13 +08:00

391 lines
14 KiB
Python
Raw Permalink 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.

# 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._center_window()
self._create_widgets()
# 窗口可见后再设置 grab_set
self.dialog.after(100, self.dialog.grab_set)
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._center_window()
self._create_widgets()
self._refresh_data()
# 窗口可见后再设置 grab_set
self.dialog.after(100, self.dialog.grab_set)
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()