Files
diskmanager/mainwindow.py
2026-02-02 04:20:00 +08:00

326 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# mainwindow.py
import sys
import logging
from PySide6.QtWidgets import (QApplication, QMainWindow, QTreeWidgetItem,
QMessageBox, QHeaderView, QMenu, QInputDialog)
from PySide6.QtCore import Qt, QPoint
from ui_form import Ui_MainWindow
from system_info import SystemInfoManager
from logger_config import setup_logging, logger
from disk_operations import DiskOperations
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
setup_logging(self.ui.logOutputTextEdit)
logger.info("应用程序启动。")
self.system_manager = SystemInfoManager()
self.disk_ops = DiskOperations()
if hasattr(self.ui, 'refreshButton'):
self.ui.refreshButton.clicked.connect(self.refresh_all_info)
else:
logger.warning("Warning: refreshButton not found in UI. Please add it in form.ui and regenerate ui_form.py.")
self.ui.treeWidget_block_devices.setContextMenuPolicy(Qt.CustomContextMenu)
self.ui.treeWidget_block_devices.customContextMenuRequested.connect(self.show_block_device_context_menu)
self.refresh_all_info()
logger.info("所有设备信息已初始化加载。")
def refresh_all_info(self):
"""
刷新所有设备信息块设备、RAID和LVM。
"""
logger.info("开始刷新所有设备信息...")
self.refresh_block_devices_info()
self.refresh_raid_info()
self.refresh_lvm_info()
logger.info("所有设备信息刷新完成。")
def refresh_block_devices_info(self):
"""
刷新块设备信息并显示在 QTreeWidget 中。
"""
self.ui.treeWidget_block_devices.clear()
columns = [
("设备名", 'name'),
("类型", 'type'),
("大小", 'size'),
("挂载点", 'mountpoint'),
("文件系统", 'fstype'),
("只读", 'ro'),
("UUID", 'uuid'),
("PARTUUID", 'partuuid'),
("厂商", 'vendor'),
("型号", 'model'),
("序列号", 'serial'),
("主次号", 'maj:min'),
("父设备名", 'pkname'),
]
headers = [col[0] for col in columns]
self.field_keys = [col[1] for col in columns]
self.ui.treeWidget_block_devices.setColumnCount(len(headers))
self.ui.treeWidget_block_devices.setHeaderLabels(headers)
for i in range(len(headers)):
self.ui.treeWidget_block_devices.header().setSectionResizeMode(i, QHeaderView.ResizeToContents)
try:
devices = self.system_manager.get_block_devices()
for dev in devices:
self._add_device_to_tree(self.ui.treeWidget_block_devices, dev)
for i in range(len(headers)):
self.ui.treeWidget_block_devices.resizeColumnToContents(i)
logger.info("块设备信息刷新成功。")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新块设备信息失败: {e}")
logger.error(f"刷新块设备信息失败: {e}")
def _add_device_to_tree(self, parent_item, dev_data):
"""
辅助函数,将单个设备及其子设备添加到 QTreeWidget。
parent_item 可以是 QTreeWidget 本身,也可以是另一个 QTreeWidgetItem。
"""
item = QTreeWidgetItem(parent_item)
for i, key in enumerate(self.field_keys):
value = dev_data.get(key)
if key == 'ro':
item.setText(i, "" if value else "")
elif value is None:
item.setText(i, "")
else:
item.setText(i, str(value))
item.setData(0, Qt.UserRole, dev_data)
if 'children' in dev_data:
for child in dev_data['children']:
self._add_device_to_tree(item, child)
item.setExpanded(True)
def refresh_raid_info(self):
"""
刷新RAID阵列信息并显示在 QTreeWidget 中。
"""
self.ui.treeWidget_raid.clear()
# 定义RAID显示列头
raid_headers = [
"阵列设备", "级别", "状态", "大小", "活动设备", "失败设备", "备用设备",
"总设备数", "UUID", "名称", "Chunk Size"
]
self.ui.treeWidget_raid.setColumnCount(len(raid_headers))
self.ui.treeWidget_raid.setHeaderLabels(raid_headers)
for i in range(len(raid_headers)):
self.ui.treeWidget_raid.header().setSectionResizeMode(i, QHeaderView.ResizeToContents)
try:
raid_arrays = self.system_manager.get_mdadm_arrays()
if not raid_arrays:
item = QTreeWidgetItem(self.ui.treeWidget_raid)
item.setText(0, "未找到RAID阵列。")
logger.info("未找到RAID阵列。")
return
for array in raid_arrays:
array_item = QTreeWidgetItem(self.ui.treeWidget_raid)
array_item.setText(0, array.get('device', 'N/A'))
array_item.setText(1, array.get('level', 'N/A'))
array_item.setText(2, array.get('state', 'N/A'))
array_item.setText(3, array.get('array_size', 'N/A'))
array_item.setText(4, array.get('active_devices', 'N/A'))
array_item.setText(5, array.get('failed_devices', 'N/A'))
array_item.setText(6, array.get('spare_devices', 'N/A'))
array_item.setText(7, array.get('total_devices', 'N/A'))
array_item.setText(8, array.get('uuid', 'N/A'))
array_item.setText(9, array.get('name', 'N/A'))
array_item.setText(10, array.get('chunk_size', 'N/A'))
array_item.setExpanded(True)
# 添加成员设备作为子节点
for member in array.get('member_devices', []):
member_item = QTreeWidgetItem(array_item)
member_item.setText(0, f" {member.get('device_path', 'N/A')}") # 缩进显示
member_item.setText(1, f"成员")
member_item.setText(2, member.get('state', 'N/A'))
# 其他列留空或填充N/A因为是成员设备的特有信息
member_item.setText(3, f"RaidDevice: {member.get('raid_device', 'N/A')}")
for i in range(len(raid_headers)):
self.ui.treeWidget_raid.resizeColumnToContents(i)
logger.info("RAID阵列信息刷新成功。")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新RAID阵列信息失败: {e}")
logger.error(f"刷新RAID阵列信息失败: {e}")
def refresh_lvm_info(self):
"""
刷新LVM信息PVs, VGs, LVs并显示在 QTreeWidget 中。
"""
self.ui.treeWidget_lvm.clear()
# 定义LVM显示列头
lvm_headers = ["名称", "大小", "属性", "UUID", "关联", "空闲/已用", "路径"]
self.ui.treeWidget_lvm.setColumnCount(len(lvm_headers))
self.ui.treeWidget_lvm.setHeaderLabels(lvm_headers)
for i in range(len(lvm_headers)):
self.ui.treeWidget_lvm.header().setSectionResizeMode(i, QHeaderView.ResizeToContents)
try:
lvm_data = self.system_manager.get_lvm_info()
if not lvm_data.get('pvs') and not lvm_data.get('vgs') and not lvm_data.get('lvs'):
item = QTreeWidgetItem(self.ui.treeWidget_lvm)
item.setText(0, "未找到LVM信息。")
logger.info("未找到LVM信息。")
return
# 物理卷 (PVs)
pv_root_item = QTreeWidgetItem(self.ui.treeWidget_lvm)
pv_root_item.setText(0, "物理卷 (PVs)")
pv_root_item.setExpanded(True)
if lvm_data.get('pvs'):
for pv in lvm_data['pvs']:
pv_item = QTreeWidgetItem(pv_root_item)
pv_item.setText(0, pv.get('pv_name', 'N/A'))
pv_item.setText(1, pv.get('pv_size', 'N/A'))
pv_item.setText(2, pv.get('pv_attr', 'N/A'))
pv_item.setText(3, pv.get('pv_uuid', 'N/A'))
pv_item.setText(4, f"VG: {pv.get('vg_name', 'N/A')}")
pv_item.setText(5, f"空闲: {pv.get('pv_free', 'N/A')}")
pv_item.setText(6, pv.get('pv_fmt', 'N/A')) # PV格式
else:
item = QTreeWidgetItem(pv_root_item)
item.setText(0, "未找到物理卷。")
# 卷组 (VGs)
vg_root_item = QTreeWidgetItem(self.ui.treeWidget_lvm)
vg_root_item.setText(0, "卷组 (VGs)")
vg_root_item.setExpanded(True)
if lvm_data.get('vgs'):
for vg in lvm_data['vgs']:
vg_item = QTreeWidgetItem(vg_root_item)
vg_item.setText(0, vg.get('vg_name', 'N/A'))
vg_item.setText(1, vg.get('vg_size', 'N/A'))
vg_item.setText(2, vg.get('vg_attr', 'N/A'))
vg_item.setText(3, vg.get('vg_uuid', 'N/A'))
vg_item.setText(4, f"PVs: {vg.get('pv_count', 'N/A')}, LVs: {vg.get('lv_count', 'N/A')}")
vg_item.setText(5, f"空闲: {vg.get('vg_free', 'N/A')}, 已分配: {vg.get('vg_alloc_percent', 'N/A')}%")
vg_item.setText(6, vg.get('vg_fmt', 'N/A')) # VG格式
else:
item = QTreeWidgetItem(vg_root_item)
item.setText(0, "未找到卷组。")
# 逻辑卷 (LVs)
lv_root_item = QTreeWidgetItem(self.ui.treeWidget_lvm)
lv_root_item.setText(0, "逻辑卷 (LVs)")
lv_root_item.setExpanded(True)
if lvm_data.get('lvs'):
for lv in lvm_data['lvs']:
lv_item = QTreeWidgetItem(lv_root_item)
lv_item.setText(0, lv.get('lv_name', 'N/A'))
lv_item.setText(1, lv.get('lv_size', 'N/A'))
lv_item.setText(2, lv.get('lv_attr', 'N/A'))
lv_item.setText(3, lv.get('lv_uuid', 'N/A'))
lv_item.setText(4, f"VG: {lv.get('vg_name', 'N/A')}, Origin: {lv.get('origin', 'N/A')}")
lv_item.setText(5, f"快照: {lv.get('snap_percent', 'N/A')}%")
lv_item.setText(6, lv.get('lv_path', 'N/A'))
else:
item = QTreeWidgetItem(lv_root_item)
item.setText(0, "未找到逻辑卷。")
for i in range(len(lvm_headers)):
self.ui.treeWidget_lvm.resizeColumnToContents(i)
logger.info("LVM信息刷新成功。")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新LVM信息失败: {e}")
logger.error(f"刷新LVM信息失败: {e}")
def show_block_device_context_menu(self, pos: QPoint):
"""
显示块设备列表的右键上下文菜单。
"""
item = self.ui.treeWidget_block_devices.itemAt(pos)
if item:
dev_data = item.data(0, Qt.UserRole)
if not dev_data:
logger.warning(f"无法获取设备 {item.text(0)} 的详细数据。")
return
device_name = dev_data.get('name')
device_type = dev_data.get('type')
mount_point = dev_data.get('mountpoint')
menu = QMenu(self)
if device_type in ['part', 'disk']:
if not mount_point or mount_point == '' or mount_point == 'N/A':
mount_action = menu.addAction(f"挂载 {device_name}...")
mount_action.triggered.connect(lambda: self._handle_mount(device_name))
elif mount_point != '[SWAP]':
unmount_action = menu.addAction(f"卸载 {device_name}")
unmount_action.triggered.connect(lambda: self._handle_unmount(device_name))
if menu.actions():
menu.addSeparator()
if device_type == 'part':
delete_action = menu.addAction(f"删除分区 {device_name}")
delete_action.triggered.connect(lambda: self._handle_delete_partition(device_name))
format_action = menu.addAction(f"格式化分区 {device_name}...")
format_action.triggered.connect(lambda: self._handle_format_partition(device_name))
if menu.actions():
menu.exec(self.ui.treeWidget_block_devices.mapToGlobal(pos))
else:
logger.info(f"设备 {device_name} 没有可用的操作。")
else:
logger.info("右键点击了空白区域。")
def _handle_mount(self, device_name):
"""处理挂载操作并刷新UI。"""
if self.disk_ops.mount_partition(device_name):
self.refresh_all_info()
def _handle_unmount(self, device_name):
"""处理卸载操作并刷新UI。"""
if self.disk_ops.unmount_partition(device_name):
self.refresh_all_info()
def _handle_delete_partition(self, device_name):
"""处理删除分区操作并刷新UI。"""
if self.disk_ops.delete_partition(device_name):
self.refresh_all_info()
def _handle_format_partition(self, device_name):
"""处理格式化分区操作并刷新UI。"""
if self.disk_ops.format_partition(device_name):
self.refresh_all_info()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = MainWindow()
widget.show()
sys.exit(app.exec())