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

649 lines
24 KiB
Python
Raw 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_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(
"<Configure>",
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(
"<Configure>",
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