# 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("", 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()