326 lines
14 KiB
Python
326 lines
14 KiB
Python
# 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())
|