diff --git a/service/lib/agama/storage/config_checkers/alias.rb b/service/lib/agama/storage/config_checkers/alias.rb
index ae78b09ce5..a6d9b9ce18 100644
--- a/service/lib/agama/storage/config_checkers/alias.rb
+++ b/service/lib/agama/storage/config_checkers/alias.rb
@@ -88,7 +88,7 @@ def formatted_issue
target_users = storage_config.target_users(config.alias)
any_user = (users + target_users).any?
- return unless users.any? || target_users.any?
+ return unless any_user
error(
format(
diff --git a/web/src/components/storage/ConfigEditor.tsx b/web/src/components/storage/ConfigEditor.tsx
index db29a48ef0..bf028e8aaf 100644
--- a/web/src/components/storage/ConfigEditor.tsx
+++ b/web/src/components/storage/ConfigEditor.tsx
@@ -26,7 +26,6 @@ import Text from "~/components/core/Text";
import DriveEditor from "~/components/storage/DriveEditor";
import VolumeGroupEditor from "~/components/storage/VolumeGroupEditor";
import MdRaidEditor from "~/components/storage/MdRaidEditor";
-import { useDevices } from "~/hooks/api/system/storage";
import { useReset } from "~/hooks/api/config/storage";
import ConfigureDeviceMenu from "./ConfigureDeviceMenu";
import { useModel } from "~/hooks/storage/model";
@@ -57,24 +56,8 @@ const NoDevicesConfiguredAlert = () => {
);
};
-/**
- * @fixme Adapt components (DriveEditor, MdRaidEditor, etc) to receive a list name and an index
- * instead of a device object. Each component will retrieve the device from the model if needed.
- *
- * That will allow to:
- * * Simplify the model types (list and listIndex properties are not needed).
- * * All the components (DriveEditor, PartitionPage, etc) work in a similar way. They receive a
- * list and an index and each component retrieves the device from the model if needed.
- * * The components always have all the needed info for generating an url.
- * * The partitions and logical volumes can also be referenced by an index, so it opens the door
- * to have partitions and lvs without a mount path.
- *
- * These changes will be done once creating partitions without a mount path is needed (e.g., for
- * manually creating physical volumes).
- */
export default function ConfigEditor() {
const model = useModel();
- const devices = useDevices();
const drives = model.drives;
const mdRaids = model.mdRaids;
const volumeGroups = model.volumeGroups;
@@ -89,22 +72,12 @@ export default function ConfigEditor() {
{volumeGroups.map((vg, i) => {
return ;
})}
- {mdRaids.map((raid, i) => {
- const device = devices.find((d) => d.name === raid.name);
-
- return ;
- })}
- {drives.map((drive, i) => {
- const device = devices.find((d) => d.name === drive.name);
-
- /**
- * @fixme Make DriveEditor to work when the device is not found (e.g., after disabling
- * a iSCSI device).
- */
- if (device === undefined) return null;
-
- return ;
- })}
+ {mdRaids.map((_, i) => (
+
+ ))}
+ {drives.map((_, i) => (
+
+ ))}
diff --git a/web/src/components/storage/DeviceEditorContent.tsx b/web/src/components/storage/DeviceEditorContent.tsx
index 3da4fe0d73..978670e656 100644
--- a/web/src/components/storage/DeviceEditorContent.tsx
+++ b/web/src/components/storage/DeviceEditorContent.tsx
@@ -25,22 +25,25 @@ import UnusedMenu from "~/components/storage/UnusedMenu";
import FilesystemMenu from "~/components/storage/FilesystemMenu";
import PartitionsSection from "~/components/storage/PartitionsSection";
import SpacePolicyMenu from "~/components/storage/SpacePolicyMenu";
-import type { model } from "~/storage";
-import type { storage } from "~/api/system";
+import { useDevice } from "~/hooks/storage/model";
-type DeviceEditorContentProps = { deviceModel: model.Drive | model.MdRaid; device: storage.Device };
+type DeviceEditorContentProps = {
+ collection: "drives" | "mdRaids";
+ index: number;
+};
export default function DeviceEditorContent({
- deviceModel,
- device,
+ collection,
+ index,
}: DeviceEditorContentProps): React.ReactNode {
- if (!deviceModel.isUsed) return ;
+ const deviceModel = useDevice(collection, index);
+ if (!deviceModel.isUsed) return ;
return (
<>
- {deviceModel.filesystem && }
- {!deviceModel.filesystem && }
- {!deviceModel.filesystem && }
+ {deviceModel.filesystem && }
+ {!deviceModel.filesystem && }
+ {!deviceModel.filesystem && }
>
);
}
diff --git a/web/src/components/storage/DriveEditor.tsx b/web/src/components/storage/DriveEditor.tsx
index 73d7bb2d20..594ba8ad69 100644
--- a/web/src/components/storage/DriveEditor.tsx
+++ b/web/src/components/storage/DriveEditor.tsx
@@ -30,17 +30,14 @@ import { CustomToggleProps } from "~/components/core/MenuButton";
import { useDeleteDrive } from "~/hooks/storage/drive";
import { Button, Flex, FlexItem } from "@patternfly/react-core";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
+import { useDrive } from "~/hooks/storage/model";
+import { useDevice } from "~/hooks/api/system/storage";
import type { model } from "~/storage";
-import type { storage } from "~/api/system";
-
-type DriveDeviceMenuProps = {
- drive: model.Drive;
- selected: storage.Device;
-};
+import type { storage as system } from "~/api/system";
type DriveDeviceMenuToggleProps = CustomToggleProps & {
drive: model.Drive | model.MdRaid;
- device: storage.Device;
+ device: system.Device;
};
const DriveDeviceMenuToggle = forwardRef(
@@ -71,6 +68,11 @@ const DriveDeviceMenuToggle = forwardRef(
},
);
+type DriveDeviceMenuProps = {
+ drive: model.Drive;
+ selected: system.Device;
+};
+
/**
* Internal component that renders generic actions available for a Drive device.
*/
@@ -88,16 +90,25 @@ const DriveDeviceMenu = ({ drive, selected }: DriveDeviceMenuProps) => {
);
};
-export type DriveEditorProps = { drive: model.Drive; driveDevice: storage.Device };
+export type DriveEditorProps = { index: number };
/**
* Component responsible for displaying detailed information and available actions
* related to a specific Drive device within the storage ConfigEditor.
*/
-export default function DriveEditor({ drive, driveDevice }: DriveEditorProps) {
+export default function DriveEditor({ index }: DriveEditorProps) {
+ const driveModel = useDrive(index);
+ const drive = useDevice(driveModel.name);
+
+ /**
+ * @fixme Make DriveEditor to work when the device is not found (e.g., after disabling
+ * a iSCSI device).
+ */
+ if (drive === undefined) return null;
+
return (
- }>
-
+ }>
+
);
}
diff --git a/web/src/components/storage/FilesystemMenu.tsx b/web/src/components/storage/FilesystemMenu.tsx
index 29fa8d8f2f..1cd1d7da31 100644
--- a/web/src/components/storage/FilesystemMenu.tsx
+++ b/web/src/components/storage/FilesystemMenu.tsx
@@ -28,12 +28,11 @@ import MenuButton, { CustomToggleProps } from "~/components/core/MenuButton";
import { STORAGE as PATHS } from "~/routes/paths";
import { model } from "~/storage";
import { filesystemType, formattedPath } from "~/components/storage/utils";
+import { useDevice } from "~/hooks/storage/model";
import { sprintf } from "sprintf-js";
import { _ } from "~/i18n";
-type FilesystemMenuProps = { deviceModel: model.Drive | model.MdRaid };
-
-function deviceDescription(deviceModel: FilesystemMenuProps["deviceModel"]): string {
+function deviceDescription(deviceModel: model.Drive | model.MdRaid): string {
const fs = filesystemType(deviceModel.filesystem);
const mountPath = deviceModel.mountPath;
const reuse = deviceModel.filesystem.reuse;
@@ -82,10 +81,18 @@ const FilesystemMenuToggle = forwardRef(
},
);
-export default function FilesystemMenu({ deviceModel }: FilesystemMenuProps): React.ReactNode {
+type FilesystemMenuProps = {
+ collection: "drives" | "mdRaids";
+ index: number;
+};
+
+export default function FilesystemMenu({
+ collection,
+ index,
+}: FilesystemMenuProps): React.ReactNode {
const navigate = useNavigate();
- const { list, listIndex } = deviceModel;
- const editFilesystemPath = generatePath(PATHS.formatDevice, { list, listIndex });
+ const deviceModel = useDevice(collection, index);
+ const editFilesystemPath = generatePath(PATHS.formatDevice, { collection, index });
return (
d.name === deviceModel.name);
+function useDeviceFromParams(): system.Device {
+ const deviceModel = useDeviceModelFromParams();
+ return useDevice(deviceModel.name);
}
function useCurrentFilesystem(): string | null {
- const device = useDevice();
+ const device = useDeviceFromParams();
return device?.filesystem?.type || null;
}
@@ -161,14 +165,14 @@ function useDefaultFilesystem(mountPoint: string): string {
}
function useInitialFormValue(): FormValue | null {
- const deviceModel = useDeviceModel();
+ const deviceModel = useDeviceModelFromParams();
return React.useMemo(() => (deviceModel ? toFormValue(deviceModel) : null), [deviceModel]);
}
/** Unused predefined mount points. Includes the currently used mount point when editing. */
function useUnusedMountPoints(): string[] {
const unusedMountPaths = useMissingMountPaths();
- const deviceModel = useDeviceModel();
+ const deviceModel = useDeviceModelFromParams();
return compact([deviceModel?.mountPath, ...unusedMountPaths]);
}
@@ -204,7 +208,7 @@ function useUsableFilesystems(mountPoint: string): string[] {
function useMountPointError(value: FormValue): Error | undefined {
const model = useModel();
const mountPoints = model?.getMountPaths() || [];
- const deviceModel = useDeviceModel();
+ const deviceModel = useDeviceModelFromParams();
const mountPoint = value.mountPoint;
if (mountPoint === NO_VALUE) {
@@ -291,7 +295,7 @@ type FilesystemOptionsProps = {
};
function FilesystemOptions({ mountPoint }: FilesystemOptionsProps): React.ReactNode {
- const device = useDevice();
+ const device = useDeviceFromParams();
const volume = useVolumeTemplate(mountPoint);
const defaultFilesystem = useDefaultFilesystem(mountPoint);
const usableFilesystems = useUsableFilesystems(mountPoint);
@@ -402,7 +406,8 @@ export default function FormattableDevicePage() {
const value = { mountPoint, filesystem, filesystemLabel };
const { errors, getVisibleError } = useErrors(value);
- const device = useDeviceModel();
+ const { collection, index } = useParams();
+ const device = useDeviceModelFromParams();
const unusedMountPoints = useUnusedMountPoints();
const addFilesystem = useAddFilesystem();
@@ -436,8 +441,7 @@ export default function FormattableDevicePage() {
const onSubmit = () => {
const data = toData(value);
- const { list, listIndex } = device;
- addFilesystem(list, listIndex, data);
+ addFilesystem(collection, Number(index), data);
navigate(PATHS.root);
};
diff --git a/web/src/components/storage/MdRaidEditor.tsx b/web/src/components/storage/MdRaidEditor.tsx
index 51d33b27be..e6e0c20416 100644
--- a/web/src/components/storage/MdRaidEditor.tsx
+++ b/web/src/components/storage/MdRaidEditor.tsx
@@ -30,6 +30,8 @@ import { CustomToggleProps } from "~/components/core/MenuButton";
import { useDeleteMdRaid } from "~/hooks/storage/md-raid";
import { Button, Flex, FlexItem } from "@patternfly/react-core";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
+import { useMdRaid } from "~/hooks/storage/model";
+import { useDevice } from "~/hooks/api/system/storage";
import type { model } from "~/storage";
import type { storage } from "~/api/system";
@@ -88,16 +90,18 @@ const MdRaidDeviceMenu = ({ raid, selected }: MdRaidDeviceMenuProps): React.Reac
);
};
-type MdRaidEditorProps = { raid: model.MdRaid; raidDevice: storage.Device };
+type MdRaidEditorProps = { index: number };
/**
* Component responsible for displaying detailed information and available
* actions related to a specific MdRaid device within the storage ConfigEditor.
*/
-export default function MdRaidEditor({ raid, raidDevice }: MdRaidEditorProps) {
+export default function MdRaidEditor({ index }: MdRaidEditorProps) {
+ const raidModel = useMdRaid(index);
+ const raid = useDevice(raidModel.name);
return (
- }>
-
+ }>
+
);
}
diff --git a/web/src/components/storage/PartitionPage.tsx b/web/src/components/storage/PartitionPage.tsx
index 53e490d3e1..5f27f4ef69 100644
--- a/web/src/components/storage/PartitionPage.tsx
+++ b/web/src/components/storage/PartitionPage.tsx
@@ -51,12 +51,18 @@ import SizeModeSelect, { SizeMode, SizeRange } from "~/components/storage/SizeMo
import AlertOutOfSync from "~/components/core/AlertOutOfSync";
import ResourceNotFound from "~/components/core/ResourceNotFound";
import { useAddPartition, useEditPartition } from "~/hooks/storage/partition";
-import { useModel, useMissingMountPaths } from "~/hooks/storage/model";
+import {
+ useModel,
+ useMissingMountPaths,
+ useDrive as useDriveModel,
+ useMdRaid as useMdRaidModel,
+} from "~/hooks/storage/model";
import {
addPartition as addPartitionHelper,
editPartition as editPartitionHelper,
} from "~/storage/partition";
-import { useDevices, useVolumeTemplate } from "~/hooks/api/system/storage";
+import { useVolumeTemplate, useDevice } from "~/hooks/api/system/storage";
+
import { useSolvedConfigModel } from "~/queries/storage/config-model";
import { useStorageModel } from "~/hooks/api/storage";
import { findDevice } from "~/storage/api-model";
@@ -189,20 +195,19 @@ function toFormValue(partitionConfig: model.Partition): FormValue {
};
}
-function useModelDevice() {
- const { list, listIndex } = useParams();
- const model = useModel();
- return model[list].at(listIndex);
+function useDeviceModelFromParams() {
+ const { collection, index } = useParams();
+ const deviceModel = collection === "drives" ? useDriveModel : useMdRaidModel;
+ return deviceModel(Number(index));
}
-function useDevice(): system.Device {
- const modelDevice = useModelDevice();
- const devices = useDevices();
- return devices.find((d) => d.name === modelDevice.name);
+function useDeviceFromParams(): system.Device {
+ const deviceModel = useDeviceModelFromParams();
+ return useDevice(deviceModel.name);
}
function usePartition(target: string): system.Device | null {
- const device = useDevice();
+ const device = useDeviceFromParams();
if (target === NEW_PARTITION) return null;
@@ -223,7 +228,7 @@ function useDefaultFilesystem(mountPoint: string): string {
function useInitialPartitionConfig(): model.Partition | null {
const { partitionId: mountPath } = useParams();
- const device = useModelDevice();
+ const device = useDeviceModelFromParams();
return mountPath && device ? device.getPartition(mountPath) : null;
}
@@ -248,10 +253,10 @@ function useUnusedMountPoints(): string[] {
/** Unused partitions. Includes the currently used partition when editing (if any). */
function useUnusedPartitions(): system.Device[] {
- const device = useDevice();
+ const device = useDeviceFromParams();
const allPartitions = device.partitions || [];
const initialPartitionConfig = useInitialPartitionConfig();
- const configuredPartitionConfigs = useModelDevice()
+ const configuredPartitionConfigs = useDeviceModelFromParams()
.getConfiguredExistingPartitions()
.filter((p) => p.name !== initialPartitionConfig?.name)
.map((p) => p.name);
@@ -387,7 +392,8 @@ function useErrors(value: FormValue): ErrorsHandler {
}
function useSolvedModel(value: FormValue): model.Config | null {
- const device = useModelDevice();
+ const { collection, index } = useParams();
+ const device = useDeviceModelFromParams();
const model = useStorageModel();
const { errors } = useErrors(value);
const initialPartitionConfig = useInitialPartitionConfig();
@@ -395,19 +401,21 @@ function useSolvedModel(value: FormValue): model.Config | null {
partitionConfig.size = undefined;
if (partitionConfig.filesystem) partitionConfig.filesystem.label = undefined;
+ const modelCollection = collection === "drives" ? "drives" : "mdRaids";
+
let sparseModel: model.Config | undefined;
if (device && !errors.length && value.target === NEW_PARTITION && value.filesystem !== NO_VALUE) {
if (initialPartitionConfig) {
sparseModel = editPartitionHelper(
model,
- device.list,
- device.listIndex,
+ modelCollection,
+ index,
initialPartitionConfig.mountPath,
partitionConfig,
);
} else {
- sparseModel = addPartitionHelper(model, device.list, device.listIndex, partitionConfig);
+ sparseModel = addPartitionHelper(model, modelCollection, index, partitionConfig);
}
}
@@ -417,10 +425,10 @@ function useSolvedModel(value: FormValue): model.Config | null {
function useSolvedPartitionConfig(value: FormValue): model.Partition | undefined {
const model = useSolvedModel(value);
- const { list, listIndex } = useModelDevice();
+ const { collection, index } = useParams();
if (!model) return;
- const container = findDevice(model, list, listIndex);
+ const container = findDevice(model, collection, index);
return container?.partitions?.find((p) => p.mountPath === value.mountPoint);
}
@@ -490,7 +498,7 @@ type TargetOptionLabelProps = {
};
function TargetOptionLabel({ value }: TargetOptionLabelProps): React.ReactNode {
- const device = useDevice();
+ const device = useDeviceFromParams();
const partition = usePartition(value);
if (value === NEW_PARTITION) {
@@ -691,6 +699,7 @@ function AutoSizeInfo({ value }: AutoSizeInfoProps): React.ReactNode {
* deprecated hooks from ~/queries/storage/config-model.
*/
const PartitionPageForm = () => {
+ const { collection, index } = useParams();
const navigate = useNavigate();
const location = useLocation();
const headingId = useId();
@@ -710,7 +719,7 @@ const PartitionPageForm = () => {
const value = { mountPoint, target, filesystem, filesystemLabel, sizeOption, minSize, maxSize };
const { errors, getVisibleError } = useErrors(value);
- const device = useModelDevice();
+ const device = useDeviceModelFromParams();
const unusedMountPoints = useUnusedMountPoints();
@@ -792,10 +801,11 @@ const PartitionPageForm = () => {
const onSubmit = () => {
const partitionConfig = toPartitionConfig(value);
- const { list, listIndex } = device;
+ const modelCollection = collection === "drives" ? "drives" : "mdRaids";
- if (initialValue) editPartition(list, listIndex, initialValue.mountPoint, partitionConfig);
- else addPartition(list, listIndex, partitionConfig);
+ if (initialValue)
+ editPartition(modelCollection, index, initialValue.mountPoint, partitionConfig);
+ else addPartition(modelCollection, index, partitionConfig);
navigate({ pathname: PATHS.root, search: location.search });
};
@@ -916,7 +926,7 @@ const PartitionPageForm = () => {
};
export default function PartitionPage() {
- const device = useModelDevice();
+ const device = useDeviceModelFromParams();
return isUndefined(device) ? (
diff --git a/web/src/components/storage/PartitionsSection.tsx b/web/src/components/storage/PartitionsSection.tsx
index 9f6ff16a80..0214ca5745 100644
--- a/web/src/components/storage/PartitionsSection.tsx
+++ b/web/src/components/storage/PartitionsSection.tsx
@@ -40,6 +40,7 @@ import MenuButton from "~/components/core/MenuButton";
import MountPathMenuItem from "~/components/storage/MountPathMenuItem";
import { STORAGE as PATHS } from "~/routes/paths";
import { useDeletePartition } from "~/hooks/storage/partition";
+import { useDevice } from "~/hooks/storage/model";
import * as driveUtils from "~/components/storage/utils/drive";
import { generateEncodedPath } from "~/utils";
import * as partitionUtils from "~/components/storage/utils/partition";
@@ -52,12 +53,18 @@ import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacin
import { toggle } from "radashi";
import type { model } from "~/storage";
-const PartitionMenuItem = ({ device, mountPath }) => {
+type PartitionMenuItemProps = {
+ device: model.Drive | model.MdRaid;
+ mountPath: string;
+ collection: "drives" | "mdRaids";
+ index: number;
+};
+
+const PartitionMenuItem = ({ device, mountPath, collection, index }: PartitionMenuItemProps) => {
const partition = device.getPartition(mountPath);
- const { list, listIndex } = device;
const editPath = generateEncodedPath(PATHS.editPartition, {
- list,
- listIndex,
+ collection,
+ index,
partitionId: mountPath,
});
const deletePartition = useDeletePartition();
@@ -66,7 +73,7 @@ const PartitionMenuItem = ({ device, mountPath }) => {
deletePartition(list, listIndex, mountPath)}
+ deleteFn={() => deletePartition(collection, index, mountPath)}
/>
);
};
@@ -120,12 +127,16 @@ const optionalPartitionsTexts = (device) => {
return texts;
};
-const PartitionRow = ({ partition, device }) => {
- // const partition = device.getPartition(mountPath);
- const { list, listIndex } = device;
+type PartitionRowProps = {
+ partition: model.Partition;
+ collection: "drives" | "mdRaids";
+ index: number;
+};
+
+const PartitionRow = ({ partition, collection, index }: PartitionRowProps) => {
const editPath = generateEncodedPath(PATHS.editPartition, {
- list,
- listIndex,
+ collection,
+ index,
partitionId: partition.mountPath,
});
const deletePartition = useDeletePartition();
@@ -174,7 +185,7 @@ const PartitionRow = ({ partition, device }) => {
deletePartition(list, listIndex, partition.mountPath)}
+ onClick={() => deletePartition(collection, index, partition.mountPath)}
isDanger
>
{_("Delete")}
@@ -202,23 +213,24 @@ const PartitionsSectionHeader = ({ device }) => {
};
type PartitionsSectionProps = {
- device: model.Drive | model.MdRaid;
+ collection: "drives" | "mdRaids";
+ index: number;
};
-export default function PartitionsSection({ device }: PartitionsSectionProps) {
+export default function PartitionsSection({ collection, index }: PartitionsSectionProps) {
const { uiState, setUiState } = useStorageUiState();
const toggleId = useId();
const contentId = useId();
- const { list, listIndex } = device;
- const index = `${list[0]}${listIndex}`;
+ const device = useDevice(collection, index);
+ const uiIndex = `${collection[0]}${index}`;
const expanded = uiState.get("expanded")?.split(",");
- const isExpanded = expanded?.includes(index);
- const newPartitionPath = generateEncodedPath(PATHS.addPartition, { list, listIndex });
+ const isExpanded = expanded?.includes(uiIndex);
+ const newPartitionPath = generateEncodedPath(PATHS.addPartition, { collection, index });
const hasPartitions = device.partitions.some((p: model.Partition) => p.mountPath);
const onToggle = () => {
setUiState((state) => {
- const nextExpanded = toggle(expanded, index);
+ const nextExpanded = toggle(expanded, uiIndex);
state.set("expanded", nextExpanded.join(","));
return state;
});
@@ -241,7 +253,15 @@ export default function PartitionsSection({ device }: PartitionsSectionProps) {
device.partitions
.filter((p: model.Partition) => p.mountPath)
.map((p: model.Partition) => {
- return ;
+ return (
+
+ );
}),
);
}
@@ -271,7 +291,14 @@ export default function PartitionsSection({ device }: PartitionsSectionProps) {
{device.partitions
.filter((p: model.Partition) => p.mountPath)
.map((p: model.Partition) => {
- return ;
+ return (
+
+ );
})}
diff --git a/web/src/components/storage/ProposalResultSection.tsx b/web/src/components/storage/ProposalResultSection.tsx
index 95e1b68801..4bdd0e7167 100644
--- a/web/src/components/storage/ProposalResultSection.tsx
+++ b/web/src/components/storage/ProposalResultSection.tsx
@@ -28,8 +28,11 @@ import DevicesManager from "~/storage/devices-manager";
import ProposalResultTable from "~/components/storage/ProposalResultTable";
import { ProposalActionsDialog } from "~/components/storage";
import { _, n_, formatList } from "~/i18n";
-import { useDevices as useSystemDevices } from "~/hooks/api/system/storage";
-import { useDevices as useProposalDevices, useActions } from "~/hooks/api/proposal/storage";
+import { useFlattenDevices as useSystemFlattenDevices } from "~/hooks/api/system/storage";
+import {
+ useFlattenDevices as useProposalFlattenDevices,
+ useActions,
+} from "~/hooks/api/proposal/storage";
import { sprintf } from "sprintf-js";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import { useStorageUiState } from "~/context/storage-ui-state";
@@ -108,8 +111,8 @@ export type ProposalResultSectionProps = {
export default function ProposalResultSection({ isLoading = false }: ProposalResultSectionProps) {
const { uiState, setUiState } = useStorageUiState();
- const system = useSystemDevices();
- const staging = useProposalDevices();
+ const system = useSystemFlattenDevices();
+ const staging = useProposalFlattenDevices();
const actions = useActions();
const devicesManager = new DevicesManager(system, staging, actions);
const handleTabClick = (
diff --git a/web/src/components/storage/SpacePolicyMenu.tsx b/web/src/components/storage/SpacePolicyMenu.tsx
index 332422cd68..6647bab7c7 100644
--- a/web/src/components/storage/SpacePolicyMenu.tsx
+++ b/web/src/components/storage/SpacePolicyMenu.tsx
@@ -32,7 +32,8 @@ import { STORAGE as PATHS } from "~/routes/paths";
import * as driveUtils from "~/components/storage/utils/drive";
import { generateEncodedPath } from "~/utils";
import { isEmpty } from "radashi";
-import type { storage as system } from "~/api/system";
+import { useDevice as useDeviceModel } from "~/hooks/storage/model";
+import { useDevice } from "~/hooks/api/system/storage";
import type { model as apiModel } from "~/api/storage";
import type { model } from "~/storage";
@@ -72,27 +73,28 @@ const SpacePolicyMenuToggle = forwardRef(({ drive, ...props }: SpacePolicyMenuTo
});
type SpacePolicyMenuProps = {
- modelDevice: model.Drive | model.MdRaid;
- device: system.Device;
+ collection: "drives" | "mdRaids";
+ index: number;
};
-export default function SpacePolicyMenu({ modelDevice, device }: SpacePolicyMenuProps) {
+export default function SpacePolicyMenu({ collection, index }: SpacePolicyMenuProps) {
const navigate = useNavigate();
const setSpacePolicy = useSetSpacePolicy();
- const { list, listIndex } = modelDevice;
+ const deviceModel = useDeviceModel(collection, index);
+ const device = useDevice(deviceModel.name);
const existingPartitions = device.partitions?.length;
if (isEmpty(existingPartitions)) return;
const onSpacePolicyChange = (spacePolicy: apiModel.SpacePolicy) => {
if (spacePolicy === "custom") {
- return navigate(generateEncodedPath(PATHS.editSpacePolicy, { list, listIndex }));
+ return navigate(generateEncodedPath(PATHS.editSpacePolicy, { collection, index }));
} else {
- setSpacePolicy(list, listIndex, { type: spacePolicy });
+ setSpacePolicy(collection, index, { type: spacePolicy });
}
};
- const currentPolicy = driveUtils.spacePolicyEntry(modelDevice);
+ const currentPolicy = driveUtils.spacePolicyEntry(deviceModel);
return (
))}
- customToggle={}
+ customToggle={}
/>
);
}
diff --git a/web/src/components/storage/SpacePolicySelection.tsx b/web/src/components/storage/SpacePolicySelection.tsx
index 341e639c16..ddcd5ea5ff 100644
--- a/web/src/components/storage/SpacePolicySelection.tsx
+++ b/web/src/components/storage/SpacePolicySelection.tsx
@@ -28,7 +28,7 @@ import SpaceActionsTable, { SpacePolicyAction } from "~/components/storage/Space
import { deviceChildren } from "~/components/storage/utils";
import { _ } from "~/i18n";
import { useDevices } from "~/hooks/api/system/storage";
-import { useModel } from "~/hooks/storage/model";
+import { useDrive as useDriveModel, useMdRaid as useMdRaidModel } from "~/hooks/storage/model";
import { useSetSpacePolicy } from "~/hooks/storage/space-policy";
import { toDevice } from "./device-utils";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
@@ -43,17 +43,22 @@ const partitionAction = (partition: model.Partition) => {
return undefined;
};
+function useDeviceModelFromParams(): model.Drive | model.MdRaid | null {
+ const { collection, index } = useParams();
+ const deviceModel = collection === "drives" ? useDriveModel : useMdRaidModel;
+ return deviceModel(Number(index));
+}
+
/**
* Renders a page that allows the user to select the space policy and actions.
*/
export default function SpacePolicySelection() {
- const { list, listIndex } = useParams();
- const model = useModel();
- const deviceModel = model[list][listIndex];
+ const deviceModel = useDeviceModelFromParams();
const devices = useDevices();
const device = devices.find((d) => d.name === deviceModel.name);
const children = deviceChildren(device);
const setSpacePolicy = useSetSpacePolicy();
+ const { collection, index } = useParams();
const partitionDeviceAction = (device: Device) => {
const partition = deviceModel.partitions?.find((p) => p.name === device.name);
@@ -89,7 +94,7 @@ export default function SpacePolicySelection() {
const onSubmit = (e) => {
e.preventDefault();
- setSpacePolicy(list, listIndex, { type: "custom", actions });
+ setSpacePolicy(collection, index, { type: "custom", actions });
navigate("..");
};
diff --git a/web/src/components/storage/UnusedMenu.tsx b/web/src/components/storage/UnusedMenu.tsx
index b6896a7d54..8b70a33683 100644
--- a/web/src/components/storage/UnusedMenu.tsx
+++ b/web/src/components/storage/UnusedMenu.tsx
@@ -26,12 +26,9 @@ import Icon from "~/components/layout/Icon";
import { useNavigate } from "react-router";
import MenuButton, { CustomToggleProps } from "~/components/core/MenuButton";
import { STORAGE as PATHS } from "~/routes/paths";
-import { model } from "~/storage";
import { generateEncodedPath } from "~/utils";
import { _ } from "~/i18n";
-type UnusedMenuProps = { deviceModel: model.Drive | model.MdRaid };
-
const UnusedMenuToggle = forwardRef(({ ...props }: CustomToggleProps, ref) => {
const description = _("Not configured yet");
@@ -52,13 +49,19 @@ const UnusedMenuToggle = forwardRef(({ ...props }: CustomToggleProps, ref) => {
);
});
-export default function UnusedMenu({ deviceModel }: UnusedMenuProps): React.ReactNode {
+type UnusedMenuProps = {
+ collection: "drives" | "mdRaids";
+ index: number;
+};
+
+export default function UnusedMenu({ collection, index }: UnusedMenuProps): React.ReactNode {
const navigate = useNavigate();
- const { list, listIndex } = deviceModel;
- const newPartitionPath = generateEncodedPath(PATHS.addPartition, { list, listIndex });
- const formatDevicePath = generateEncodedPath(PATHS.formatDevice, { list, listIndex });
+ const newPartitionPath = generateEncodedPath(PATHS.addPartition, { collection, index });
+ const formatDevicePath = generateEncodedPath(PATHS.formatDevice, { collection, index });
const filesystemLabel =
- list === "drives" ? _("Use the disk without partitions") : _("Use the RAID without partitions");
+ collection === "drives"
+ ? _("Use the disk without partitions")
+ : _("Use the RAID without partitions");
return (
data?.storage;
@@ -44,6 +45,17 @@ function useDevices(): storage.Device[] {
return data;
}
+const selectFlattenDevices = (data: Proposal | null): storage.Device[] =>
+ data?.storage ? flatDevices(data.storage) : [];
+
+function useFlattenDevices(): storage.Device[] {
+ const { data } = useSuspenseQuery({
+ ...proposalQuery,
+ select: selectFlattenDevices,
+ });
+ return data;
+}
+
const selectActions = (data: Proposal | null): storage.Action[] => data?.storage?.actions || [];
function useActions(): storage.Action[] {
@@ -54,4 +66,4 @@ function useActions(): storage.Action[] {
return data;
}
-export { useProposal, useDevices, useActions };
+export { useProposal, useDevices, useFlattenDevices, useActions };
diff --git a/web/src/hooks/api/system/storage.ts b/web/src/hooks/api/system/storage.ts
index bc51084894..0451d8f834 100644
--- a/web/src/hooks/api/system/storage.ts
+++ b/web/src/hooks/api/system/storage.ts
@@ -23,7 +23,7 @@
import { useCallback } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { systemQuery } from "~/hooks/api/system";
-import { findDevices } from "~/storage/system";
+import { flatDevices, findDevices, findDeviceByName } from "~/storage/system";
import type { System, storage } from "~/api/system";
import type { EncryptionMethod } from "~/api/system/storage";
@@ -155,6 +155,30 @@ function useDevices(): storage.Device[] {
return data;
}
+const selectFlattenDevices = (data: System | null): storage.Device[] =>
+ data?.storage ? flatDevices(data.storage) : [];
+
+function useFlattenDevices(): storage.Device[] {
+ const { data } = useSuspenseQuery({
+ ...systemQuery,
+ select: selectFlattenDevices,
+ });
+ return data;
+}
+
+function useDevice(name: string): storage.Device | null {
+ const { data } = useSuspenseQuery({
+ ...systemQuery,
+ select: useCallback(
+ (data: System | null): storage.Device | null => {
+ return data?.storage ? findDeviceByName(data.storage, name) : null;
+ },
+ [name],
+ ),
+ });
+ return data;
+}
+
const selectVolumeTemplates = (data: System | null): storage.Volume[] =>
data?.storage?.volumeTemplates || [];
@@ -199,6 +223,8 @@ export {
useAvailableDevices,
useCandidateDevices,
useDevices,
+ useFlattenDevices,
+ useDevice,
useVolumeTemplates,
useVolumeTemplate,
useIssues,
diff --git a/web/src/hooks/storage/model.ts b/web/src/hooks/storage/model.ts
index 37564e2089..7cf435b4f2 100644
--- a/web/src/hooks/storage/model.ts
+++ b/web/src/hooks/storage/model.ts
@@ -55,4 +55,41 @@ function useMissingMountPaths(): string[] {
return data;
}
-export { useModel, useMissingMountPaths };
+function useDevice(
+ collection: "drives" | "mdRaids",
+ index: number,
+): model.Drive | model.MdRaid | null {
+ const { data } = useSuspenseQuery({
+ ...storageModelQuery,
+ select: useCallback(
+ (data: apiModel.Config): model.Drive | model.MdRaid | null =>
+ build(data)?.[collection]?.at(index) || null,
+ [collection, index],
+ ),
+ });
+ return data;
+}
+
+function useDrive(index: number): model.Drive | null {
+ const { data } = useSuspenseQuery({
+ ...storageModelQuery,
+ select: useCallback(
+ (data: apiModel.Config): model.Drive | null => build(data)?.drives?.at(index) || null,
+ [index],
+ ),
+ });
+ return data;
+}
+
+function useMdRaid(index: number): model.MdRaid | null {
+ const { data } = useSuspenseQuery({
+ ...storageModelQuery,
+ select: useCallback(
+ (data: apiModel.Config): model.MdRaid | null => build(data)?.mdRaids?.at(index) || null,
+ [index],
+ ),
+ });
+ return data;
+}
+
+export { useModel, useMissingMountPaths, useDevice, useDrive, useMdRaid };
diff --git a/web/src/hooks/storage/partition.ts b/web/src/hooks/storage/partition.ts
index 407914ab95..272ab0b7d7 100644
--- a/web/src/hooks/storage/partition.ts
+++ b/web/src/hooks/storage/partition.ts
@@ -26,21 +26,21 @@ import { data } from "~/storage";
import { addPartition, editPartition, deletePartition } from "~/storage/partition";
type AddPartitionFn = (
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
data: data.Partition,
) => void;
function useAddPartition(): AddPartitionFn {
const apiModel = useStorageModel();
- return (list: "drives" | "mdRaids", listIndex: number | string, data: data.Partition) => {
- putStorageModel(addPartition(apiModel, list, listIndex, data));
+ return (collection: "drives" | "mdRaids", index: number | string, data: data.Partition) => {
+ putStorageModel(addPartition(apiModel, collection, index, data));
};
}
type EditPartitionFn = (
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
mountPath: string,
data: data.Partition,
) => void;
@@ -48,25 +48,25 @@ type EditPartitionFn = (
function useEditPartition(): EditPartitionFn {
const apiModel = useStorageModel();
return (
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
mountPath: string,
data: data.Partition,
) => {
- putStorageModel(editPartition(apiModel, list, listIndex, mountPath, data));
+ putStorageModel(editPartition(apiModel, collection, index, mountPath, data));
};
}
type DeletePartitionFn = (
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
mountPath: string,
) => void;
function useDeletePartition(): DeletePartitionFn {
const apiModel = useStorageModel();
- return (list: "drives" | "mdRaids", listIndex: number | string, mountPath: string) =>
- putStorageModel(deletePartition(apiModel, list, listIndex, mountPath));
+ return (collection: "drives" | "mdRaids", index: number | string, mountPath: string) =>
+ putStorageModel(deletePartition(apiModel, collection, index, mountPath));
}
export { useAddPartition, useEditPartition, useDeletePartition };
diff --git a/web/src/hooks/storage/space-policy.ts b/web/src/hooks/storage/space-policy.ts
index aa132b013b..d30741512c 100644
--- a/web/src/hooks/storage/space-policy.ts
+++ b/web/src/hooks/storage/space-policy.ts
@@ -25,12 +25,16 @@ import { putStorageModel } from "~/api";
import { data } from "~/storage";
import { setSpacePolicy } from "~/storage/space-policy";
-type setSpacePolicyFn = (list: string, listIndex: number | string, data: data.SpacePolicy) => void;
+type setSpacePolicyFn = (
+ collection: string,
+ index: number | string,
+ data: data.SpacePolicy,
+) => void;
function useSetSpacePolicy(): setSpacePolicyFn {
- const apiModel = useStorageModel();
- return (list: string, listIndex: number | string, data: data.SpacePolicy) => {
- putStorageModel(setSpacePolicy(apiModel, list, listIndex, data));
+ const model = useStorageModel();
+ return (collection: string, index: number | string, data: data.SpacePolicy) => {
+ putStorageModel(setSpacePolicy(model, collection, index, data));
};
}
diff --git a/web/src/routes/paths.ts b/web/src/routes/paths.ts
index 8c2357f8b3..96f764f142 100644
--- a/web/src/routes/paths.ts
+++ b/web/src/routes/paths.ts
@@ -78,10 +78,10 @@ const STORAGE = {
progress: "/storage/progress",
editBootDevice: "/storage/boot-device/edit",
editEncryption: "/storage/encryption/edit",
- editSpacePolicy: "/storage/:list/:listIndex/space-policy/edit",
- formatDevice: "/storage/:list/:listIndex/format",
- addPartition: "/storage/:list/:listIndex/partitions/add",
- editPartition: "/storage/:list/:listIndex/partitions/:partitionId/edit",
+ editSpacePolicy: "/storage/:collection/:index/space-policy/edit",
+ formatDevice: "/storage/:collection/:index/format",
+ addPartition: "/storage/:collection/:index/partitions/add",
+ editPartition: "/storage/:collection/:index/partitions/:partitionId/edit",
selectDevice: "/storage/devices/select",
volumeGroup: {
add: "/storage/volume-groups/add",
diff --git a/web/src/storage/devices-manager.ts b/web/src/storage/devices-manager.ts
index 6f9f141e24..5c0fa42fb2 100644
--- a/web/src/storage/devices-manager.ts
+++ b/web/src/storage/devices-manager.ts
@@ -64,14 +64,14 @@ export default class DevicesManager {
* Whether the given device exists in system.
*/
existInSystem(device: system.Device): boolean {
- return this.system.find((d) => d.sid === device.sid) !== undefined;
+ return this.systemDevice(device.sid) !== undefined;
}
/**
* Whether the given device exists in staging.
*/
existInStaging(device: proposal.Device): boolean {
- return this.staging.find((d) => d.sid === device.sid) !== undefined;
+ return this.stagingDevice(device.sid) !== undefined;
}
/**
diff --git a/web/src/storage/model.ts b/web/src/storage/model.ts
index dfc168c7ab..3846f4a1b1 100644
--- a/web/src/storage/model.ts
+++ b/web/src/storage/model.ts
@@ -41,14 +41,7 @@ interface Boot extends Omit {
getDevice: () => Drive | MdRaid | null;
}
-/**
- * @fixme Remove list and listIndex from types once the components are adapted to receive a list
- * and an index instead of a device object. See ConfigEditor component.
- */
-
interface Drive extends Omit {
- list: string;
- listIndex: number;
isExplicitBoot: boolean;
isUsed: boolean;
isAddingPartitions: boolean;
@@ -63,8 +56,6 @@ interface Drive extends Omit {
}
interface MdRaid extends Omit {
- list: string;
- listIndex: number;
isExplicitBoot: boolean;
isUsed: boolean;
isAddingPartitions: boolean;
@@ -86,8 +77,6 @@ interface Partition extends apiModel.Partition {
}
interface VolumeGroup extends Omit {
- list: string;
- listIndex: number;
logicalVolumes: LogicalVolume[];
getTargetDevices: () => Drive[];
getMountPaths: () => string[];
@@ -213,34 +202,16 @@ function partitionableProperties(
};
}
-function buildDrive(
- apiDrive: apiModel.Drive,
- listIndex: number,
- apiModel: apiModel.Config,
- model: Model,
-): Drive {
- const list = "drives";
-
+function buildDrive(apiDrive: apiModel.Drive, apiModel: apiModel.Config, model: Model): Drive {
return {
...apiDrive,
- list,
- listIndex,
...partitionableProperties(apiDrive, apiModel, model),
};
}
-function buildMdRaid(
- apiMdRaid: apiModel.MdRaid,
- listIndex: number,
- apiModel: apiModel.Config,
- model: Model,
-): MdRaid {
- const list = "mdRaids";
-
+function buildMdRaid(apiMdRaid: apiModel.MdRaid, apiModel: apiModel.Config, model: Model): MdRaid {
return {
...apiMdRaid,
- list,
- listIndex,
...partitionableProperties(apiMdRaid, apiModel, model),
};
}
@@ -249,13 +220,7 @@ function buildLogicalVolume(logicalVolumeData: apiModel.LogicalVolume): LogicalV
return { ...logicalVolumeData };
}
-function buildVolumeGroup(
- apiVolumeGroup: apiModel.VolumeGroup,
- listIndex: number,
- model: Model,
-): VolumeGroup {
- const list = "volumeGroups";
-
+function buildVolumeGroup(apiVolumeGroup: apiModel.VolumeGroup, model: Model): VolumeGroup {
const getMountPaths = (): string[] => {
return (apiVolumeGroup.logicalVolumes || []).map((l) => l.mountPath).filter((p) => p);
};
@@ -271,8 +236,6 @@ function buildVolumeGroup(
return {
...apiVolumeGroup,
logicalVolumes: buildLogicalVolumes(),
- list,
- listIndex,
getMountPaths,
getTargetDevices,
};
@@ -294,15 +257,15 @@ function buildModel(apiModel: apiModel.Config): Model {
};
const buildDrives = (): Drive[] => {
- return (apiModel.drives || []).map((d, i) => buildDrive(d, i, apiModel, model));
+ return (apiModel.drives || []).map((d) => buildDrive(d, apiModel, model));
};
const buildMdRaids = (): MdRaid[] => {
- return (apiModel.mdRaids || []).map((r, i) => buildMdRaid(r, i, apiModel, model));
+ return (apiModel.mdRaids || []).map((r) => buildMdRaid(r, apiModel, model));
};
const buildVolumeGroups = (): VolumeGroup[] => {
- return (apiModel.volumeGroups || []).map((v, i) => buildVolumeGroup(v, i, model));
+ return (apiModel.volumeGroups || []).map((v) => buildVolumeGroup(v, model));
};
const withMountPaths = (): (Drive | MdRaid | VolumeGroup)[] => {
diff --git a/web/src/storage/partition.ts b/web/src/storage/partition.ts
index bfe337cfcb..020fe240dc 100644
--- a/web/src/storage/partition.ts
+++ b/web/src/storage/partition.ts
@@ -43,65 +43,65 @@ function indexByPath(device: Partitionable, path: string): number {
* */
function addPartition(
model: model.Config,
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
data: data.Partition,
): model.Config {
model = copyApiModel(model);
- const device = findDevice(model, list, listIndex);
+ const device = findDevice(model, collection, index);
if (device === undefined) return model;
// Reset the spacePolicy to the default value if the device goes from unused to used
- if (!isUsed(model, list, listIndex) && device.spacePolicy === "keep") device.spacePolicy = null;
+ if (!isUsed(model, collection, index) && device.spacePolicy === "keep") device.spacePolicy = null;
const partition = buildPartition(data);
- const index = indexByName(device, partition.name);
+ const partitionIndex = indexByName(device, partition.name);
- if (index === -1) device.partitions.push(partition);
- else device.partitions[index] = partition;
+ if (partitionIndex === -1) device.partitions.push(partition);
+ else device.partitions[partitionIndex] = partition;
return model;
}
function editPartition(
model: model.Config,
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
mountPath: string,
data: data.Partition,
): model.Config {
model = copyApiModel(model);
- const device = findDevice(model, list, listIndex);
+ const device = findDevice(model, collection, index);
if (device === undefined) return model;
- const index = indexByPath(device, mountPath);
- if (index === -1) return model;
+ const partitionIndex = indexByPath(device, mountPath);
+ if (partitionIndex === -1) return model;
- const oldPartition = device.partitions[index];
+ const oldPartition = device.partitions[partitionIndex];
const newPartition = { ...oldPartition, ...buildPartition(data) };
- device.partitions.splice(index, 1, newPartition);
+ device.partitions.splice(partitionIndex, 1, newPartition);
return model;
}
function deletePartition(
model: model.Config,
- list: "drives" | "mdRaids",
- listIndex: number | string,
+ collection: "drives" | "mdRaids",
+ index: number | string,
mountPath: string,
): model.Config {
model = copyApiModel(model);
- const device = findDevice(model, list, listIndex);
+ const device = findDevice(model, collection, index);
if (device === undefined) return model;
- const index = indexByPath(device, mountPath);
- device.partitions.splice(index, 1);
+ const partitionIndex = indexByPath(device, mountPath);
+ device.partitions.splice(partitionIndex, 1);
// Do not delete anything if the device is not really used
- if (!isUsed(model, list, listIndex)) {
+ if (!isUsed(model, collection, index)) {
device.spacePolicy = "keep";
}
diff --git a/web/src/storage/proposal.ts b/web/src/storage/proposal.ts
new file mode 100644
index 0000000000..d80ce09d80
--- /dev/null
+++ b/web/src/storage/proposal.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) [2025] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import { sift } from "radashi";
+import type { Proposal, Device } from "~/api/proposal/storage";
+
+function flatDevices(proposal: Proposal): Device[] {
+ const partitions = proposal.devices?.flatMap((d) => d.partitions);
+ const logicalVolumes = proposal.devices?.flatMap((d) => d.logicalVolumes);
+ return sift([proposal.devices, partitions, logicalVolumes].flat());
+}
+
+export { flatDevices };
diff --git a/web/src/storage/space-policy.ts b/web/src/storage/space-policy.ts
index 2b3c61d74b..ab0af2b091 100644
--- a/web/src/storage/space-policy.ts
+++ b/web/src/storage/space-policy.ts
@@ -57,20 +57,20 @@ function setActions(device: model.Drive, actions: data.SpacePolicyAction[]) {
}
function setSpacePolicy(
- apiModel: model.Config,
- list: string,
- listIndex: number | string,
+ model: model.Config,
+ collection: string,
+ index: number | string,
data: data.SpacePolicy,
): model.Config {
- apiModel = copyApiModel(apiModel);
- const apiDevice = findDevice(apiModel, list, listIndex);
+ model = copyApiModel(model);
+ const apiDevice = findDevice(model, collection, index);
- if (apiDevice === undefined) return apiModel;
+ if (apiDevice === undefined) return model;
apiDevice.spacePolicy = data.type;
if (data.type === "custom") setActions(apiDevice, data.actions || []);
- return apiModel;
+ return model;
}
export { setSpacePolicy };
diff --git a/web/src/storage/system.ts b/web/src/storage/system.ts
index 6bc8253ac8..1f3be1ac95 100644
--- a/web/src/storage/system.ts
+++ b/web/src/storage/system.ts
@@ -20,10 +20,17 @@
* find current contact information at www.suse.com.
*/
+import { sift } from "radashi";
import type { System, Device } from "~/api/system/storage";
+function flatDevices(system: System): Device[] {
+ const partitions = system.devices?.flatMap((d) => d.partitions);
+ const logicalVolumes = system.devices?.flatMap((d) => d.logicalVolumes);
+ return sift([system.devices, partitions, logicalVolumes].flat());
+}
+
function findDevice(system: System, sid: number): Device | undefined {
- const device = system.devices.find((d) => d.sid === sid);
+ const device = flatDevices(system).find((d) => d.sid === sid);
if (device === undefined) console.warn("Device not found:", sid);
return device;
@@ -33,4 +40,8 @@ function findDevices(system: System, sids: number[]): Device[] {
return sids.map((sid) => findDevice(system, sid)).filter((d) => d);
}
-export { findDevice, findDevices };
+function findDeviceByName(system: System, name: string): Device | null {
+ return flatDevices(system).find((d) => d.name === name) || null;
+}
+
+export { flatDevices, findDevice, findDevices, findDeviceByName };