Files
diskmanager/dialogs.py
2026-02-02 18:38:41 +08:00

446 lines
18 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.

# dialogs.py
import os
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QComboBox, QDoubleSpinBox,
QPushButton, QFormLayout, QFileDialog,
QMessageBox, QCheckBox, QListWidget, QListWidgetItem)
from PySide6.QtCore import Qt
class CreatePartitionDialog(QDialog):
def __init__(self, parent=None, disk_path="", total_disk_mib=0.0, max_available_mib=0.0):
super().__init__(parent)
self.setWindowTitle("创建分区")
self.setMinimumWidth(300)
self.disk_path = disk_path
self.total_disk_mib = total_disk_mib # 磁盘总大小 (MiB)
self.max_available_mib = max_available_mib # 可用空间 (MiB)
self.partition_table_type_combo = QComboBox()
self.partition_table_type_combo.addItems(["gpt", "msdos"])
self.size_spinbox = QDoubleSpinBox()
self.size_spinbox.setMinimum(0.1) # 最小分区大小 0.1 GB
self.size_spinbox.setMaximum(self.max_available_mib / 1024.0) # 将 MiB 转换为 GB 显示
self.size_spinbox.setSuffix(" GB")
self.size_spinbox.setDecimals(2)
self.use_max_space_checkbox = QCheckBox("使用最大可用空间")
self.use_max_space_checkbox.setChecked(True) # 默认选中
self._setup_ui()
self._connect_signals()
self._initialize_state()
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("磁盘路径:", QLabel(self.disk_path))
form_layout.addRow("分区表类型:", self.partition_table_type_combo)
form_layout.addRow("分区大小:", self.size_spinbox)
form_layout.addRow("", self.use_max_space_checkbox) # 添加复选框
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
self.use_max_space_checkbox.stateChanged.connect(self._toggle_size_input)
def _initialize_state(self):
# 根据默认选中状态设置 spinbox
self._toggle_size_input(self.use_max_space_checkbox.checkState())
def _toggle_size_input(self, state):
if state == Qt.Checked:
self.size_spinbox.setDisabled(True)
self.size_spinbox.setValue(self.size_spinbox.maximum()) # 设置为最大可用 GB
else:
self.size_spinbox.setDisabled(False)
# 如果之前是最大值,取消勾选后,恢复到最小值或一个合理值
if self.size_spinbox.value() == self.size_spinbox.maximum():
self.size_spinbox.setValue(self.size_spinbox.minimum())
def get_partition_info(self):
size_gb = self.size_spinbox.value()
use_max_space = self.use_max_space_checkbox.isChecked()
if not use_max_space and size_gb <= 0:
QMessageBox.warning(self, "输入错误", "分区大小必须大于0。")
return None
if not use_max_space and size_gb > self.max_available_mib / 1024.0:
QMessageBox.warning(self, "输入错误", "分区大小不能超过最大可用空间。")
return None
return {
'disk_path': self.disk_path,
'partition_table_type': self.partition_table_type_combo.currentText(),
'size_gb': size_gb,
'total_disk_mib': self.total_disk_mib, # 传递磁盘总大小 (MiB)
'use_max_space': use_max_space # 返回此标志
}
class MountDialog(QDialog):
def __init__(self, parent=None, device_path=""):
super().__init__(parent)
self.setWindowTitle("挂载分区")
self.setMinimumWidth(300)
self.device_path = device_path
self.mount_point_input = QLineEdit()
self.add_to_fstab_checkbox = QCheckBox("开机自动挂载 (添加到 /etc/fstab)") # 新增
self._setup_ui()
self._connect_signals()
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("设备路径:", QLabel(self.device_path))
form_layout.addRow("挂载点:", self.mount_point_input)
form_layout.addRow("", self.add_to_fstab_checkbox) # 添加复选框
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
# 默认挂载点,可以根据设备名生成
default_mount_point = f"/mnt/{os.path.basename(self.device_path)}"
self.mount_point_input.setText(default_mount_point)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
def get_mount_info(self):
mount_point = self.mount_point_input.text().strip()
add_to_fstab = self.add_to_fstab_checkbox.isChecked()
if not mount_point:
QMessageBox.warning(self, "输入错误", "挂载点不能为空。")
return None
if not mount_point.startswith('/'):
QMessageBox.warning(self, "输入错误", "挂载点必须是绝对路径 (以 '/' 开头)。")
return None
return {
'mount_point': mount_point,
'add_to_fstab': add_to_fstab
}
class CreateRaidDialog(QDialog):
def __init__(self, parent=None, available_devices=None):
super().__init__(parent)
self.setWindowTitle("创建 RAID 阵列")
self.setMinimumWidth(350)
self.available_devices = available_devices if available_devices is not None else []
self.selected_devices = []
self.raid_level_combo = QComboBox()
self.raid_level_combo.addItems(["raid0", "raid1", "raid5", "raid6", "raid10"])
self.chunk_size_spinbox = QDoubleSpinBox()
self.chunk_size_spinbox.setMinimum(4)
self.chunk_size_spinbox.setMaximum(1024)
self.chunk_size_spinbox.setSingleStep(4)
self.chunk_size_spinbox.setValue(512) # 默认值
self.chunk_size_spinbox.setSuffix(" KB")
self.device_list_widget = QListWidget()
for dev in self.available_devices:
item = QListWidgetItem(dev)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Unchecked)
self.device_list_widget.addItem(item)
self._setup_ui()
self._connect_signals()
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("RAID 级别:", self.raid_level_combo)
form_layout.addRow("Chunk 大小:", self.chunk_size_spinbox)
form_layout.addRow("选择设备:", self.device_list_widget)
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
def get_raid_info(self):
self.selected_devices = []
for i in range(self.device_list_widget.count()):
item = self.device_list_widget.item(i)
if item.checkState() == Qt.Checked:
self.selected_devices.append(item.text())
if not self.selected_devices:
QMessageBox.warning(self, "输入错误", "请选择至少一个设备来创建 RAID 阵列。")
return None
raid_level = self.raid_level_combo.currentText()
num_devices = len(self.selected_devices)
# RAID level specific checks for minimum devices
if raid_level == "raid0" and num_devices < 1: # RAID0 can be 1 device in some contexts, but usually >1
QMessageBox.warning(self, "输入错误", "RAID0 至少需要一个设备。")
return None
elif raid_level == "raid1" and num_devices < 2:
QMessageBox.warning(self, "输入错误", "RAID1 至少需要两个设备。")
return None
elif raid_level == "raid5" and num_devices < 3:
QMessageBox.warning(self, "输入错误", "RAID5 至少需要三个设备。")
return None
elif raid_level == "raid6" and num_devices < 4:
QMessageBox.warning(self, "输入错误", "RAID6 至少需要四个设备。")
return None
elif raid_level == "raid10" and num_devices < 2: # RAID10 needs at least 2 (for 1+0)
QMessageBox.warning(self, "输入错误", "RAID10 至少需要两个设备。")
return None
return {
'devices': self.selected_devices,
'level': self.raid_level_combo.currentText().replace('raid', ''), # 移除 'raid' 前缀
'chunk_size': int(self.chunk_size_spinbox.value()) # KB
}
class CreatePvDialog(QDialog):
def __init__(self, parent=None, available_partitions=None):
super().__init__(parent)
self.setWindowTitle("创建物理卷 (PV)")
self.setMinimumWidth(300)
self.available_partitions = available_partitions if available_partitions is not None else []
self.device_combo_box = QComboBox()
self.device_combo_box.addItems(self.available_partitions)
self._setup_ui()
self._connect_signals()
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("选择设备:", self.device_combo_box)
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
def get_pv_info(self):
device_path = self.device_combo_box.currentText()
if not device_path:
QMessageBox.warning(self, "输入错误", "请选择一个设备来创建物理卷。")
return None
return {'device_path': device_path}
class CreateVgDialog(QDialog):
def __init__(self, parent=None, available_pvs=None):
super().__init__(parent)
self.setWindowTitle("创建卷组 (VG)")
self.setMinimumWidth(300)
self.available_pvs = available_pvs if available_pvs is not None else []
self.selected_pvs = []
self.vg_name_input = QLineEdit()
self.pv_list_widget = QListWidget()
for pv in self.available_pvs:
item = QListWidgetItem(pv)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Unchecked)
self.pv_list_widget.addItem(item)
self._setup_ui()
self._connect_signals()
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("卷组名称:", self.vg_name_input)
form_layout.addRow("选择物理卷:", self.pv_list_widget)
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
def get_vg_info(self):
vg_name = self.vg_name_input.text().strip()
self.selected_pvs = []
for i in range(self.pv_list_widget.count()):
item = self.pv_list_widget.item(i)
if item.checkState() == Qt.Checked:
self.selected_pvs.append(item.text())
if not vg_name:
QMessageBox.warning(self, "输入错误", "卷组名称不能为空。")
return None
if not self.selected_pvs:
QMessageBox.warning(self, "输入错误", "请选择至少一个物理卷来创建卷组。")
return None
return {
'vg_name': vg_name,
'pvs': self.selected_pvs
}
class CreateLvDialog(QDialog):
def __init__(self, parent=None, available_vgs=None, vg_sizes=None):
super().__init__(parent)
self.setWindowTitle("创建逻辑卷")
self.setMinimumWidth(300)
self.available_vgs = available_vgs if available_vgs is not None else []
self.vg_sizes = vg_sizes if vg_sizes is not None else {} # 存储每个 VG 的可用大小 (GB)
self.lv_name_input = QLineEdit()
self.vg_combo_box = QComboBox()
self.lv_size_spinbox = QDoubleSpinBox()
self.use_max_space_checkbox = QCheckBox("使用最大可用空间") # 新增复选框
self._setup_ui()
self._connect_signals()
self._initialize_state() # 初始化状态
def _setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.addRow("逻辑卷名称:", self.lv_name_input)
form_layout.addRow("选择卷组:", self.vg_combo_box)
form_layout.addRow("逻辑卷大小 (GB):", self.lv_size_spinbox)
form_layout.addRow("", self.use_max_space_checkbox) # 添加复选框
layout.addLayout(form_layout)
button_box = QHBoxLayout()
self.confirm_button = QPushButton("确定")
self.cancel_button = QPushButton("取消")
button_box.addWidget(self.confirm_button)
button_box.addWidget(self.cancel_button)
layout.addLayout(button_box)
self.lv_size_spinbox.setMinimum(0.1) # 逻辑卷最小大小,例如 0.1 GB
self.lv_size_spinbox.setSuffix(" GB")
self.lv_size_spinbox.setDecimals(2)
def _connect_signals(self):
self.confirm_button.clicked.connect(self.accept)
self.cancel_button.clicked.connect(self.reject)
self.vg_combo_box.currentIndexChanged.connect(self._update_size_options) # 卷组选择改变时更新大小选项
self.use_max_space_checkbox.stateChanged.connect(self._toggle_size_input) # 复选框状态改变时切换输入
def _initialize_state(self):
self.vg_combo_box.addItems(self.available_vgs)
self._update_size_options() # 首次调用以设置初始状态
self.use_max_space_checkbox.setChecked(True) # 默认选中“使用最大可用空间”
def _update_size_options(self):
"""根据选中的卷组更新逻辑卷大小的选项。"""
selected_vg = self.vg_combo_box.currentText()
max_size_gb = self.vg_sizes.get(selected_vg, 0.0)
# 确保 max_size_gb 至少是 spinbox 的最小值,以防卷组可用空间过小导致 UI 问题
if max_size_gb < self.lv_size_spinbox.minimum():
self.lv_size_spinbox.setMinimum(max_size_gb) # 临时将最小值设为实际最大值
else:
self.lv_size_spinbox.setMinimum(0.1) # 恢复正常最小值
self.lv_size_spinbox.setMaximum(max_size_gb) # 设置最大值
# 如果选中了“使用最大可用空间”,则将 spinbox 值设置为最大值
if self.use_max_space_checkbox.isChecked():
self.lv_size_spinbox.setValue(max_size_gb)
else:
# 如果当前值超过了新的最大值,则调整为新的最大值
if self.lv_size_spinbox.value() > max_size_gb:
self.lv_size_spinbox.setValue(max_size_gb)
# 如果当前值小于新的最小值 (例如VG可用空间变为0最小值被调整为0),则调整
elif self.lv_size_spinbox.value() < self.lv_size_spinbox.minimum():
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
def _toggle_size_input(self, state):
"""根据“使用最大可用空间”复选框的状态切换大小输入框的启用/禁用状态。"""
if state == Qt.Checked:
self.lv_size_spinbox.setDisabled(True)
self.lv_size_spinbox.setValue(self.lv_size_spinbox.maximum()) # 设置为最大值
else:
self.lv_size_spinbox.setDisabled(False)
# 如果之前是最大值,取消勾选后,恢复到最小值或一个合理值
if self.lv_size_spinbox.value() == self.lv_size_spinbox.maximum():
self.lv_size_spinbox.setValue(self.lv_size_spinbox.minimum())
def get_lv_info(self):
lv_name = self.lv_name_input.text().strip()
vg_name = self.vg_combo_box.currentText()
size_gb = self.lv_size_spinbox.value()
use_max_space = self.use_max_space_checkbox.isChecked() # 获取复选框状态
if not lv_name:
QMessageBox.warning(self, "输入错误", "逻辑卷名称不能为空。")
return None
if not vg_name:
QMessageBox.warning(self, "输入错误", "请选择一个卷组。")
return None
if not use_max_space and size_gb <= 0:
QMessageBox.warning(self, "输入错误", "逻辑卷大小必须大于0。")
return None
if not use_max_space and size_gb > self.vg_sizes.get(vg_name, 0.0):
QMessageBox.warning(self, "输入错误", "逻辑卷大小不能超过卷组的可用空间。")
return None
return {
'lv_name': lv_name,
'vg_name': vg_name,
'size_gb': size_gb,
'use_max_space': use_max_space # 返回此标志
}