diff --git a/packaging/anaconda-webui.spec.in b/packaging/anaconda-webui.spec.in index c479633bf0..99aeae809f 100644 --- a/packaging/anaconda-webui.spec.in +++ b/packaging/anaconda-webui.spec.in @@ -13,9 +13,18 @@ BuildRequires: gettext %global anacondacorever 40.20 %global cockpitver 275 +%global cockpitstorver 309 + +# LVM and BTRFS are only recommended by cockpit-storaged +Requires: cockpit-storaged >= %{cockpitstorver} +Requires: udisks2-lvm +%if 0%{?fedora} +Requires: udisks2-btrfs +%endif Requires: cockpit-bridge >= %{cockpitver} Requires: cockpit-ws >= %{cockpitver} +Requires: cockpit-storage Requires: anaconda-core >= %{anacondacorever} # Firefox dependency needs to be specified there as cockpit web-view does not have a hard dependency on Firefox as # it can often fall back to a diferent browser. This does not work in the limited installer diff --git a/src/components/AnacondaWizard.jsx b/src/components/AnacondaWizard.jsx index 2df892205a..69a3a1e29d 100644 --- a/src/components/AnacondaWizard.jsx +++ b/src/components/AnacondaWizard.jsx @@ -35,6 +35,7 @@ import { import { AnacondaPage } from "./AnacondaPage.jsx"; import { InstallationMethod, getPageProps as getInstallationMethodProps } from "./storage/InstallationMethod.jsx"; import { getDefaultScenario } from "./storage/InstallationScenario.jsx"; +import { CockpitStorageIntegration } from "./storage/CockpitStorageIntegration.jsx"; import { MountPointMapping, getPageProps as getMountPointMappingProps } from "./storage/MountPointMapping.jsx"; import { DiskEncryption, getStorageEncryptionState, getPageProps as getDiskEncryptionProps } from "./storage/DiskEncryption.jsx"; import { InstallationLanguage, getPageProps as getInstallationLanguageProps } from "./localization/InstallationLanguage.jsx"; @@ -51,7 +52,7 @@ import { SystemTypeContext, OsReleaseContext } from "./Common.jsx"; const _ = cockpit.gettext; const N_ = cockpit.noop; -export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtimeData, onCritFail, title, conf }) => { +export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtimeData, onCritFail, showStorage, setShowStorage, title, conf }) => { const [isFormDisabled, setIsFormDisabled] = useState(false); const [isFormValid, setIsFormValid] = useState(false); const [reusePartitioning, setReusePartitioning] = useState(false); @@ -64,6 +65,13 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim const osRelease = useContext(OsReleaseContext); const isBootIso = useContext(SystemTypeContext) === "BOOT_ISO"; const selectedDisks = storageData.diskSelection.selectedDisks; + const [scenarioPartitioningMapping, setScenarioPartitioningMapping] = useState({}); + + useEffect(() => { + if (storageScenarioId && storageData.partitioning.path) { + setScenarioPartitioningMapping({ [storageScenarioId]: storageData.partitioning.path }); + } + }, [storageData.partitioning.path, storageScenarioId]); const availableDevices = useMemo(() => { return Object.keys(storageData.devices); @@ -107,11 +115,14 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim deviceNames: storageData.deviceNames, diskSelection: storageData.diskSelection, dispatch, + partitioning: storageData.partitioning.path, + scenarioPartitioningMapping, storageScenarioId, setStorageScenarioId: (scenarioId) => { window.sessionStorage.setItem("storage-scenario-id", scenarioId); setStorageScenarioId(scenarioId); - } + }, + setShowStorage, }, ...getInstallationMethodProps({ isBootIso, osRelease, isFormValid }) }, @@ -267,6 +278,20 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim return currentStepId ? step.props.id === currentStepId : !step.props.isHidden; }) + 1; + if (showStorage) { + return ( + + ); + } + return ( { return ( diff --git a/src/components/app.jsx b/src/components/app.jsx index 9093655662..cc8e10834e 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -26,7 +26,7 @@ import { import { read_os_release as readOsRelease } from "os-release.js"; import { WithDialogs } from "dialogs.jsx"; -import { AddressContext, LanguageContext, SystemTypeContext, OsReleaseContext } from "./Common.jsx"; +import { AddressContext, LanguageContext, SystemTypeContext, TargetSystemRootContext, OsReleaseContext } from "./Common.jsx"; import { AnacondaHeader } from "./AnacondaHeader.jsx"; import { AnacondaWizard } from "./AnacondaWizard.jsx"; import { CriticalError, errorHandlerWithContext, bugzillaPrefiledReportURL } from "./Error.jsx"; @@ -59,6 +59,7 @@ export const Application = () => { const [storeInitilized, setStoreInitialized] = useState(false); const criticalError = state?.error?.criticalError; const [jsError, setJsEroor] = useState(); + const [showStorage, setShowStorage] = useState(false); const onCritFail = useCallback((contextData) => { return errorHandlerWithContext(contextData, exc => dispatch(setCriticalErrorAction(exc))); @@ -155,6 +156,7 @@ export const Application = () => { reportLinkURL={bzReportURL} />} {!jsError && <> + {!showStorage && { isConnected={state.network.connected} onCritFail={onCritFail} /> - + } - - - + + + + + } diff --git a/src/components/review/ReviewConfiguration.jsx b/src/components/review/ReviewConfiguration.jsx index a7f51b37c0..4f58a65cd1 100644 --- a/src/components/review/ReviewConfiguration.jsx +++ b/src/components/review/ReviewConfiguration.jsx @@ -177,7 +177,14 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local {diskSelection.selectedDisks.map(disk => { - return ; + return ( + + ); })} diff --git a/src/components/storage/CockpitStorageIntegration.jsx b/src/components/storage/CockpitStorageIntegration.jsx new file mode 100644 index 0000000000..659393a811 --- /dev/null +++ b/src/components/storage/CockpitStorageIntegration.jsx @@ -0,0 +1,523 @@ +/* + * 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 + HelperTextItem, + * 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, { useEffect, useMemo, useState } from "react"; + +import { + ActionList, + Alert, + Button, + Card, + CardBody, + Flex, + FlexItem, + HelperText, + HelperTextItem, + List, + ListItem, + Modal, + PageSection, + PageSectionVariants, + Text, + TextContent, + Title, +} from "@patternfly/react-core"; +import { ArrowLeftIcon } from "@patternfly/react-icons"; + +import { EmptyStatePanel } from "cockpit-components-empty-state"; +import { checkConfiguredStorage, checkUseFreeSpace } from "./InstallationScenario.jsx"; +import { useDiskTotalSpace, useDiskFreeSpace, useRequiredSize, useMountPointConstraints } from "./Common.jsx"; + +import { + runStorageTask, + scanDevicesWithTask, +} from "../../apis/storage.js"; +import { + unlockDevice, +} from "../../apis/storage_devicetree.js"; +import { + setBootloaderDrive, +} from "../../apis/storage_bootloader.js"; +import { + setInitializationMode, +} from "../../apis/storage_disk_initialization.js"; +import { + applyStorage, + createPartitioning, + gatherRequests, + resetPartitioning, + setManualPartitioningRequests +} from "../../apis/storage_partitioning.js"; + +import { getDevicesAction } from "../../actions/storage-actions.js"; +import { getDeviceNameByPath } from "../../helpers/storage.js"; + +import "./CockpitStorageIntegration.scss"; + +const _ = cockpit.gettext; +const idPrefix = "cockpit-storage-integration"; + +const ReturnToInstallationButton = ({ isDisabled, onAction }) => ( + +); + +export const CockpitStorageIntegration = ({ + deviceData, + dispatch, + onCritFail, + scenarioAvailability, + scenarioPartitioningMapping, + selectedDisks, + setShowStorage, + setStorageScenarioId, +}) => { + const [showDialog, setShowDialog] = useState(false); + const [needsResetPartitioning, setNeedsResetPartitioning] = useState(true); + + useEffect(() => { + resetPartitioning().then(() => setNeedsResetPartitioning(false), onCritFail); + }, [onCritFail]); + + return ( + <> + + + {_("Configure storage")} + + + +
+ +