522 lines
19 KiB
Python
522 lines
19 KiB
Python
# 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
|