# dialogs_enhanced.py """增强型对话框 - 智能提示与确认""" import tkinter as tk from tkinter import ttk, messagebox import logging logger = logging.getLogger(__name__) class EnhancedFormatDialog: """增强格式化对话框 - 显示详细警告信息""" def __init__(self, parent, device_path, device_info): self.parent = parent self.device_path = device_path self.device_info = device_info self.result = None self.dialog = tk.Toplevel(parent) self.dialog.title(f"格式化确认 - {device_path}") self.dialog.geometry("500x575") self.dialog.minsize(480, 450) 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="15") main_frame.pack(fill=tk.BOTH, expand=True) # 警告标题 warning_frame = ttk.Frame(main_frame) warning_frame.pack(fill=tk.X, pady=(0, 15)) warning_label = ttk.Label( warning_frame, text="⚠ 警告:此操作将永久删除所有数据!", font=("Arial", 12, "bold"), foreground="red" ) warning_label.pack() # 设备信息区域 info_frame = ttk.LabelFrame(main_frame, text="设备信息", padding="10") info_frame.pack(fill=tk.X, pady=(0, 15)) # 显示详细信息 infos = [ ("设备路径", self.device_path), ("设备名称", self.device_info.get('name', 'N/A')), ("文件系统", self.device_info.get('fstype', 'N/A')), ("大小", self.device_info.get('size', 'N/A')), ("UUID", self.device_info.get('uuid', 'N/A')), ("挂载点", self.device_info.get('mountpoint', '未挂载')), ] for i, (label, value) in enumerate(infos): ttk.Label(info_frame, text=f"{label}:", font=("Arial", 10, "bold")).grid( row=i, column=0, sticky=tk.W, padx=5, pady=3 ) val_label = ttk.Label(info_frame, text=str(value)) val_label.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) # 如果有挂载点,高亮显示 if label == "挂载点" and value and value != '未挂载': val_label.configure(foreground="orange", font=("Arial", 9, "bold")) # 数据风险提示 risk_frame = ttk.LabelFrame(main_frame, text="数据风险提示", padding="10") risk_frame.pack(fill=tk.X, pady=(0, 15)) risks = [] if self.device_info.get('fstype'): risks.append(f"• 此分区包含 {self.device_info['fstype']} 文件系统") if self.device_info.get('uuid'): risks.append(f"• 分区有 UUID: {self.device_info['uuid'][:20]}...") if self.device_info.get('mountpoint'): risks.append(f"• 分区当前挂载在: {self.device_info['mountpoint']}") if not risks: risks.append("• 无法获取分区详细信息") for risk in risks: ttk.Label(risk_frame, text=risk, foreground="red").pack( anchor=tk.W, pady=2 ) # 文件系统选择 fs_frame = ttk.LabelFrame(main_frame, text="选择新文件系统", padding="10") fs_frame.pack(fill=tk.X, pady=(0, 15)) self.fs_var = tk.StringVar(value="ext4") fs_options = ["ext4", "xfs", "fat32", "ntfs"] for fs in fs_options: ttk.Radiobutton(fs_frame, text=fs, variable=self.fs_var, value=fs).pack(side=tk.LEFT, padx=10) # 确认输入 confirm_frame = ttk.Frame(main_frame) confirm_frame.pack(fill=tk.X, pady=(0, 15)) ttk.Label(confirm_frame, text=f"请输入设备名 '{self.device_info.get('name', '确认')}' 以继续:", foreground="red").pack(anchor=tk.W, pady=5) self.confirm_var = tk.StringVar() self.confirm_entry = ttk.Entry(confirm_frame, textvariable=self.confirm_var, width=20) self.confirm_entry.pack(anchor=tk.W, pady=5) self.confirm_entry.focus() # 按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="取消", command=self._on_cancel).pack(side=tk.RIGHT, padx=5) ttk.Button(btn_frame, text="确认格式化", command=self._on_confirm).pack(side=tk.RIGHT, padx=5) def _on_confirm(self): expected = self.device_info.get('name', '确认') if self.confirm_var.get() != expected: messagebox.showerror("错误", f"输入错误!请输入 '{expected}' 以确认格式化操作。") return self.result = self.fs_var.get() self.dialog.destroy() def _on_cancel(self): self.dialog.destroy() def wait_for_result(self): self.dialog.wait_window() return self.result class EnhancedRaidDeleteDialog: """增强 RAID 删除对话框 - 显示阵列详情""" def __init__(self, parent, array_path, array_data, member_devices): self.parent = parent self.array_path = array_path self.array_data = array_data self.member_devices = member_devices self.result = False self.dialog = tk.Toplevel(parent) self.dialog.title(f"删除 RAID 阵列确认 - {array_path}") self.dialog.geometry("550x750") self.dialog.minsize(520, 750) 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="15") main_frame.pack(fill=tk.BOTH, expand=True) # 危险警告 warning_frame = ttk.Frame(main_frame) warning_frame.pack(fill=tk.X, pady=(0, 15)) warning_label = ttk.Label( warning_frame, text="⚠ 危险操作:此操作将永久删除 RAID 阵列!", font=("Arial", 13, "bold"), foreground="red" ) warning_label.pack() # 阵列详情 array_frame = ttk.LabelFrame(main_frame, text="阵列详情", padding="10") array_frame.pack(fill=tk.X, pady=(0, 15)) details = [ ("阵列设备", self.array_path), ("RAID 级别", self.array_data.get('level', 'N/A')), ("阵列状态", self.array_data.get('state', 'N/A')), ("阵列大小", self.array_data.get('array_size', 'N/A')), ("UUID", self.array_data.get('uuid', 'N/A')), ("Chunk 大小", self.array_data.get('chunk_size', 'N/A')), ] for i, (label, value) in enumerate(details): ttk.Label(array_frame, text=f"{label}:", font=("Arial", 10, "bold")).grid( row=i, column=0, sticky=tk.W, padx=5, pady=3 ) ttk.Label(array_frame, text=str(value)).grid( row=i, column=1, sticky=tk.W, padx=5, pady=3 ) # 成员设备列表 member_frame = ttk.LabelFrame(main_frame, text="成员设备(将被清除 RAID 超级块)", padding="10") member_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) if self.member_devices: for i, dev in enumerate(self.member_devices): if dev: ttk.Label(member_frame, text=f" • {dev}", font=("Arial", 10), foreground="red").pack( anchor=tk.W, pady=2 ) else: ttk.Label(member_frame, text=" 无法获取成员设备列表", foreground="orange").pack(anchor=tk.W, pady=5) # 后果警告 consequence_frame = ttk.LabelFrame(main_frame, text="操作后果", padding="10") consequence_frame.pack(fill=tk.X, pady=(0, 15)) consequences = [ "• 阵列将被停止并从系统中移除", "• 所有成员设备的 RAID 超级块将被清除", "• 阵列上的所有数据将永久丢失", "• /etc/mdadm.conf 中的配置将被删除", "• 此操作无法撤销!" ] for con in consequences: ttk.Label(consequence_frame, text=con, foreground="red").pack(anchor=tk.W, pady=2) # 二次确认 confirm_frame = ttk.Frame(main_frame) confirm_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(confirm_frame, text="请输入 'DELETE' 以确认删除操作:", foreground="red", font=("Arial", 10, "bold")).pack( anchor=tk.W, pady=5) self.confirm_var = tk.StringVar() self.confirm_entry = ttk.Entry(confirm_frame, textvariable=self.confirm_var, width=15) self.confirm_entry.pack(anchor=tk.W, pady=5) self.confirm_entry.focus() # 按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="取消", command=self._on_cancel).pack(side=tk.RIGHT, padx=5) ttk.Button(btn_frame, text="确认删除", command=self._on_confirm, style="Danger.TButton").pack(side=tk.RIGHT, padx=5) # 配置危险按钮样式 style = ttk.Style() style.configure("Danger.TButton", foreground="red") def _on_confirm(self): if self.confirm_var.get() != "DELETE": messagebox.showerror("错误", "输入错误!请输入 'DELETE'(全大写)以确认删除操作。") return self.result = True self.dialog.destroy() def _on_cancel(self): self.dialog.destroy() def wait_for_result(self): self.dialog.wait_window() return self.result class EnhancedPartitionDialog: """增强分区创建对话框 - 可视化滑块选择大小""" def __init__(self, parent, disk_path, total_disk_mib, max_available_mib): self.parent = parent self.disk_path = disk_path self.total_disk_mib = total_disk_mib self.max_available_mib = max_available_mib self.result = None self.dialog = tk.Toplevel(parent) self.dialog.title(f"创建分区 - {disk_path}") self.dialog.geometry("550x575") self.dialog.minsize(500, 575) 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="15") 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, 15)) total_gb = self.total_disk_mib / 1024 max_gb = self.max_available_mib / 1024 ttk.Label(info_frame, text=f"磁盘: {self.disk_path}").pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=f"总容量: {total_gb:.2f} GB").pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=f"可用空间: {max_gb:.2f} GB", foreground="green").pack(anchor=tk.W, pady=2) # 分区表类型 pt_frame = ttk.LabelFrame(main_frame, text="分区表类型", padding="10") pt_frame.pack(fill=tk.X, pady=(0, 15)) self.part_type_var = tk.StringVar(value="gpt") ttk.Radiobutton(pt_frame, text="GPT (推荐,支持大容量磁盘)", variable=self.part_type_var, value="gpt").pack(anchor=tk.W, pady=2) ttk.Radiobutton(pt_frame, text="MBR/MSDOS (兼容旧系统)", variable=self.part_type_var, value="msdos").pack(anchor=tk.W, pady=2) # 大小选择 size_frame = ttk.LabelFrame(main_frame, text="分区大小", padding="10") size_frame.pack(fill=tk.X, pady=(0, 15)) # 使用最大空间复选框 self.use_max_var = tk.BooleanVar(value=True) ttk.Checkbutton(size_frame, text="使用最大可用空间", variable=self.use_max_var, command=self._toggle_size_input).pack(anchor=tk.W, pady=5) # 滑块和输入框容器 self.size_control_frame = ttk.Frame(size_frame) self.size_control_frame.pack(fill=tk.X, pady=5) # 滑块 self.size_var = tk.DoubleVar(value=max_gb) self.size_slider = ttk.Scale( self.size_control_frame, from_=0.1, to=max_gb, orient=tk.HORIZONTAL, variable=self.size_var, command=self._on_slider_change ) self.size_slider.pack(fill=tk.X, pady=5) # 输入框和标签 input_frame = ttk.Frame(self.size_control_frame) input_frame.pack(fill=tk.X, pady=5) ttk.Label(input_frame, text="大小:").pack(side=tk.LEFT, padx=5) self.size_spin = tk.Spinbox( input_frame, from_=0.1, to=max_gb, increment=0.1, textvariable=self.size_var, width=10, command=self._on_spin_change ) self.size_spin.pack(side=tk.LEFT, padx=5) ttk.Label(input_frame, text="GB").pack(side=tk.LEFT, padx=5) # 显示百分比 self.percent_label = ttk.Label(input_frame, text="(100%)") self.percent_label.pack(side=tk.LEFT, padx=10) # 可视化空间显示 self._create_space_visualizer(main_frame, max_gb) # 初始状态 self._toggle_size_input() # 按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="取消", command=self._on_cancel).pack(side=tk.RIGHT, padx=5) ttk.Button(btn_frame, text="创建分区", command=self._on_confirm).pack(side=tk.RIGHT, padx=5) def _create_space_visualizer(self, parent, max_gb): """创建空间可视化条""" viz_frame = ttk.LabelFrame(parent, text="空间使用预览", padding="10") viz_frame.pack(fill=tk.X, pady=(0, 15)) # Canvas 用于绘制 self.viz_canvas = tk.Canvas(viz_frame, height=40, bg="white") self.viz_canvas.pack(fill=tk.X, pady=5) # 等待 Canvas 渲染完成后再更新 self.viz_canvas.update_idletasks() # 绘制初始状态 self._update_visualizer(max_gb, max_gb) def _update_visualizer(self, current_gb, max_gb): """更新空间可视化""" self.viz_canvas.delete("all") width = self.viz_canvas.winfo_width() or 400 height = self.viz_canvas.winfo_height() or 40 # 计算比例 if max_gb > 0: ratio = current_gb / max_gb else: ratio = 0 fill_width = int(width * ratio) # 绘制背景 self.viz_canvas.create_rectangle(0, 0, width, height, fill="lightgray", outline="") # 绘制已用空间 if fill_width > 0: color = "green" if ratio < 0.8 else "orange" if ratio < 0.95 else "red" self.viz_canvas.create_rectangle(0, 0, fill_width, height, fill=color, outline="") # 绘制文字 self.viz_canvas.create_text( width // 2, height // 2, text=f"{current_gb:.2f} GB / {max_gb:.2f} GB", font=("Arial", 10, "bold") ) def _on_slider_change(self, value): """滑块变化回调""" try: gb = float(value) self._update_percent(gb) self._update_visualizer(gb, self.max_available_mib / 1024) except: pass def _on_spin_change(self): """输入框变化回调""" try: gb = self.size_var.get() self._update_percent(gb) self._update_visualizer(gb, self.max_available_mib / 1024) except: pass def _update_percent(self, gb): """更新百分比显示""" max_gb = self.max_available_mib / 1024 if max_gb > 0: percent = (gb / max_gb) * 100 self.percent_label.configure(text=f"({percent:.1f}%)") def _toggle_size_input(self): """切换大小输入状态""" if self.use_max_var.get(): self.size_slider.configure(state=tk.DISABLED) self.size_spin.configure(state=tk.DISABLED) max_gb = self.max_available_mib / 1024 self.size_var.set(max_gb) self._update_percent(max_gb) self._update_visualizer(max_gb, max_gb) else: self.size_slider.configure(state=tk.NORMAL) self.size_spin.configure(state=tk.NORMAL) def _on_confirm(self): size_gb = self.size_var.get() use_max_space = self.use_max_var.get() if not use_max_space and size_gb <= 0: messagebox.showwarning("输入错误", "分区大小必须大于0。") return max_gb = self.max_available_mib / 1024 if not use_max_space and size_gb > max_gb: messagebox.showwarning("输入错误", "分区大小不能超过最大可用空间。") return self.result = { 'disk_path': self.disk_path, 'partition_table_type': self.part_type_var.get(), 'size_gb': size_gb, 'total_disk_mib': self.total_disk_mib, 'use_max_space': use_max_space } self.dialog.destroy() def _on_cancel(self): self.dialog.destroy() def wait_for_result(self): self.dialog.wait_window() return self.result