构建
This commit is contained in:
521
dialogs_enhanced.py
Normal file
521
dialogs_enhanced.py
Normal 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
|
||||
Reference in New Issue
Block a user