387 lines
14 KiB
Python
387 lines
14 KiB
Python
# 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()
|