This commit is contained in:
zj
2026-02-09 17:59:50 +08:00
parent 1a3a4746a3
commit 0112e4d3b1
11 changed files with 3223 additions and 13 deletions

521
dialogs_enhanced.py Normal file
View File

@@ -0,0 +1,521 @@
# 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