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

View File

@@ -22,6 +22,11 @@ from occupation_resolver_tkinter import OccupationResolver
# 导入自定义对话框
from dialogs_tkinter import (CreatePartitionDialog, MountDialog, CreateRaidDialog,
CreatePvDialog, CreateVgDialog, CreateLvDialog)
# 导入 SMART 对话框
from dialogs_smart import SmartInfoDialog, SmartOverviewDialog
from smart_monitor import SmartMonitor
# 导入增强对话框
from dialogs_enhanced import EnhancedFormatDialog, EnhancedRaidDeleteDialog, EnhancedPartitionDialog
class MainWindow:
@@ -71,9 +76,20 @@ class MainWindow:
def _create_widgets(self):
"""创建界面元素"""
# 顶部按钮栏
btn_frame = ttk.Frame(self.main_frame)
btn_frame.pack(fill=tk.X, pady=(0, 5))
# 刷新按钮
self.refresh_btn = ttk.Button(self.main_frame, text="刷新数据", command=self.refresh_all_info)
self.refresh_btn.pack(fill=tk.X, pady=(0, 5))
self.refresh_btn = ttk.Button(btn_frame, text="刷新数据", command=self.refresh_all_info)
self.refresh_btn.pack(side=tk.LEFT, padx=(0, 5))
# SMART 监控按钮
self.smart_btn = ttk.Button(btn_frame, text="SMART 监控", command=self._show_smart_overview)
self.smart_btn.pack(side=tk.LEFT, padx=(0, 5))
# 菜单栏
self._create_menu()
# Notebook (Tab 控件)
self.notebook = ttk.Notebook(self.main_frame)
@@ -107,6 +123,38 @@ class MainWindow:
self.log_text = ScrolledText(log_frame, height=8, state=tk.DISABLED)
self.log_text.pack(fill=tk.BOTH, expand=True)
def _create_menu(self):
"""创建菜单栏"""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# 工具菜单
tools_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="工具", menu=tools_menu)
tools_menu.add_command(label="SMART 监控", command=self._show_smart_overview)
tools_menu.add_separator()
tools_menu.add_command(label="刷新数据", command=self.refresh_all_info)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="关于", command=self._show_about)
def _show_about(self):
"""显示关于对话框"""
messagebox.showinfo(
"关于",
"Linux 存储管理工具\n\n"
"版本: 2.0 (Tkinter 版)\n"
"适配低版本系统 (CentOS 8+)\n\n"
"功能:\n"
"- 块设备管理\n"
"- RAID 管理\n"
"- LVM 管理\n"
"- SMART 监控\n\n"
"使用 Python 3.6+ 和 Tkinter 构建"
)
def _create_block_devices_tree(self):
"""创建块设备 Treeview"""
# 创建框架
@@ -359,6 +407,10 @@ class MainWindow:
menu.add_command(label=f"擦除分区表 {device_path}...",
command=lambda: self._handle_wipe_partition_table(device_path))
menu.add_separator()
# SMART 信息
menu.add_command(label=f"查看 SMART 信息 {device_path}",
command=lambda: self._handle_show_smart(device_path))
menu.add_separator()
# 分区操作
if device_type == 'part':
@@ -373,7 +425,24 @@ class MainWindow:
menu.add_command(label=f"删除分区 {device_path}",
command=lambda: self._handle_delete_partition(device_path))
menu.add_command(label=f"格式化分区 {device_path}...",
command=lambda: self._handle_format_partition(device_path))
command=lambda dp=device_path, dd=dev_data: self._handle_format_partition(dp, dd))
# 文件系统检查和修复
fstype = dev_data.get('fstype', '')
if fstype:
fs_menu = tk.Menu(menu, tearoff=0)
# 根据文件系统类型添加不同的检查/修复选项
if fstype.startswith('ext'):
fs_menu.add_command(label=f"检查文件系统 (fsck) {device_path}",
command=lambda dp=device_path, ft=fstype: self._handle_fsck(dp, ft))
fs_menu.add_command(label=f"调整文件系统大小 (resize2fs) {device_path}",
command=lambda dp=device_path, ft=fstype: self._handle_resize2fs(dp, ft))
elif fstype == 'xfs':
fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {device_path}",
command=lambda dp=device_path: self._handle_xfs_repair(dp))
if fs_menu.index(tk.END) is not None:
menu.add_cascade(label=f"文件系统工具 {device_path}", menu=fs_menu)
menu.add_separator()
# Loop 设备操作
@@ -459,9 +528,26 @@ class MainWindow:
menu.add_command(label=f"停止阵列 {array_path}",
command=lambda: self._handle_stop_raid_array(array_path))
menu.add_command(label=f"删除阵列 {array_path}",
command=lambda: self._handle_delete_active_raid_array(array_path, member_devices, array_uuid))
command=lambda ap=array_path, md=member_devices, au=array_uuid, ad=array_data:
self._handle_delete_active_raid_array(ap, md, au))
menu.add_command(label=f"格式化阵列 {array_path}...",
command=lambda: self._handle_format_raid_array(array_path))
# 文件系统检查和修复
dev_details = self.system_manager.get_device_details_by_path(array_path)
fstype = dev_details.get('fstype', '') if dev_details else ''
if fstype:
fs_menu = tk.Menu(menu, tearoff=0)
if fstype.startswith('ext'):
fs_menu.add_command(label=f"检查文件系统 (fsck) {array_path}",
command=lambda dp=array_path, ft=fstype: self._handle_fsck(dp, ft))
fs_menu.add_command(label=f"调整文件系统大小 (resize2fs) {array_path}",
command=lambda dp=array_path, ft=fstype: self._handle_resize2fs(dp, ft))
elif fstype == 'xfs':
fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {array_path}",
command=lambda dp=array_path: self._handle_xfs_repair(dp))
if fs_menu.index(tk.END) is not None:
menu.add_cascade(label=f"文件系统工具 {array_path}", menu=fs_menu)
if menu.index(tk.END) is not None:
self._show_context_menu(menu, event.x_root, event.y_root)
@@ -535,7 +621,24 @@ class MainWindow:
menu.add_command(label=f"删除逻辑卷 {lv_name}",
command=lambda: self._handle_delete_lv(lv_name, vg_name))
menu.add_command(label=f"格式化逻辑卷 {lv_name}...",
command=lambda: self._handle_format_partition(lv_path))
command=lambda lp=lv_path, ln=lv_name: self._handle_format_partition(
lp, {'name': ln, 'path': lp}))
# 文件系统检查和修复
dev_details = self.system_manager.get_device_details_by_path(lv_path)
fstype = dev_details.get('fstype', '') if dev_details else ''
if fstype:
fs_menu = tk.Menu(menu, tearoff=0)
if fstype.startswith('ext'):
fs_menu.add_command(label=f"检查文件系统 (fsck) {lv_path}",
command=lambda dp=lv_path, ft=fstype: self._handle_fsck(dp, ft))
fs_menu.add_command(label=f"调整文件系统大小 (resize2fs) {lv_path}",
command=lambda dp=lv_path, ft=fstype: self._handle_resize2fs(dp, ft))
elif fstype == 'xfs':
fs_menu.add_command(label=f"修复文件系统 (xfs_repair) {lv_path}",
command=lambda dp=lv_path: self._handle_xfs_repair(dp))
if fs_menu.index(tk.END) is not None:
menu.add_cascade(label=f"文件系统工具 {lv_path}", menu=fs_menu)
if menu.index(tk.END) is not None:
self._show_context_menu(menu, event.x_root, event.y_root)
@@ -580,6 +683,41 @@ class MainWindow:
"""处理卸载 loop 设备"""
if self.disk_ops.unmount_loop_device(device_path):
self.refresh_all_info()
def _handle_show_smart(self, device_path):
"""显示指定设备的 SMART 信息"""
monitor = SmartMonitor()
if not monitor.is_available():
messagebox.showwarning("警告",
"smartctl 工具未安装。\n"
"请安装 smartmontools:\n"
" CentOS/RHEL: sudo yum install smartmontools\n"
" Ubuntu/Debian: sudo apt-get install smartmontools")
return
smart_info = monitor.get_disk_smart_info(device_path)
if smart_info:
SmartInfoDialog(self.root, device_path, smart_info)
else:
messagebox.showerror("错误",
f"无法获取 {device_path} 的 SMART 信息。\n"
f"可能的原因:\n"
f"1. 设备不支持 SMART\n"
f"2. 需要 root 权限\n"
f"3. 设备未正确连接")
def _show_smart_overview(self):
"""显示 SMART 总览"""
monitor = SmartMonitor()
if not monitor.is_available():
messagebox.showwarning("警告",
"smartctl 工具未安装。\n"
"请安装 smartmontools:\n"
" CentOS/RHEL: sudo yum install smartmontools\n"
" Ubuntu/Debian: sudo apt-get install smartmontools")
return
SmartOverviewDialog(self.root)
def _handle_wipe_partition_table(self, device_path):
"""处理擦除物理盘分区表"""
@@ -652,7 +790,8 @@ class MainWindow:
max_available_mib = max(0.0, total_disk_mib - 1.0)
start_position_mib = 1.0
dialog = CreatePartitionDialog(self.root, disk_path, total_disk_mib, max_available_mib)
# 使用增强型分区创建对话框(带滑块可视化)
dialog = EnhancedPartitionDialog(self.root, disk_path, total_disk_mib, max_available_mib)
info = dialog.wait_for_result()
if info:
@@ -684,9 +823,101 @@ class MainWindow:
if self.disk_ops.delete_partition(device_path):
self.refresh_all_info()
def _handle_format_partition(self, device_path):
"""处理格式化分区"""
self.disk_ops.format_partition(device_path)
def _handle_format_partition(self, device_path, dev_data=None):
"""处理格式化分区 - 使用增强型对话框"""
# 获取设备信息
if dev_data is None:
dev_data = self.system_manager.get_device_details_by_path(device_path)
# 使用增强型格式化对话框
dialog = EnhancedFormatDialog(self.root, device_path, dev_data or {})
fs_type = dialog.wait_for_result()
if fs_type:
# 执行格式化
self.disk_ops.format_partition(device_path, fs_type)
def _handle_fsck(self, device_path, fstype):
"""检查/修复 ext 文件系统"""
# 检查设备是否已挂载
mount_point = self.system_manager.get_mountpoint_for_device(device_path)
if mount_point and mount_point != 'N/A':
messagebox.showwarning("警告", f"设备 {device_path} 已挂载到 {mount_point}\n请先卸载后再执行文件系统检查。")
return
if not messagebox.askyesno("确认",
f"即将对 {device_path} ({fstype}) 执行文件系统检查。\n"
f"命令: fsck -f -y {device_path}\n\n"
f"注意:此操作可能需要较长时间,请勿中断。",
default=messagebox.NO):
return
logger.info(f"开始检查文件系统: {device_path} ({fstype})")
success, stdout, stderr = self.lvm_ops._execute_shell_command(
["fsck", "-f", "-y", device_path],
f"检查文件系统 {device_path} 失败"
)
if success:
messagebox.showinfo("成功", f"文件系统检查完成:\n{stdout[:500] if stdout else '无输出'}")
else:
messagebox.showerror("错误", f"文件系统检查失败:\n{stderr[:500] if stderr else '未知错误'}")
self.refresh_all_info()
def _handle_xfs_repair(self, device_path):
"""修复 XFS 文件系统"""
# 检查设备是否已挂载
mount_point = self.system_manager.get_mountpoint_for_device(device_path)
if mount_point and mount_point != 'N/A':
messagebox.showwarning("警告", f"设备 {device_path} 已挂载到 {mount_point}\n请先卸载后再执行文件系统修复。")
return
if not messagebox.askyesno("确认",
f"即将对 {device_path} (XFS) 执行文件系统修复。\n"
f"命令: xfs_repair {device_path}\n\n"
f"警告:此操作会尝试修复文件系统错误,但可能无法恢复所有数据。\n"
f"建议先备份重要数据。",
default=messagebox.NO):
return
logger.info(f"开始修复 XFS 文件系统: {device_path}")
success, stdout, stderr = self.lvm_ops._execute_shell_command(
["xfs_repair", device_path],
f"修复 XFS 文件系统 {device_path} 失败"
)
if success:
messagebox.showinfo("成功", f"XFS 文件系统修复完成:\n{stdout[:500] if stdout else '无输出'}")
else:
messagebox.showerror("错误", f"XFS 文件系统修复失败:\n{stderr[:500] if stderr else '未知错误'}")
self.refresh_all_info()
def _handle_resize2fs(self, device_path, fstype):
"""调整 ext 文件系统大小"""
# 检查设备是否已挂载
mount_point = self.system_manager.get_mountpoint_for_device(device_path)
if mount_point and mount_point != 'N/A':
messagebox.showwarning("警告", f"设备 {device_path} 已挂载到 {mount_point}\n请先卸载后再执行调整大小操作。")
return
if not messagebox.askyesno("确认",
f"即将对 {device_path} ({fstype}) 强制调整文件系统大小以匹配分区大小。\n"
f"命令: resize2fs -f {device_path}\n\n"
f"注意:此操作会强制调整文件系统大小,请确保分区大小已正确设置。",
default=messagebox.NO):
return
logger.info(f"开始调整文件系统大小: {device_path} ({fstype})")
success, stdout, stderr = self.lvm_ops._execute_shell_command(
["resize2fs", "-f", device_path],
f"调整文件系统大小 {device_path} 失败"
)
if success:
messagebox.showinfo("成功", f"文件系统大小调整完成:\n{stdout[:500] if stdout else '无输出'}")
else:
messagebox.showerror("错误", f"调整文件系统大小失败:\n{stderr[:500] if stderr else '未知错误'}")
self.refresh_all_info()
def on_disk_formatting_finished(self, success, device_path, stdout, stderr):
"""接收格式化完成回调并刷新界面"""
@@ -791,10 +1022,29 @@ class MainWindow:
if self.raid_ops.stop_raid_array(array_path):
self.refresh_all_info()
def _handle_delete_active_raid_array(self, array_path, member_devices, uuid):
"""处理删除活动的 RAID 阵列"""
if self.raid_ops.delete_active_raid_array(array_path, member_devices, uuid):
self.refresh_all_info()
def _handle_delete_active_raid_array(self, array_path, member_devices, uuid, array_data=None):
"""处理删除活动的 RAID 阵列 - 使用增强型对话框"""
# 获取阵列详情(如果未提供)
if array_data is None:
array_data = {}
try:
raid_arrays = self.system_manager.get_mdadm_arrays()
for array in raid_arrays:
if array.get('device') == array_path:
array_data = array
break
except:
pass
# 使用增强型删除对话框
dialog = EnhancedRaidDeleteDialog(
self.root, array_path, array_data, member_devices
)
if dialog.wait_for_result():
# 执行删除
if self.raid_ops.delete_active_raid_array(array_path, member_devices, uuid):
self.refresh_all_info()
def _handle_delete_configured_raid_array(self, uuid):
"""处理删除停止状态 RAID 阵列的配置文件条目"""
@@ -1051,3 +1301,10 @@ class MainWindow:
self.refresh_all_info()
else:
messagebox.showerror("删除卷组失败", f"删除卷组 {vg_name} 失败,请检查日志。")
if __name__ == "__main__":
# 创建主窗口
root = tk.Tk()
app = MainWindow(root)
root.mainloop()