diff --git a/web/src/components/storage/SpaceActionsTable.test.tsx b/web/src/components/storage/SpaceActionsTable.test.tsx index 1d6a60ace2..1de9f9d53b 100644 --- a/web/src/components/storage/SpaceActionsTable.test.tsx +++ b/web/src/components/storage/SpaceActionsTable.test.tsx @@ -28,6 +28,7 @@ import { deviceChildren, gib } from "~/components/storage/utils"; import { plainRender } from "~/test-utils"; import SpaceActionsTable, { SpaceActionsTableProps } from "~/components/storage/SpaceActionsTable"; import { StorageDevice } from "~/types/storage"; +import * as configModel from "~/api/storage/types/config-model"; const sda: StorageDevice = { sid: 59, @@ -80,6 +81,22 @@ sda.partitionTable = { unusedSlots: [{ start: 3, size: gib(2) }], }; +const mockDrive: configModel.Drive = { + name: "/dev/sda", + partitions: [ + { + name: "/dev/sda2", + mountPath: "swap", + filesystem: { reuse: false, default: true }, + }, + ], +}; + +const mockUseConfigModelFn = jest.fn(); +jest.mock("~/queries/storage/config-model", () => ({ + useConfigModel: () => mockUseConfigModelFn(), +})); + /** * Function to ask for the action of a device. * @@ -101,6 +118,8 @@ describe("SpaceActionsTable", () => { deviceAction, onActionChange: jest.fn(), }; + + mockUseConfigModelFn.mockReturnValue({ drives: [] }); }); it("shows the devices to configure the space actions", () => { @@ -141,6 +160,22 @@ describe("SpaceActionsTable", () => { expect(sda2ShrinkButton).not.toBeDisabled(); }); + describe("if a partition is going to be used", () => { + beforeEach(() => { + mockUseConfigModelFn.mockReturnValue({ drives: [mockDrive] }); + }); + + it("disables shrink and delete actions for the partition", () => { + plainRender(); + + const sda2Row = screen.getByRole("row", { name: /sda2/ }); + const sda2ShrinkButton = within(sda2Row).getByRole("button", { name: "Allow shrink" }); + const sda2DeleteButton = within(sda2Row).getByRole("button", { name: "Delete" }); + expect(sda2ShrinkButton).toBeDisabled(); + expect(sda2DeleteButton).toBeDisabled(); + }); + }); + it("allows to change the action", async () => { const { user } = plainRender(); diff --git a/web/src/components/storage/SpaceActionsTable.tsx b/web/src/components/storage/SpaceActionsTable.tsx index 196b3b41fa..7dd5221255 100644 --- a/web/src/components/storage/SpaceActionsTable.tsx +++ b/web/src/components/storage/SpaceActionsTable.tsx @@ -34,7 +34,7 @@ import { import { sprintf } from "sprintf-js"; import { _ } from "~/i18n"; -import { deviceSize } from "~/components/storage/utils"; +import { deviceSize, formattedPath } from "~/components/storage/utils"; import { DeviceName, DeviceDetails, @@ -43,8 +43,25 @@ import { } from "~/components/storage/device-utils"; import { Icon } from "~/components/layout"; import { PartitionSlot, SpacePolicyAction, StorageDevice } from "~/types/storage"; +import { configModel } from "~/api/storage/types"; import { TreeTableColumn } from "~/components/core/TreeTable"; import { Table, Td, Th, Tr, Thead, Tbody } from "@patternfly/react-table"; +import { useConfigModel } from "~/queries/storage/config-model"; + +const isUsedPartition = (partition: configModel.Partition): boolean => { + return partition.filesystem !== undefined || partition.alias !== undefined; +}; + +// FIXME: there is too much logic here. This is one of those cases that should be considered +// when restructuring the hooks and queries. +const useReusedPartition = (name: string): configModel.Partition | undefined => { + const model = useConfigModel(); + + if (!model || !name) return; + + const allPartitions = model.drives.flatMap((d) => d.partitions); + return allPartitions.find((p) => p.name === name && isUsedPartition(p)); +}; /** * Info about the device. @@ -53,6 +70,14 @@ import { Table, Td, Th, Tr, Thead, Tbody } from "@patternfly/react-table"; const DeviceInfoContent = ({ device }: { device: StorageDevice }) => { const minSize = device.shrinking?.supported; + const reused = useReusedPartition(device.name); + if (reused) { + if (!reused.mountPath) return _("The device will be used by the new system."); + + // TRANSLATORS: %s is a mount path like "/home". + return sprintf(_("The device will be mounted at %s."), formattedPath(reused.mountPath)); + } + if (minSize) { const recoverable = device.size - minSize; return sprintf( @@ -114,8 +139,11 @@ const DeviceActionSelector = ({ }) => { const changeAction = (value) => onChange({ deviceName: device.name, value }); - const isResizeDisabled = device.shrinking?.supported === undefined; - const hasInfo = device.shrinking !== undefined; + const forceKeep = !!useReusedPartition(device.name); + const isResizeDisabled = forceKeep || device.shrinking?.supported === undefined; + const isDeleteDisabled = forceKeep; + const hasInfo = forceKeep || device.shrinking !== undefined; + const adjustedAction = forceKeep ? "keep" : action; return ( @@ -124,20 +152,21 @@ const DeviceActionSelector = ({ changeAction("keep")} /> changeAction("resizeIfNeeded")} /> changeAction("delete")} />