fix bug
This commit is contained in:
@@ -8,6 +8,237 @@ 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:
|
||||
"""增强格式化对话框 - 显示详细警告信息"""
|
||||
|
||||
@@ -19,7 +250,7 @@ class EnhancedFormatDialog:
|
||||
|
||||
self.dialog = tk.Toplevel(parent)
|
||||
self.dialog.title(f"格式化确认 - {device_path}")
|
||||
self.dialog.geometry("500x575")
|
||||
self.dialog.geometry("500x500")
|
||||
self.dialog.minsize(480, 450)
|
||||
self.dialog.transient(parent)
|
||||
self.dialog.grab_set()
|
||||
@@ -28,14 +259,17 @@ class EnhancedFormatDialog:
|
||||
self._create_widgets()
|
||||
|
||||
def _center_window(self):
|
||||
"""居中窗口"""
|
||||
self.dialog.update_idletasks()
|
||||
width = self.dialog.winfo_width()
|
||||
height = self.dialog.winfo_height()
|
||||
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}")
|
||||
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)
|
||||
|
||||
@@ -43,58 +277,45 @@ class EnhancedFormatDialog:
|
||||
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()
|
||||
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))
|
||||
|
||||
# 显示详细信息
|
||||
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', '未挂载')),
|
||||
]
|
||||
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)
|
||||
|
||||
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)
|
||||
# 显示文件系统信息
|
||||
fstype = self.device_info.get('fstype', '')
|
||||
if fstype:
|
||||
ttk.Label(info_frame,
|
||||
text=f"当前文件系统: {fstype}",
|
||||
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
|
||||
|
||||
# 如果有挂载点,高亮显示
|
||||
if label == "挂载点" and value and value != '未挂载':
|
||||
val_label.configure(foreground="orange", font=("Arial", 9, "bold"))
|
||||
size = self.device_info.get('size', 'Unknown')
|
||||
ttk.Label(info_frame,
|
||||
text=f"设备大小: {size}",
|
||||
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
|
||||
|
||||
# 数据风险提示
|
||||
risk_frame = ttk.LabelFrame(main_frame, text="数据风险提示", padding="10")
|
||||
risk_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
# 危险警告
|
||||
danger_frame = ttk.Frame(main_frame)
|
||||
danger_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']}")
|
||||
ttk.Label(danger_frame,
|
||||
text="此操作将永久删除设备上的所有数据!",
|
||||
font=('Arial', 10, 'bold'),
|
||||
foreground='red').pack(anchor=tk.W, pady=5)
|
||||
|
||||
if not risks:
|
||||
risks.append("• 无法获取分区详细信息")
|
||||
|
||||
for risk in risks:
|
||||
ttk.Label(risk_frame, text=risk, foreground="red").pack(
|
||||
anchor=tk.W, pady=2
|
||||
)
|
||||
ttk.Label(danger_frame,
|
||||
text="• 所有数据将无法恢复\n• 请确保已备份重要数据",
|
||||
foreground='red').pack(anchor=tk.W, pady=5)
|
||||
|
||||
# 文件系统选择
|
||||
fs_frame = ttk.LabelFrame(main_frame, text="选择新文件系统", padding="10")
|
||||
@@ -107,19 +328,6 @@ class EnhancedFormatDialog:
|
||||
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)
|
||||
@@ -130,12 +338,6 @@ class EnhancedFormatDialog:
|
||||
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()
|
||||
|
||||
@@ -158,9 +360,9 @@ class EnhancedRaidDeleteDialog:
|
||||
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.title(f"删除 RAID 阵列 - {array_path}")
|
||||
self.dialog.geometry("500x500")
|
||||
self.dialog.minsize(450, 400)
|
||||
self.dialog.transient(parent)
|
||||
self.dialog.grab_set()
|
||||
|
||||
@@ -168,96 +370,62 @@ class EnhancedRaidDeleteDialog:
|
||||
self._create_widgets()
|
||||
|
||||
def _center_window(self):
|
||||
"""居中窗口"""
|
||||
self.dialog.update_idletasks()
|
||||
width = self.dialog.winfo_width()
|
||||
height = self.dialog.winfo_height()
|
||||
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}")
|
||||
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)
|
||||
|
||||
# 危险警告
|
||||
warning_frame = ttk.Frame(main_frame)
|
||||
warning_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
danger_frame = ttk.Frame(main_frame)
|
||||
danger_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
warning_label = ttk.Label(
|
||||
warning_frame,
|
||||
text="⚠ 危险操作:此操作将永久删除 RAID 阵列!",
|
||||
font=("Arial", 13, "bold"),
|
||||
foreground="red"
|
||||
)
|
||||
warning_label.pack()
|
||||
ttk.Label(danger_frame,
|
||||
text="⚠️ 删除操作将:",
|
||||
font=('Arial', 10, 'bold'),
|
||||
foreground='red').pack(anchor=tk.W, pady=5)
|
||||
|
||||
# 阵列详情
|
||||
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')),
|
||||
warnings = [
|
||||
"• 停止并删除 RAID 阵列",
|
||||
"• 清除成员设备上的 RAID 超级块",
|
||||
"• 从配置文件中移除阵列配置",
|
||||
"• 所有数据将永久丢失且无法恢复"
|
||||
]
|
||||
|
||||
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()
|
||||
for warning in warnings:
|
||||
ttk.Label(danger_frame, text=warning,
|
||||
foreground='red').pack(anchor=tk.W, pady=2)
|
||||
|
||||
# 按钮
|
||||
btn_frame = ttk.Frame(main_frame)
|
||||
@@ -266,23 +434,14 @@ class EnhancedRaidDeleteDialog:
|
||||
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")
|
||||
command=self._on_confirm).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
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.result = False
|
||||
self.dialog.destroy()
|
||||
|
||||
def wait_for_result(self):
|
||||
@@ -291,7 +450,7 @@ class EnhancedRaidDeleteDialog:
|
||||
|
||||
|
||||
class EnhancedPartitionDialog:
|
||||
"""增强分区创建对话框 - 可视化滑块选择大小"""
|
||||
"""增强分区创建对话框 - 智能分区建议"""
|
||||
|
||||
def __init__(self, parent, disk_path, total_disk_mib, max_available_mib):
|
||||
self.parent = parent
|
||||
@@ -302,8 +461,8 @@ class EnhancedPartitionDialog:
|
||||
|
||||
self.dialog = tk.Toplevel(parent)
|
||||
self.dialog.title(f"创建分区 - {disk_path}")
|
||||
self.dialog.geometry("550x575")
|
||||
self.dialog.minsize(500, 575)
|
||||
self.dialog.geometry("550x550")
|
||||
self.dialog.minsize(500, 500)
|
||||
self.dialog.transient(parent)
|
||||
self.dialog.grab_set()
|
||||
|
||||
@@ -311,90 +470,99 @@ class EnhancedPartitionDialog:
|
||||
self._create_widgets()
|
||||
|
||||
def _center_window(self):
|
||||
"""居中窗口"""
|
||||
self.dialog.update_idletasks()
|
||||
width = self.dialog.winfo_width()
|
||||
height = self.dialog.winfo_height()
|
||||
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}")
|
||||
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, 15))
|
||||
info_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
total_gb = self.total_disk_mib / 1024
|
||||
max_gb = self.max_available_mib / 1024
|
||||
available_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)
|
||||
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)
|
||||
|
||||
# 分区表类型
|
||||
pt_frame = ttk.LabelFrame(main_frame, text="分区表类型", padding="10")
|
||||
pt_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
# 分区表类型选择
|
||||
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(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)
|
||||
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, 15))
|
||||
size_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
# 使用最大空间复选框
|
||||
self.use_max_var = tk.BooleanVar(value=True)
|
||||
ttk.Checkbutton(size_frame, text="使用最大可用空间",
|
||||
# 使用全部空间选项
|
||||
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)
|
||||
|
||||
# 滑块和输入框容器
|
||||
self.size_control_frame = ttk.Frame(size_frame)
|
||||
self.size_control_frame.pack(fill=tk.X, pady=5)
|
||||
# 大小输入
|
||||
size_input_frame = ttk.Frame(size_frame)
|
||||
size_input_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)
|
||||
ttk.Label(size_input_frame, text="大小 (GB):").pack(side=tk.LEFT)
|
||||
|
||||
# 输入框和标签
|
||||
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_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)
|
||||
|
||||
ttk.Label(input_frame, text="GB").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_label = ttk.Label(input_frame, text="(100%)")
|
||||
self.percent_label.pack(side=tk.LEFT, padx=10)
|
||||
# 百分比显示
|
||||
self.percent_var = tk.StringVar(value="0%")
|
||||
ttk.Label(size_frame, textvariable=self.percent_var,
|
||||
foreground='gray').pack(anchor=tk.E)
|
||||
|
||||
# 可视化空间显示
|
||||
self._create_space_visualizer(main_frame, max_gb)
|
||||
# 绑定更新事件
|
||||
self.size_var.trace('w', self._on_size_change)
|
||||
self.size_slider.configure(command=self._on_slider_change)
|
||||
|
||||
# 初始状态
|
||||
self._toggle_size_input()
|
||||
@@ -405,90 +573,49 @@ class EnhancedPartitionDialog:
|
||||
|
||||
ttk.Button(btn_frame, text="取消",
|
||||
command=self._on_cancel).pack(side=tk.RIGHT, padx=5)
|
||||
ttk.Button(btn_frame, text="创建分区",
|
||||
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_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:
|
||||
gb = float(value)
|
||||
self._update_percent(gb)
|
||||
self._update_visualizer(gb, self.max_available_mib / 1024)
|
||||
self.size_var.set(float(value))
|
||||
self._update_percent(float(value))
|
||||
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):
|
||||
def _update_percent(self, value):
|
||||
"""更新百分比显示"""
|
||||
max_gb = self.max_available_mib / 1024
|
||||
if max_gb > 0:
|
||||
percent = (gb / max_gb) * 100
|
||||
self.percent_label.configure(text=f"({percent:.1f}%)")
|
||||
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():
|
||||
self.size_slider.configure(state=tk.DISABLED)
|
||||
# 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)
|
||||
self._update_visualizer(max_gb, max_gb)
|
||||
else:
|
||||
self.size_slider.configure(state=tk.NORMAL)
|
||||
try:
|
||||
self.size_slider.configure(state=tk.NORMAL)
|
||||
except tk.TclError:
|
||||
pass # 忽略不支持的选项
|
||||
self.size_spin.configure(state=tk.NORMAL)
|
||||
|
||||
def _on_confirm(self):
|
||||
@@ -508,8 +635,8 @@ class EnhancedPartitionDialog:
|
||||
'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
|
||||
'use_max_space': use_max_space,
|
||||
'total_disk_mib': self.total_disk_mib
|
||||
}
|
||||
self.dialog.destroy()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user