diff --git a/src/components/storage/CockpitStorageIntegration.jsx b/src/components/storage/CockpitStorageIntegration.jsx
index abbdeaa05c..9511c31cec 100644
--- a/src/components/storage/CockpitStorageIntegration.jsx
+++ b/src/components/storage/CockpitStorageIntegration.jsx
@@ -79,7 +79,8 @@ import {
import { EmptyStatePanel } from "cockpit-components-empty-state";
-import { checkConfiguredStorage, checkUseFreeSpace } from "./InstallationScenario.jsx";
+import { checkConfiguredStorage } from "./scenarios/UseConfiguredStorage.jsx";
+import { checkUseFreeSpace } from "./scenarios/UseFreeSpace.jsx";
import "./CockpitStorageIntegration.scss";
diff --git a/src/components/storage/InstallationMethod.jsx b/src/components/storage/InstallationMethod.jsx
index 7046a419c2..900865e739 100644
--- a/src/components/storage/InstallationMethod.jsx
+++ b/src/components/storage/InstallationMethod.jsx
@@ -42,8 +42,9 @@ import { getNewPartitioning } from "../../hooks/Storage.jsx";
import { AnacondaWizardFooter } from "../AnacondaWizardFooter.jsx";
import { InstallationDestination } from "./InstallationDestination.jsx";
-import { InstallationScenario, scenarios } from "./InstallationScenario.jsx";
+import { InstallationScenario } from "./InstallationScenario.jsx";
import { ReclaimSpaceModal } from "./ReclaimSpaceModal.jsx";
+import { scenarios } from "./scenarios/index.js";
const _ = cockpit.gettext;
diff --git a/src/components/storage/InstallationScenario.jsx b/src/components/storage/InstallationScenario.jsx
index 8c16e971ba..14246215ea 100644
--- a/src/components/storage/InstallationScenario.jsx
+++ b/src/components/storage/InstallationScenario.jsx
@@ -19,26 +19,21 @@ import cockpit from "cockpit";
import React, { useContext, useEffect, useMemo, useState } from "react";
import {
- Checkbox,
FormGroup,
FormSection,
Radio,
Title,
} from "@patternfly/react-core";
-import { getAutopartReuseDBusRequest } from "../../apis/storage_partitioning.js";
-
import { setStorageScenarioAction } from "../../actions/storage-actions.js";
import { debug } from "../../helpers/log.js";
import {
- bootloaderTypes,
- getDeviceAncestors,
getLockedLUKSDevices,
} from "../../helpers/storage.js";
+import { AvailabilityState } from "./scenarios/helpers.js";
import {
- DialogsContext,
StorageContext,
StorageDefaultsContext,
SystemTypeContext
@@ -55,314 +50,13 @@ import {
useUsablePartitions,
} from "../../hooks/Storage.jsx";
-import { StorageReview } from "../review/StorageReview.jsx";
import { EncryptedDevices } from "./EncryptedDevices.jsx";
-import { helpConfiguredStorage, helpEraseAll, helpHomeReuse, helpMountPointMapping, helpUseFreeSpace } from "./HelpAutopartOptions.jsx";
+import { scenarios } from "./scenarios/index.js";
import "./InstallationScenario.scss";
const _ = cockpit.gettext;
-function AvailabilityState (available = false, hidden = true, reason = null, hint = null, enforceAction = false) {
- this.available = available;
- this.enforceAction = enforceAction;
- this.hidden = hidden;
- this.reason = reason;
- this.hint = hint;
-}
-
-const checkEraseAll = ({ diskTotalSpace, requiredSize, selectedDisks }) => {
- const availability = new AvailabilityState();
-
- availability.available = !!selectedDisks.length;
- availability.hidden = false;
-
- if (diskTotalSpace < requiredSize) {
- availability.available = false;
- availability.reason = _("Not enough space on selected disks.");
- availability.hint = cockpit.format(_(
- "The installation needs $1 of disk space; " +
- "however, the capacity of the selected disks is only $0."
- ), cockpit.format_bytes(diskTotalSpace), cockpit.format_bytes(requiredSize));
- }
-
- return availability;
-};
-
-export const checkUseFreeSpace = ({ diskFreeSpace, diskTotalSpace, requiredSize, selectedDisks }) => {
- const availability = new AvailabilityState();
-
- availability.hidden = false;
- availability.available = !!selectedDisks.length;
-
- if (diskFreeSpace > 0 && diskTotalSpace > 0) {
- availability.hidden = diskFreeSpace === diskTotalSpace;
- }
- if (diskFreeSpace < requiredSize) {
- availability.enforceAction = true;
- availability.reason = _("Not enough free space on the selected disks.");
- availability.hint = cockpit.format(
- _("To use this option, resize or remove existing partitions to free up at least $0."),
- cockpit.format_bytes(requiredSize)
- );
- }
- return availability;
-};
-
-const getMissingNonmountablePartitions = (usablePartitions, mountPointConstraints) => {
- const existingNonmountablePartitions = usablePartitions
- .filter(device => !device.formatData.mountable.v)
- .map(device => device.formatData.type.v);
-
- const missingNonmountablePartitions = mountPointConstraints.filter(constraint =>
- constraint.required.v &&
- !constraint["mount-point"].v &&
- !existingNonmountablePartitions.includes(constraint["required-filesystem-type"].v))
- .map(constraint => constraint.description);
-
- return missingNonmountablePartitions;
-};
-
-const checkMountPointMapping = ({ mountPointConstraints, selectedDisks, usablePartitions }) => {
- const availability = new AvailabilityState();
-
- availability.hidden = false;
- availability.available = !!selectedDisks.length;
-
- const missingNMParts = getMissingNonmountablePartitions(usablePartitions, mountPointConstraints);
- const hasFilesystems = usablePartitions
- .filter(device => device.formatData.mountable.v || device.formatData.type.v === "luks").length > 0;
-
- if (!hasFilesystems) {
- // No usable devices on the selected disks: hide the scenario to reduce UI clutter
- availability.hidden = true;
- } else if (missingNMParts.length) {
- availability.available = false;
- availability.reason = cockpit.format(_("Some required partitions are missing: $0"), missingNMParts.join(", "));
- }
- return availability;
-};
-
-const checkHomeReuse = ({ autopartScheme, devices, originalExistingSystems, selectedDisks }) => {
- const availability = new AvailabilityState();
- let reusedOS = null;
-
- availability.hidden = false;
- availability.available = !!selectedDisks.length;
-
- const isCompleteOSOnDisks = (osData, disks) => {
- const osDisks = osData.devices.v.map(deviceId => getDeviceAncestors(devices, deviceId))
- .reduce((disks, ancestors) => disks.concat(ancestors))
- .filter(dev => devices[dev].type.v === "disk")
- .reduce((uniqueDisks, disk) => uniqueDisks.includes(disk) ? uniqueDisks : [...uniqueDisks, disk], []);
- const missingDisks = osDisks.filter(disk => !disks.includes(disk));
- return missingDisks.length === 0;
- };
-
- const getUnknownMountPoints = (scheme, existingOS) => {
- const reuseRequest = getAutopartReuseDBusRequest(scheme);
- const isBootloader = (device) => bootloaderTypes.includes(devices[device].formatData.type.v);
- const existingMountPoints = Object.entries(existingOS["mount-points"].v)
- .map(([mountPoint, device]) => isBootloader(device) ? "bootloader" : mountPoint);
-
- const managedMountPoints = reuseRequest["reformatted-mount-points"].v
- .concat(reuseRequest["reused-mount-points"].v, reuseRequest["removed-mount-points"].v);
-
- const unknownMountPoints = existingMountPoints.filter(i => !managedMountPoints.includes(i));
- return unknownMountPoints;
- };
-
- // Check that exactly one Linux OS is present and it is Fedora Linux
- // (Stronger check for mountpoints uniqueness is in the backend
- const linuxSystems = originalExistingSystems.filter(osdata => osdata["os-name"].v.includes("Linux"))
- .filter(osdata => isCompleteOSOnDisks(osdata, selectedDisks));
- if (linuxSystems.length === 0) {
- availability.available = false;
- availability.hidden = true;
- debug("home reuse: No existing Linux system found.");
- } else if (linuxSystems.length > 1) {
- availability.available = false;
- availability.hidden = true;
- debug("home reuse: Multiple existing Linux systems found.");
- } else {
- reusedOS = linuxSystems[0];
- if (!linuxSystems.some(osdata => osdata["os-name"].v.includes("Fedora"))) {
- availability.available = false;
- availability.hidden = true;
- debug("home reuse: No existing Fedora Linux system found.");
- }
- }
-
- debug(`home reuse: Default scheme is ${autopartScheme}.`);
- if (reusedOS) {
- // Check that required autopartitioning scheme matches reused OS.
- // Check just "/home". To be more generic we could check all reused devices (as the backend).
- const homeDevice = reusedOS["mount-points"].v["/home"];
- const homeDeviceType = devices[homeDevice]?.type.v;
- const requiredSchemeTypes = {
- BTRFS: "btrfs subvolume",
- LVM: "lvmlv",
- LVM_THINP: "lvmthinlv",
- PLAIN: "partition",
- };
- if (homeDeviceType !== requiredSchemeTypes[autopartScheme]) {
- availability.available = false;
- availability.hidden = true;
- debug(`home reuse: No reusable existing Linux system found, reused devices must have ${requiredSchemeTypes[autopartScheme]} type`);
- }
- }
-
- if (reusedOS) {
- // Check that existing system does not have mountpoints unexpected
- // by the required autopartitioning scheme
- const unknownMountPoints = getUnknownMountPoints(autopartScheme, reusedOS);
- if (unknownMountPoints.length > 0) {
- availability.available = false;
- availability.hidden = true;
- console.info(`home reuse: Unknown existing mountpoints found ${unknownMountPoints}`);
- }
- }
-
- // TODO checks:
- // - luks - partitions are unlocked - enforce? allow opt-out?
- // - size ?
- // - Windows system along (forbidden for now?)
-
- return availability;
-};
-
-export const checkConfiguredStorage = ({
- devices,
- mountPointConstraints,
- newMountPoints,
- partitioning,
- storageScenarioId,
-}) => {
- const availability = new AvailabilityState();
-
- const currentPartitioningMatches = storageScenarioId === "use-configured-storage";
- availability.hidden = partitioning === undefined || !currentPartitioningMatches;
-
- availability.available = (
- newMountPoints === undefined ||
- (
- mountPointConstraints
- ?.filter(m => m.required.v)
- .every(m => {
- const allDirs = [];
- const getNestedDirs = (object) => {
- if (!object) {
- return;
- }
- const { content, dir, subvolumes } = object;
-
- if (dir) {
- allDirs.push(dir);
- }
- if (content) {
- getNestedDirs(content);
- }
- if (subvolumes) {
- Object.keys(subvolumes).forEach(sv => getNestedDirs(subvolumes[sv]));
- }
- };
-
- if (m["mount-point"].v) {
- Object.keys(newMountPoints).forEach(key => getNestedDirs(newMountPoints[key]));
-
- return allDirs.includes(m["mount-point"].v);
- }
-
- if (m["required-filesystem-type"].v === "biosboot") {
- const biosboot = Object.keys(devices).find(d => devices[d].formatData.type.v === "biosboot");
-
- return biosboot !== undefined;
- }
-
- return false;
- })
- )
- );
-
- availability.review = ;
-
- return availability;
-};
-
-const ReclaimSpace = ({ availability }) => {
- const { isReclaimSpaceCheckboxChecked, setIsReclaimSpaceCheckboxChecked } = useContext(DialogsContext);
-
- useEffect(() => {
- setIsReclaimSpaceCheckboxChecked(availability.enforceAction);
- }, [availability.enforceAction, setIsReclaimSpaceCheckboxChecked]);
-
- return (
- setIsReclaimSpaceCheckboxChecked(value)}
- />
- );
-};
-
-export const scenarios = [{
- buttonLabel: _("Reinstall Fedora"),
- buttonVariant: "danger",
- check: checkHomeReuse,
- default: false,
- detail: helpHomeReuse,
- id: "home-reuse",
- // CLEAR_PARTITIONS_NONE = 0
- initializationMode: 0,
- label: _("Reinstall Fedora"),
-}, {
- buttonLabel: _("Erase data and install"),
- buttonVariant: "danger",
- check: checkEraseAll,
- default: true,
- detail: helpEraseAll,
- id: "erase-all",
- // CLEAR_PARTITIONS_ALL = 1
- initializationMode: 1,
- label: _("Use entire disk"),
-}, {
- action: ReclaimSpace,
- buttonLabel: _("Install"),
- buttonVariant: "primary",
- canReclaimSpace: true,
- check: checkUseFreeSpace,
- default: false,
- detail: helpUseFreeSpace,
- id: "use-free-space",
- // CLEAR_PARTITIONS_NONE = 0
- initializationMode: 0,
- label: _("Share disk with other operating system"),
-}, {
- buttonLabel: _("Apply mount point assignment and install"),
- buttonVariant: "danger",
- check: checkMountPointMapping,
- default: false,
- detail: helpMountPointMapping,
- id: "mount-point-mapping",
- // CLEAR_PARTITIONS_NONE = 0
- initializationMode: 0,
- label: _("Mount point assignment"),
-}, {
- buttonLabel: _("Install"),
- buttonVariant: "danger",
- check: checkConfiguredStorage,
- default: false,
- detail: helpConfiguredStorage,
- id: "use-configured-storage",
- // CLEAR_PARTITIONS_NONE = 0
- initializationMode: 0,
- label: _("Use configured storage"),
-}
-];
-
export const useScenario = () => {
const { storageScenarioId } = useContext(StorageContext);
const [scenario, setScenario] = useState({});
diff --git a/src/components/storage/scenarios/EraseAll.jsx b/src/components/storage/scenarios/EraseAll.jsx
new file mode 100644
index 0000000000..ed006e5326
--- /dev/null
+++ b/src/components/storage/scenarios/EraseAll.jsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import { AvailabilityState } from "./helpers.js";
+
+import { helpEraseAll } from "../HelpAutopartOptions.jsx";
+
+const _ = cockpit.gettext;
+
+const checkEraseAll = ({ diskTotalSpace, requiredSize, selectedDisks }) => {
+ const availability = new AvailabilityState();
+
+ availability.available = !!selectedDisks.length;
+ availability.hidden = false;
+
+ if (diskTotalSpace < requiredSize) {
+ availability.available = false;
+ availability.reason = _("Not enough space on selected disks.");
+ availability.hint = cockpit.format(_(
+ "The installation needs $1 of disk space; " +
+ "however, the capacity of the selected disks is only $0."
+ ), cockpit.format_bytes(diskTotalSpace), cockpit.format_bytes(requiredSize));
+ }
+
+ return availability;
+};
+
+export const scenarioEraseAll = {
+ buttonLabel: _("Erase data and install"),
+ buttonVariant: "danger",
+ check: checkEraseAll,
+ default: true,
+ detail: helpEraseAll,
+ id: "erase-all",
+ // CLEAR_PARTITIONS_ALL = 1
+ initializationMode: 1,
+ label: _("Use entire disk"),
+};
diff --git a/src/components/storage/scenarios/MountPointMapping.jsx b/src/components/storage/scenarios/MountPointMapping.jsx
new file mode 100644
index 0000000000..c128550376
--- /dev/null
+++ b/src/components/storage/scenarios/MountPointMapping.jsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import { AvailabilityState } from "./helpers.js";
+
+import { helpMountPointMapping } from "../HelpAutopartOptions.jsx";
+
+const _ = cockpit.gettext;
+
+const checkMountPointMapping = ({ mountPointConstraints, selectedDisks, usablePartitions }) => {
+ const availability = new AvailabilityState();
+
+ availability.hidden = false;
+ availability.available = !!selectedDisks.length;
+
+ const missingNMParts = getMissingNonmountablePartitions(usablePartitions, mountPointConstraints);
+ const hasFilesystems = usablePartitions
+ .filter(device => device.formatData.mountable.v || device.formatData.type.v === "luks").length > 0;
+
+ if (!hasFilesystems) {
+ // No usable devices on the selected disks: hide the scenario to reduce UI clutter
+ availability.hidden = true;
+ } else if (missingNMParts.length) {
+ availability.available = false;
+ availability.reason = cockpit.format(_("Some required partitions are missing: $0"), missingNMParts.join(", "));
+ }
+ return availability;
+};
+
+const getMissingNonmountablePartitions = (usablePartitions, mountPointConstraints) => {
+ const existingNonmountablePartitions = usablePartitions
+ .filter(device => !device.formatData.mountable.v)
+ .map(device => device.formatData.type.v);
+
+ const missingNonmountablePartitions = mountPointConstraints.filter(constraint =>
+ constraint.required.v &&
+ !constraint["mount-point"].v &&
+ !existingNonmountablePartitions.includes(constraint["required-filesystem-type"].v))
+ .map(constraint => constraint.description);
+
+ return missingNonmountablePartitions;
+};
+
+export const scenarioMountPointMapping = {
+ buttonLabel: _("Apply mount point assignment and install"),
+ buttonVariant: "danger",
+ check: checkMountPointMapping,
+ default: false,
+ detail: helpMountPointMapping,
+ id: "mount-point-mapping",
+ // CLEAR_PARTITIONS_NONE = 0
+ initializationMode: 0,
+ label: _("Mount point assignment"),
+};
diff --git a/src/components/storage/scenarios/ReinstallFedora.jsx b/src/components/storage/scenarios/ReinstallFedora.jsx
new file mode 100644
index 0000000000..ad05cf88b4
--- /dev/null
+++ b/src/components/storage/scenarios/ReinstallFedora.jsx
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import { getAutopartReuseDBusRequest } from "../../../apis/storage_partitioning.js";
+
+import { debug } from "../../../helpers/log.js";
+import { bootloaderTypes, getDeviceAncestors } from "../../../helpers/storage.js";
+import { AvailabilityState } from "./helpers.js";
+
+import { helpHomeReuse } from "../HelpAutopartOptions.jsx";
+
+const _ = cockpit.gettext;
+
+const checkHomeReuse = ({ autopartScheme, devices, originalExistingSystems, selectedDisks }) => {
+ const availability = new AvailabilityState();
+ let reusedOS = null;
+
+ availability.hidden = false;
+ availability.available = !!selectedDisks.length;
+
+ const isCompleteOSOnDisks = (osData, disks) => {
+ const osDisks = osData.devices.v.map(deviceId => getDeviceAncestors(devices, deviceId))
+ .reduce((disks, ancestors) => disks.concat(ancestors))
+ .filter(dev => devices[dev].type.v === "disk")
+ .reduce((uniqueDisks, disk) => uniqueDisks.includes(disk) ? uniqueDisks : [...uniqueDisks, disk], []);
+ const missingDisks = osDisks.filter(disk => !disks.includes(disk));
+ return missingDisks.length === 0;
+ };
+
+ const getUnknownMountPoints = (scheme, existingOS) => {
+ const reuseRequest = getAutopartReuseDBusRequest(scheme);
+ const isBootloader = (device) => bootloaderTypes.includes(devices[device].formatData.type.v);
+ const existingMountPoints = Object.entries(existingOS["mount-points"].v)
+ .map(([mountPoint, device]) => isBootloader(device) ? "bootloader" : mountPoint);
+
+ const managedMountPoints = reuseRequest["reformatted-mount-points"].v
+ .concat(reuseRequest["reused-mount-points"].v, reuseRequest["removed-mount-points"].v);
+
+ const unknownMountPoints = existingMountPoints.filter(i => !managedMountPoints.includes(i));
+ return unknownMountPoints;
+ };
+
+ // Check that exactly one Linux OS is present and it is Fedora Linux
+ // (Stronger check for mountpoints uniqueness is in the backend
+ const linuxSystems = originalExistingSystems.filter(osdata => osdata["os-name"].v.includes("Linux"))
+ .filter(osdata => isCompleteOSOnDisks(osdata, selectedDisks));
+ if (linuxSystems.length === 0) {
+ availability.available = false;
+ availability.hidden = true;
+ debug("home reuse: No existing Linux system found.");
+ } else if (linuxSystems.length > 1) {
+ availability.available = false;
+ availability.hidden = true;
+ debug("home reuse: Multiple existing Linux systems found.");
+ } else {
+ reusedOS = linuxSystems[0];
+ if (!linuxSystems.some(osdata => osdata["os-name"].v.includes("Fedora"))) {
+ availability.available = false;
+ availability.hidden = true;
+ debug("home reuse: No existing Fedora Linux system found.");
+ }
+ }
+
+ debug(`home reuse: Default scheme is ${autopartScheme}.`);
+ if (reusedOS) {
+ // Check that required autopartitioning scheme matches reused OS.
+ // Check just "/home". To be more generic we could check all reused devices (as the backend).
+ const homeDevice = reusedOS["mount-points"].v["/home"];
+ const homeDeviceType = devices[homeDevice]?.type.v;
+ const requiredSchemeTypes = {
+ BTRFS: "btrfs subvolume",
+ LVM: "lvmlv",
+ LVM_THINP: "lvmthinlv",
+ PLAIN: "partition",
+ };
+ if (homeDeviceType !== requiredSchemeTypes[autopartScheme]) {
+ availability.available = false;
+ availability.hidden = true;
+ debug(`home reuse: No reusable existing Linux system found, reused devices must have ${requiredSchemeTypes[autopartScheme]} type`);
+ }
+ }
+
+ if (reusedOS) {
+ // Check that existing system does not have mountpoints unexpected
+ // by the required autopartitioning scheme
+ const unknownMountPoints = getUnknownMountPoints(autopartScheme, reusedOS);
+ if (unknownMountPoints.length > 0) {
+ availability.available = false;
+ availability.hidden = true;
+ console.info(`home reuse: Unknown existing mountpoints found ${unknownMountPoints}`);
+ }
+ }
+
+ // TODO checks:
+ // - luks - partitions are unlocked - enforce? allow opt-out?
+ // - size ?
+ // - Windows system along (forbidden for now?)
+
+ return availability;
+};
+
+export const scenarioReinstallFedora = {
+ buttonLabel: _("Reinstall Fedora"),
+ buttonVariant: "danger",
+ check: checkHomeReuse,
+ default: false,
+ detail: helpHomeReuse,
+ id: "home-reuse",
+ // CLEAR_PARTITIONS_NONE = 0
+ initializationMode: 0,
+ label: _("Reinstall Fedora"),
+};
diff --git a/src/components/storage/scenarios/UseConfiguredStorage.jsx b/src/components/storage/scenarios/UseConfiguredStorage.jsx
new file mode 100644
index 0000000000..1c7169a270
--- /dev/null
+++ b/src/components/storage/scenarios/UseConfiguredStorage.jsx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import React from "react";
+
+import { AvailabilityState } from "./helpers.js";
+
+import { StorageReview } from "../../review/StorageReview.jsx";
+import { helpConfiguredStorage } from "../HelpAutopartOptions.jsx";
+
+const _ = cockpit.gettext;
+
+export const checkConfiguredStorage = ({
+ devices,
+ mountPointConstraints,
+ newMountPoints,
+ partitioning,
+ storageScenarioId,
+}) => {
+ const availability = new AvailabilityState();
+
+ const currentPartitioningMatches = storageScenarioId === "use-configured-storage";
+ availability.hidden = partitioning === undefined || !currentPartitioningMatches;
+
+ availability.available = (
+ newMountPoints === undefined ||
+ (
+ mountPointConstraints
+ ?.filter(m => m.required.v)
+ .every(m => {
+ const allDirs = [];
+ const getNestedDirs = (object) => {
+ if (!object) {
+ return;
+ }
+ const { content, dir, subvolumes } = object;
+
+ if (dir) {
+ allDirs.push(dir);
+ }
+ if (content) {
+ getNestedDirs(content);
+ }
+ if (subvolumes) {
+ Object.keys(subvolumes).forEach(sv => getNestedDirs(subvolumes[sv]));
+ }
+ };
+
+ if (m["mount-point"].v) {
+ Object.keys(newMountPoints).forEach(key => getNestedDirs(newMountPoints[key]));
+
+ return allDirs.includes(m["mount-point"].v);
+ }
+
+ if (m["required-filesystem-type"].v === "biosboot") {
+ const biosboot = Object.keys(devices).find(d => devices[d].formatData.type.v === "biosboot");
+
+ return biosboot !== undefined;
+ }
+
+ return false;
+ })
+ )
+ );
+
+ availability.review = ;
+
+ return availability;
+};
+
+export const scenarioConfiguredStorage = {
+ buttonLabel: _("Install"),
+ buttonVariant: "danger",
+ check: checkConfiguredStorage,
+ default: false,
+ detail: helpConfiguredStorage,
+ id: "use-configured-storage",
+ // CLEAR_PARTITIONS_NONE = 0
+ initializationMode: 0,
+ label: _("Use configured storage"),
+};
diff --git a/src/components/storage/scenarios/UseFreeSpace.jsx b/src/components/storage/scenarios/UseFreeSpace.jsx
new file mode 100644
index 0000000000..1f21c90706
--- /dev/null
+++ b/src/components/storage/scenarios/UseFreeSpace.jsx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This program; If not, see .
+ */
+
+import cockpit from "cockpit";
+
+import React, { useContext, useEffect } from "react";
+import { Checkbox } from "@patternfly/react-core";
+
+import { AvailabilityState } from "./helpers.js";
+
+import { DialogsContext } from "../../../contexts/Common.jsx";
+
+import { helpUseFreeSpace } from "../HelpAutopartOptions.jsx";
+
+const _ = cockpit.gettext;
+
+export const checkUseFreeSpace = ({ diskFreeSpace, diskTotalSpace, requiredSize, selectedDisks }) => {
+ const availability = new AvailabilityState();
+
+ availability.hidden = false;
+ availability.available = !!selectedDisks.length;
+
+ if (diskFreeSpace > 0 && diskTotalSpace > 0) {
+ availability.hidden = diskFreeSpace === diskTotalSpace;
+ }
+ if (diskFreeSpace < requiredSize) {
+ availability.enforceAction = true;
+ availability.reason = _("Not enough free space on the selected disks.");
+ availability.hint = cockpit.format(
+ _("To use this option, resize or remove existing partitions to free up at least $0."),
+ cockpit.format_bytes(requiredSize)
+ );
+ }
+ return availability;
+};
+
+const ReclaimSpace = ({ availability }) => {
+ const { isReclaimSpaceCheckboxChecked, setIsReclaimSpaceCheckboxChecked } = useContext(DialogsContext);
+
+ useEffect(() => {
+ setIsReclaimSpaceCheckboxChecked(availability.enforceAction);
+ }, [availability.enforceAction, setIsReclaimSpaceCheckboxChecked]);
+
+ return (
+ setIsReclaimSpaceCheckboxChecked(value)}
+ />
+ );
+};
+
+export const scenarioUseFreeSpace = {
+ action: ReclaimSpace,
+ buttonLabel: _("Install"),
+ buttonVariant: "primary",
+ canReclaimSpace: true,
+ check: checkUseFreeSpace,
+ default: false,
+ detail: helpUseFreeSpace,
+ id: "use-free-space",
+ // CLEAR_PARTITIONS_NONE = 0
+ initializationMode: 0,
+ label: _("Share disk with other operating system"),
+};
diff --git a/src/components/storage/scenarios/helpers.js b/src/components/storage/scenarios/helpers.js
new file mode 100644
index 0000000000..fab4381904
--- /dev/null
+++ b/src/components/storage/scenarios/helpers.js
@@ -0,0 +1,24 @@
+/*
+* Copyright (C) 2024 Red Hat, Inc.
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2.1 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with This program; If not, see .
+*/
+
+export function AvailabilityState (available = false, hidden = true, reason = null, hint = null, enforceAction = false) {
+ this.available = available;
+ this.enforceAction = enforceAction;
+ this.hidden = hidden;
+ this.reason = reason;
+ this.hint = hint;
+}
diff --git a/src/components/storage/scenarios/index.js b/src/components/storage/scenarios/index.js
new file mode 100644
index 0000000000..442ef506be
--- /dev/null
+++ b/src/components/storage/scenarios/index.js
@@ -0,0 +1,13 @@
+import { scenarioEraseAll } from "./EraseAll.jsx";
+import { scenarioMountPointMapping } from "./MountPointMapping.jsx";
+import { scenarioReinstallFedora } from "./ReinstallFedora.jsx";
+import { scenarioConfiguredStorage } from "./UseConfiguredStorage.jsx";
+import { scenarioUseFreeSpace } from "./UseFreeSpace.jsx";
+
+export const scenarios = [
+ scenarioReinstallFedora,
+ scenarioEraseAll,
+ scenarioUseFreeSpace,
+ scenarioMountPointMapping,
+ scenarioConfiguredStorage,
+];
diff --git a/src/hooks/Storage.jsx b/src/hooks/Storage.jsx
index 873fa9e2cf..6097773cf3 100644
--- a/src/hooks/Storage.jsx
+++ b/src/hooks/Storage.jsx
@@ -43,10 +43,10 @@ import {
import { getDeviceAncestors } from "../helpers/storage.js";
-import { scenarios } from "../components/storage/InstallationScenario.jsx";
-
import { StorageContext } from "../contexts/Common.jsx";
+import { scenarios } from "../components/storage/scenarios/index.js";
+
export const useDiskTotalSpace = ({ devices, selectedDisks }) => {
const [diskTotalSpace, setDiskTotalSpace] = useState();