# 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 from logger_config import setup_logging, logger 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) logger.debug(f"CreatePartitionDialog initialized for {disk_path}. Total MiB: {total_disk_mib}, Max Available MiB: {max_available_mib}") self.partition_table_type_combo = QComboBox() self.partition_table_type_combo.addItems(["gpt", "msdos"]) self.size_spinbox = QDoubleSpinBox() # 调整最小值为 0.01 GB (约 10MB),并确保最大值不小于最小值 calculated_max_gb = self.max_available_mib / 1024.0 self.size_spinbox.setMinimum(0.01) self.size_spinbox.setMaximum(max(0.01, calculated_max_gb)) # 将 MiB 转换为 GB 显示 logger.debug(f"Size spinbox max set to: {self.size_spinbox.maximum()} GB (calculated from {calculated_max_gb} 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) # 信号连接保持不变,但 _toggle_size_input 方法内部将直接查询复选框状态 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): # state 参数仍然接收,但不再直接用于判断 # 直接查询复选框的当前状态,而不是依赖信号传递的 state 参数 is_checked = self.use_max_space_checkbox.isChecked() logger.debug(f"[_toggle_size_input] Called. Checkbox isChecked(): {is_checked}, signal state: {state}") if is_checked: logger.debug(f"[_toggle_size_input] Checkbox is CHECKED. Disabling spinbox and setting value to max.") self.size_spinbox.setEnabled(False) # 禁用 spinbox max_val = self.size_spinbox.maximum() self.size_spinbox.setValue(max_val) # 设置为最大可用 GB logger.debug(f"[_toggle_size_input] Spinbox value set to max: {max_val} GB.") else: # is_checked is False logger.debug(f"[_toggle_size_input] Checkbox is UNCHECKED. Enabling spinbox.") self.size_spinbox.setEnabled(True) # 启用 spinbox # 如果之前是最大值,取消勾选后,恢复到最小值,方便用户输入自定义值 if self.size_spinbox.value() == self.size_spinbox.maximum(): min_val = self.size_spinbox.minimum() self.size_spinbox.setValue(min_val) logger.debug(f"[_toggle_size_input] Spinbox was at max, reset to min: {min_val} GB.") else: logger.debug(f"[_toggle_size_input] Spinbox was not at max, keeping current value: {self.size_spinbox.value()} GB.") 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 # 这里的检查应该使用 self.size_spinbox.maximum() 来判断,因为它是实际的最大值 if not use_max_space and size_gb > self.size_spinbox.maximum(): 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 问题 current_min_limit = self.lv_size_spinbox.minimum() # 获取当前的最小值限制 if max_size_gb < current_min_limit: # 如果实际最大可用空间小于当前最小值限制,则将最小值临时调整为实际最大值 self.lv_size_spinbox.setMinimum(max_size_gb if max_size_gb > 0 else 0.01) # 至少0.01GB else: self.lv_size_spinbox.setMinimum(0.1) # 恢复正常最小值 # 计算并设置 spinbox 的最大值 clamped_max_gb = max(self.lv_size_spinbox.minimum(), max_size_gb) self.lv_size_spinbox.setMaximum(clamped_max_gb) # 如果选中了“使用最大可用空间”,则将 spinbox 值设置为最大值 if self.use_max_space_checkbox.isChecked(): self.lv_size_spinbox.setValue(clamped_max_gb) # 使用计算出的最大值 else: # 如果当前值超过了新的最大值,则调整为新的最大值 if self.lv_size_spinbox.value() > clamped_max_gb: self.lv_size_spinbox.setValue(clamped_max_gb) # 如果当前值小于新的最小值,则调整 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): """根据“使用最大可用空间”复选框的状态切换大小输入框的启用/禁用状态。""" # 获取当前选中的卷组的实际最大可用空间(GB) selected_vg = self.vg_combo_box.currentText() actual_max_gb = self.vg_sizes.get(selected_vg, 0.0) # 确保计算出的最大值不小于spinbox的最小值 clamped_max_gb = max(self.lv_size_spinbox.minimum(), actual_max_gb) if state == Qt.Checked: self.lv_size_spinbox.setDisabled(True) self.lv_size_spinbox.setValue(clamped_max_gb) # 使用计算出的最大值 else: self.lv_size_spinbox.setDisabled(False) # 如果之前是最大值,取消勾选后,恢复到最小值或一个合理值 if self.lv_size_spinbox.value() == clamped_max_gb: # 比较时也使用计算出的最大值 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 # 这里的检查应该使用 self.lv_size_spinbox.maximum() 来判断,因为它是实际的最大值 if not use_max_space and size_gb > self.lv_size_spinbox.maximum(): QMessageBox.warning(self, "输入错误", "逻辑卷大小不能超过卷组的可用空间。") return None return { 'lv_name': lv_name, 'vg_name': vg_name, 'size_gb': size_gb, 'use_max_space': use_max_space # 返回此标志 }