diff --git a/web/src/components/storage/ProposalFileSystemsSection.jsx b/web/src/components/storage/ProposalFileSystemsSection.jsx
deleted file mode 100644
index 0b0e5f632d..0000000000
--- a/web/src/components/storage/ProposalFileSystemsSection.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) [2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * 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 React from "react";
-import { _ } from "~/i18n";
-import { Section } from "~/components/core";
-import { ProposalVolumes } from "~/components/storage";
-import { noop } from "~/utils";
-
-/**
- * @typedef {import ("~/client/storage").ProposalManager.ProposalSettings} ProposalSettings
- * @typedef {import ("~/client/storage").ProposalManager.Volume} Volume
- */
-
-/**
- * Section for editing the proposal file systems
- * @component
- *
- * @callback onChangeFn
- * @param {object} settings
- *
- * @param {object} props
- * @param {ProposalSettings} props.settings
- * @param {Volume[]} [props.volumeTemplates=[]]
- * @param {boolean} [props.isLoading=false]
- * @param {onChangeFn} [props.onChange=noop]
- *
- */
-export default function ProposalFileSystemsSection({
- settings,
- volumeTemplates = [],
- isLoading = false,
- onChange = noop
-}) {
- const { volumes = [] } = settings;
-
- const changeVolumes = (volumes) => {
- onChange({ volumes });
- };
-
- // Templates for already existing mount points are filtered out
- const usefulTemplates = () => {
- const mountPaths = volumes.map(v => v.mountPath);
- return volumeTemplates.filter(t => (
- t.mountPath.length > 0 && !mountPaths.includes(t.mountPath)
- ));
- };
-
- const encryption = settings.encryptionPassword !== undefined && settings.encryptionPassword.length > 0;
-
- return (
-
-
-
- );
-}
diff --git a/web/src/components/storage/ProposalFileSystemsSection.test.jsx b/web/src/components/storage/ProposalFileSystemsSection.test.jsx
deleted file mode 100644
index 0b1493a5ff..0000000000
--- a/web/src/components/storage/ProposalFileSystemsSection.test.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) [2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * 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 React from "react";
-import { screen, within } from "@testing-library/react";
-import { plainRender } from "~/test-utils";
-import { ProposalFileSystemsSection } from "~/components/storage";
-
-const props = {
- settings: {},
- isLoading: false,
- onChange: jest.fn()
-};
-
-describe("ProposalFileSystemsSection", () => {
- it("renders a section holding file systems related stuff", () => {
- plainRender();
- screen.getByRole("region", { name: "File systems" });
- screen.getByRole("grid", { name: /mount points/ });
- });
-
- it("requests a volume change when onChange callback is triggered", async () => {
- const { user } = plainRender();
- const button = screen.getByRole("button", { name: "Actions" });
-
- await user.click(button);
-
- const menu = screen.getByRole("menu");
- const reset = within(menu).getByRole("menuitem", { name: /Reset/ });
-
- await user.click(reset);
-
- expect(props.onChange).toHaveBeenCalledWith(
- { volumes: expect.any(Array) }
- );
- });
-});
diff --git a/web/src/components/storage/ProposalPage.jsx b/web/src/components/storage/ProposalPage.jsx
index 85e29c5b41..c3d4753623 100644
--- a/web/src/components/storage/ProposalPage.jsx
+++ b/web/src/components/storage/ProposalPage.jsx
@@ -29,9 +29,7 @@ import {
ProposalActionsSection,
ProposalPageMenu,
ProposalSettingsSection,
- ProposalSpacePolicySection,
ProposalDeviceSection,
- ProposalFileSystemsSection,
ProposalTransactionalInfo
} from "~/components/storage";
import { IDLE } from "~/client/status";
@@ -233,17 +231,7 @@ export default function ProposalPage() {
-
- {
await screen.findByText(/\/dev\/vda/);
});
-it("renders the settings, find space and actions sections", async () => {
+it("renders the device, settings and actions sections", async () => {
installerRender();
+ await screen.findByText(/Device/);
await screen.findByText(/Settings/);
- await screen.findByText(/Find Space/);
await screen.findByText(/Planned Actions/);
});
diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx
index abc6830308..969d862b1f 100644
--- a/web/src/components/storage/ProposalSettingsSection.jsx
+++ b/web/src/components/storage/ProposalSettingsSection.jsx
@@ -24,6 +24,7 @@ import { Checkbox, Form, Skeleton, Switch, Tooltip } from "@patternfly/react-cor
import { _ } from "~/i18n";
import { If, PasswordAndConfirmationInput, Section, Popup } from "~/components/core";
+import { ProposalVolumes, ProposalSpacePolicyField } from "~/components/storage";
import { Icon } from "~/components/layout";
import { noop } from "~/utils";
import { hasFS } from "~/components/storage/utils";
@@ -283,6 +284,8 @@ const EncryptionField = ({
export default function ProposalSettingsSection({
settings,
encryptionMethods = [],
+ volumeTemplates = [],
+ isLoading = false,
onChange = noop
}) {
const changeEncryption = ({ password, method }) => {
@@ -302,8 +305,26 @@ export default function ProposalSettingsSection({
onChange({ volumes: settings.volumes });
};
+ const changeVolumes = (volumes) => {
+ onChange({ volumes });
+ };
+
+ const changeSpacePolicy = (policy, actions) => {
+ onChange({ spacePolicy: policy, spaceActions: actions });
+ };
+
const encryption = settings.encryptionPassword !== undefined && settings.encryptionPassword.length > 0;
+ const { volumes = [] } = settings;
+
+ // Templates for already existing mount points are filtered out
+ const usefulTemplates = () => {
+ const mountPaths = volumes.map(v => v.mountPath);
+ return volumeTemplates.filter(t => (
+ t.mountPath.length > 0 && !mountPaths.includes(t.mountPath)
+ ));
+ };
+
return (
<>
@@ -319,6 +340,20 @@ export default function ProposalSettingsSection({
isLoading={settings.encryptionPassword === undefined}
onChange={changeEncryption}
/>
+
+
>
);
diff --git a/web/src/components/storage/ProposalSettingsSection.test.jsx b/web/src/components/storage/ProposalSettingsSection.test.jsx
index 3077cd7903..29f7fc4723 100644
--- a/web/src/components/storage/ProposalSettingsSection.test.jsx
+++ b/web/src/components/storage/ProposalSettingsSection.test.jsx
@@ -36,7 +36,10 @@ jest.mock("@patternfly/react-core", () => {
let props;
beforeEach(() => {
- props = {};
+ props = {
+ settings: {},
+ onChange: jest.fn()
+ };
});
const rootVolume = { mountPath: "/", fsType: "Btrfs", outline: { snapshotsConfigurable: true } };
@@ -65,6 +68,28 @@ describe("if snapshots are not configurable", () => {
});
});
+it("renders a section holding file systems related stuff", () => {
+ plainRender();
+ screen.getByRole("grid", { name: "Table with mount points" });
+ screen.getByRole("grid", { name: /mount points/ });
+});
+
+it("requests a volume change when onChange callback is triggered", async () => {
+ const { user } = plainRender();
+ const button = screen.getByRole("button", { name: "Actions" });
+
+ await user.click(button);
+
+ const menu = screen.getByRole("menu");
+ const reset = within(menu).getByRole("menuitem", { name: /Reset/ });
+
+ await user.click(reset);
+
+ expect(props.onChange).toHaveBeenCalledWith(
+ { volumes: expect.any(Array) }
+ );
+});
+
describe("Encryption field", () => {
describe("if encryption password setting is not set yet", () => {
beforeEach(() => {
diff --git a/web/src/components/storage/ProposalSpacePolicySection.jsx b/web/src/components/storage/ProposalSpacePolicyField.jsx
similarity index 54%
rename from web/src/components/storage/ProposalSpacePolicySection.jsx
rename to web/src/components/storage/ProposalSpacePolicyField.jsx
index c08135c0e5..17455afb4b 100644
--- a/web/src/components/storage/ProposalSpacePolicySection.jsx
+++ b/web/src/components/storage/ProposalSpacePolicyField.jsx
@@ -19,13 +19,13 @@
* find current contact information at www.suse.com.
*/
-import React, { useEffect } from "react";
-import { FormSelect, FormSelectOption } from "@patternfly/react-core";
+import React, { useEffect, useState } from "react";
+import { Button, Form, FormSelect, FormSelectOption, Skeleton } from "@patternfly/react-core";
-import { _, N_ } from "~/i18n";
+import { _, N_, n_ } from "~/i18n";
import { deviceSize } from '~/components/storage/utils';
-import { If, OptionsPicker, Section, SectionSkeleton } from "~/components/core";
-import { noop, useLocalStorage } from "~/utils";
+import { If, OptionsPicker, Popup, SectionSkeleton } from "~/components/core";
+import { noop } from "~/utils";
import { sprintf } from "sprintf-js";
import { Table, Thead, Tr, Th, Tbody, Td, TreeRowWrapper } from '@patternfly/react-table';
@@ -48,31 +48,57 @@ const SPACE_POLICIES = [
{
id: "delete",
label: N_("Delete current content"),
- description: N_("All partitions will be removed and any data in the disks will be lost.")
+ description: N_("All partitions will be removed and any data in the disks will be lost."),
+ summaryLabels: [
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space deleting all content[...]"
+ N_("deleting all content of the installation device"),
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space deleting all content[...]"
+ N_("deleting all content of the %d selected disks")
+ ]
},
{
id: "resize",
label: N_("Shrink existing partitions"),
- description: N_("The data is kept, but the current partitions will be resized as needed.")
+ description: N_("The data is kept, but the current partitions will be resized as needed."),
+ summaryLabels: [
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space shrinking partitions[...]"
+ N_("shrinking partitions of the installation device"),
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space shrinking partitions[...]"
+ N_("shrinking partitions of the %d selected disks")
+ ]
},
{
id: "keep",
label: N_("Use available space"),
- description: N_("The data is kept. Only the space not assigned to any partition will be used.")
+ description: N_("The data is kept. Only the space not assigned to any partition will be used."),
+ summaryLabels: [
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space without modifying any partition".
+ N_("without modifying any partition")
+ ]
},
{
id: "custom",
label: N_("Custom"),
- description: N_("Select what to do with each partition.")
+ description: N_("Select what to do with each partition."),
+ summaryLabels: [
+ // TRANSLATORS: This is presented next to the label "Find space", so the whole sentence
+ // would read as "Find space performing a custom set of actions".
+ N_("performing a custom set of actions")
+ ]
}
];
// Names of the columns for the policy actions.
const columnNames = {
device: N_("Used device"),
- content: N_("Current content"),
+ content: N_("Details"),
size: N_("Size"),
- details: N_("Details"),
+ details: N_("Size details"),
action: N_("Action")
};
@@ -84,63 +110,23 @@ const columnNames = {
* @param {StorageDevice} props.device
*/
const DeviceDescriptionColumn = ({ device }) => {
- return (
- <>
-
{device.name}
- {`${device.vendor} ${device.model}`}}
- />
- >
- );
+ if (device.isDrive || device.type === "lvmVg") return device.name;
+
+ return device.name.split("/").pop();
};
/**
- * Column content with information about the current content of the device.
+ * Column content with details about the device.
* @component
*
* @param {object} props
* @param {StorageDevice} props.device
*/
const DeviceContentColumn = ({ device }) => {
- const PartitionTableContent = () => {
- return (
-
- {/* TRANSLATORS: %s is replaced by partition table type (e.g., GPT) */}
- {sprintf(_("%s partition table"), device.partitionTable.type.toUpperCase())}
-
- );
- };
-
- const BlockContent = () => {
- const renderContent = () => {
- const systems = device.systems;
- if (systems.length > 0) return systems.join(", ");
-
- const filesystem = device.filesystem;
- if (filesystem?.isEFI) return _("EFI system partition");
- if (filesystem) {
- // TRANSLATORS: %s is replaced by a file system type (e.g., btrfs).
- return sprintf(_("%s file system"), filesystem?.type);
- }
-
- const component = device.component;
- switch (component?.type) {
- case "physical_volume":
- // TRANSLATORS: %s is replaced by a LVM volume group name (e.g., /dev/vg0).
- return sprintf(_("LVM physical volume of %s"), component.deviceNames[0]);
- case "md_device":
- // TRANSLATORS: %s is replaced by a RAID name (e.g., /dev/md0).
- return sprintf(_("Member of RAID %s"), component.deviceNames[0]);
- default:
- return _("Not identified");
- }
- };
-
- return