# dialogs_enhanced.py """增强型对话框 - 智能提示与确认""" import tkinter as tk from tkinter import ttk, messagebox import logging logger = logging.getLogger(__name__) class DependencyCheckDialog: """依赖检查对话框 - 显示系统依赖状态""" def __init__(self, parent): self.parent = parent self.result = None self.dialog = tk.Toplevel(parent) self.dialog.title("系统依赖检查") self.dialog.geometry("650x550") self.dialog.minsize(600, 400) self.dialog.transient(parent) self.dialog.grab_set() self._center_window() self._create_widgets() def _center_window(self): """居中窗口""" self.dialog.update_idletasks() width = 650 height = 550 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): """创建界面""" from dependency_checker import get_dependency_checker checker = get_dependency_checker() summary = checker.get_summary() # 主框架 main_frame = ttk.Frame(self.dialog, padding="15") main_frame.pack(fill=tk.BOTH, expand=True) # 标题 ttk.Label(main_frame, text="系统依赖状态", font=('Arial', 14, 'bold')).pack(anchor=tk.W, pady=(0, 10)) # 摘要信息 summary_frame = ttk.LabelFrame(main_frame, text="摘要", padding="10") summary_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(summary_frame, text=f"操作系统: {summary['os_type'].upper()}").pack(anchor=tk.W) ttk.Label(summary_frame, text=f"总依赖项: {summary['total']} | 已安装: {summary['complete']} | 缺失: {summary['required_missing']}", foreground='green' if summary['required_missing'] == 0 else 'red').pack(anchor=tk.W) if summary['required_missing'] > 0: ttk.Label(summary_frame, text=f"⚠ 有 {summary['required_missing']} 个必需依赖未安装,部分功能可能不可用", foreground='red').pack(anchor=tk.W, pady=(5, 0)) # 创建 notebook 标签页 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) # 文件系统页 fs_frame = ttk.Frame(notebook, padding="10") notebook.add(fs_frame, text="文件系统") self._create_category_frame(fs_frame, summary['categories']['filesystem']) # 分区/LVM/RAID 页 storage_frame = ttk.Frame(notebook, padding="10") notebook.add(storage_frame, text="分区/LVM/RAID") self._create_category_frame(storage_frame, {**summary['categories']['partition'], **summary['categories']['lvm'], **summary['categories']['raid']}) # 其他工具页 other_frame = ttk.Frame(notebook, padding="10") notebook.add(other_frame, text="其他工具") self._create_category_frame(other_frame, summary['categories']['other']) # 安装指南页 if summary['install_commands']: install_frame = ttk.Frame(notebook, padding="10") notebook.add(install_frame, text="安装指南") self._create_install_frame(install_frame, summary) # 底部按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=5) ttk.Button(btn_frame, text="刷新", command=self._refresh).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="关闭", command=self._on_close).pack(side=tk.RIGHT, padx=5) def _create_category_frame(self, parent, items): """创建分类框架""" canvas = tk.Canvas(parent) scrollbar = ttk.Scrollbar(parent, orient="vertical", command=canvas.yview) scrollable_frame = ttk.Frame(canvas) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw", width=580) canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") for key, status in items.items(): self._create_item_frame(scrollable_frame, status) def _create_item_frame(self, parent, status): """创建单个依赖项框架""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=3) is_ok = status['is_complete'] is_optional = status['optional'] # 状态图标 if is_ok: icon = "✓" color = "green" elif is_optional: icon = "○" color = "orange" else: icon = "✗" color = "red" # 标题行 header_frame = ttk.Frame(frame) header_frame.pack(fill=tk.X) ttk.Label(header_frame, text=icon, foreground=color, font=('Arial', 10, 'bold')).pack(side=tk.LEFT) ttk.Label(header_frame, text=status['name'], font=('Arial', 9, 'bold')).pack(side=tk.LEFT, padx=5) if is_optional: ttk.Label(header_frame, text="(可选)", foreground="gray").pack(side=tk.LEFT) # 详情 detail_frame = ttk.Frame(frame) detail_frame.pack(fill=tk.X, padx=(20, 0)) if is_ok: ttk.Label(detail_frame, text=f"已安装命令: {', '.join(status['installed'])}", foreground="green").pack(anchor=tk.W) else: if status['missing']: ttk.Label(detail_frame, text=f"缺失命令: {', '.join(status['missing'])}", foreground="red").pack(anchor=tk.W) ttk.Label(detail_frame, text=f"安装包: {status['package']}", foreground="blue").pack(anchor=tk.W) ttk.Separator(frame, orient='horizontal').pack(fill=tk.X, pady=5) def _create_install_frame(self, parent, summary): """创建安装指南框架""" # 安装命令 cmd_frame = ttk.LabelFrame(parent, text="一键安装命令", padding="10") cmd_frame.pack(fill=tk.X, pady=(0, 10)) os_type = summary['os_type'] if os_type == 'centos': cmd = "sudo yum install -y " + " ".join(summary['install_commands'].keys()) elif os_type == 'arch': cmd = "sudo pacman -Sy --needed " + " ".join(summary['install_commands'].keys()) elif os_type == 'ubuntu': cmd = "sudo apt-get install -y " + " ".join(summary['install_commands'].keys()) else: cmd = "# 请根据您的发行版安装以下包: " + ", ".join(summary['install_commands'].keys()) text_widget = tk.Text(cmd_frame, height=3, wrap=tk.WORD, font=('Consolas', 10)) text_widget.pack(fill=tk.X) text_widget.insert('1.0', cmd) text_widget.config(state='disabled') # 包详情 detail_frame = ttk.LabelFrame(parent, text="包详情", padding="10") detail_frame.pack(fill=tk.BOTH, expand=True) canvas = tk.Canvas(detail_frame) scrollbar = ttk.Scrollbar(detail_frame, orient="vertical", command=canvas.yview) scrollable_frame = ttk.Frame(canvas) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw", width=550) canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") for pkg, names in summary['install_commands'].items(): ttk.Label(scrollable_frame, text=f"• {pkg}", font=('Arial', 9, 'bold')).pack(anchor=tk.W) ttk.Label(scrollable_frame, text=f" 支持: {', '.join(names)}", foreground="gray").pack(anchor=tk.W, padx=(15, 0)) def _refresh(self): """刷新检查""" from dependency_checker import get_dependency_checker # 清除缓存 checker = get_dependency_checker() checker._cache = {} # 重新创建界面 for widget in self.dialog.winfo_children(): widget.destroy() self._create_widgets() def _on_close(self): """关闭对话框""" self.dialog.destroy() def wait_for_result(self): """等待对话框关闭""" self.dialog.wait_window() 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("500x500") 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 = 500 height = 500 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)) ttk.Label(warning_frame, text="⚠️ 警告", font=('Arial', 16, 'bold'), foreground='red').pack(anchor=tk.W) # 设备信息 info_frame = ttk.LabelFrame(main_frame, text="设备信息", padding="10") info_frame.pack(fill=tk.X, pady=(0, 15)) ttk.Label(info_frame, text=f"设备路径: {self.device_path}", font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=f"设备类型: {self.device_info.get('type', 'Unknown')}", font=('Arial', 9)).pack(anchor=tk.W, pady=2) # 显示文件系统信息 fstype = self.device_info.get('fstype', '') if fstype: ttk.Label(info_frame, text=f"当前文件系统: {fstype}", font=('Arial', 9)).pack(anchor=tk.W, pady=2) size = self.device_info.get('size', 'Unknown') ttk.Label(info_frame, text=f"设备大小: {size}", font=('Arial', 9)).pack(anchor=tk.W, pady=2) # 危险警告 danger_frame = ttk.Frame(main_frame) danger_frame.pack(fill=tk.X, pady=(0, 15)) ttk.Label(danger_frame, text="此操作将永久删除设备上的所有数据!", font=('Arial', 10, 'bold'), foreground='red').pack(anchor=tk.W, pady=5) ttk.Label(danger_frame, text="• 所有数据将无法恢复\n• 请确保已备份重要数据", foreground='red').pack(anchor=tk.W, pady=5) # 文件系统选择 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) # 按钮 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): 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("500x500") self.dialog.minsize(450, 400) self.dialog.transient(parent) self.dialog.grab_set() self._center_window() self._create_widgets() def _center_window(self): """居中窗口""" self.dialog.update_idletasks() width = 500 height = 500 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) # 警告标题 ttk.Label(main_frame, text="⚠️ 确认删除 RAID 阵列", font=('Arial', 14, 'bold'), foreground='red').pack(anchor=tk.W, pady=(0, 10)) # 阵列信息 info_frame = ttk.LabelFrame(main_frame, text="阵列信息", padding="10") info_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(info_frame, text=f"阵列名称: {self.array_path}", font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=f"阵列类型: RAID {self.array_data.get('level', 'Unknown')}", font=('Arial', 9)).pack(anchor=tk.W, pady=2) ttk.Label(info_frame, text=f"阵列大小: {self.array_data.get('size', 'Unknown')}", font=('Arial', 9)).pack(anchor=tk.W, pady=2) # 成员设备 member_frame = ttk.LabelFrame(main_frame, text="成员设备", padding="10") member_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) for dev in self.member_devices: ttk.Label(member_frame, text=f"• {dev}", font=('Arial', 9)).pack(anchor=tk.W, pady=1) # 危险警告 danger_frame = ttk.Frame(main_frame) danger_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(danger_frame, text="⚠️ 删除操作将:", font=('Arial', 10, 'bold'), foreground='red').pack(anchor=tk.W, pady=5) warnings = [ "• 停止并删除 RAID 阵列", "• 清除成员设备上的 RAID 超级块", "• 从配置文件中移除阵列配置", "• 所有数据将永久丢失且无法恢复" ] for warning in warnings: ttk.Label(danger_frame, text=warning, foreground='red').pack(anchor=tk.W, pady=2) # 按钮 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): self.result = True self.dialog.destroy() def _on_cancel(self): self.result = False 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("550x550") self.dialog.minsize(500, 500) self.dialog.transient(parent) self.dialog.grab_set() self._center_window() self._create_widgets() def _center_window(self): """居中窗口""" self.dialog.update_idletasks() width = 550 height = 550 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) # 标题 ttk.Label(main_frame, text=f"在 {self.disk_path} 上创建分区", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(0, 10)) # 磁盘信息 info_frame = ttk.LabelFrame(main_frame, text="磁盘信息", padding="10") info_frame.pack(fill=tk.X, pady=(0, 10)) total_gb = self.total_disk_mib / 1024 available_gb = self.max_available_mib / 1024 ttk.Label(info_frame, text=f"磁盘总大小: {total_gb:.2f} GB").pack(anchor=tk.W) ttk.Label(info_frame, text=f"最大可用空间: {available_gb:.2f} GB", foreground='green' if available_gb > 1 else 'red').pack(anchor=tk.W) # 分区表类型选择 part_type_frame = ttk.LabelFrame(main_frame, text="分区表类型", padding="10") part_type_frame.pack(fill=tk.X, pady=(0, 10)) self.part_type_var = tk.StringVar(value="gpt") ttk.Radiobutton(part_type_frame, text="GPT (推荐,支持2TB以上磁盘)", variable=self.part_type_var, value="gpt").pack(anchor=tk.W, pady=2) ttk.Radiobutton(part_type_frame, text="MBR (传统格式,兼容性更好)", 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, 10)) # 使用全部空间选项 self.use_max_var = tk.BooleanVar(value=False) ttk.Checkbutton(size_frame, text="使用全部可用空间", variable=self.use_max_var, command=self._toggle_size_input).pack(anchor=tk.W, pady=5) # 大小输入 size_input_frame = ttk.Frame(size_frame) size_input_frame.pack(fill=tk.X, pady=5) ttk.Label(size_input_frame, text="大小 (GB):").pack(side=tk.LEFT) self.size_var = tk.DoubleVar(value=min(available_gb, 100)) # 使用 tk.Spinbox 兼容旧版 tkinter (Python 3.6) try: self.size_spin = tk.Spinbox(size_input_frame, from_=0.1, to=available_gb, textvariable=self.size_var, width=10, increment=0.1) except AttributeError: # 如果 tk.Spinbox 也不可用,使用 Entry 替代 self.size_spin = tk.Entry(size_input_frame, textvariable=self.size_var, width=10) self.size_spin.pack(side=tk.LEFT, padx=5) # 滑块 self.size_slider = ttk.Scale(size_frame, from_=0, to=available_gb, orient=tk.HORIZONTAL, length=400) self.size_slider.set(min(available_gb, 100)) self.size_slider.pack(fill=tk.X, pady=5) # 百分比显示 self.percent_var = tk.StringVar(value="0%") ttk.Label(size_frame, textvariable=self.percent_var, foreground='gray').pack(anchor=tk.E) # 绑定更新事件 self.size_var.trace('w', self._on_size_change) self.size_slider.configure(command=self._on_slider_change) # 初始状态 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 _on_size_change(self, *args): """大小输入变化时更新滑块""" try: value = self.size_var.get() self.size_slider.set(value) self._update_percent(value) except: pass def _on_slider_change(self, value): """滑块变化时更新输入""" try: self.size_var.set(float(value)) self._update_percent(float(value)) except: pass def _update_percent(self, value): """更新百分比显示""" if self.max_available_mib > 0: percent = (value * 1024 / self.max_available_mib) * 100 self.percent_var.set(f"{percent:.1f}%") def _toggle_size_input(self): """切换大小输入状态""" if self.use_max_var.get(): # ttk.Scale 在某些旧版本 Tkinter 中不支持 state 选项 try: self.size_slider.configure(state=tk.DISABLED) except tk.TclError: pass # 忽略不支持的选项 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) else: try: self.size_slider.configure(state=tk.NORMAL) except tk.TclError: pass # 忽略不支持的选项 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, 'use_max_space': use_max_space, 'total_disk_mib': self.total_disk_mib } self.dialog.destroy() def _on_cancel(self): self.dialog.destroy() def wait_for_result(self): self.dialog.wait_window() return self.result