Skip to content

Commit

Permalink
Re-implement 'Modify storage' target to open cockpit storage in-page
Browse files Browse the repository at this point in the history
This commit introduces a new user workflow for storage configuration in the installer.
The Cockpit storage editor, is replacing the blivet-gui and is now presented inside an iframe.
The communication between Anaconda and Cockpit is doen via localStorage.
The API which is still developed is defined in
[Cockpit Anaconda Documentation](https://github.com/cockpit-project/cockpit/blob/main/doc/anaconda.md).

The updated user workflow is as follows:
- Access the storage editor via the 'Modify storage' button.
- Requirements and recommendations for manual partitioning are detailed in the right sidebar.
- Upon completing storage manipulations, users can return to the installer wizard using the 'Return to installation' button.
- Upon clicking the button, the system checks for the possibility of two scenarios:
  1) Utilize manually configured partitioning.
  2) Utilize free space for automatic partitioning.
- The dialog prioritizes the previous order, creating the manual partitioning object for scenario 1,
and returning to the main screen. The selected scenario aligns with the user's presumed intention based on their storage edits.
  • Loading branch information
KKoukiou committed Feb 2, 2024
1 parent b191453 commit 6f82b02
Show file tree
Hide file tree
Showing 17 changed files with 1,045 additions and 170 deletions.
9 changes: 9 additions & 0 deletions packaging/anaconda-webui.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 27 additions & 2 deletions src/components/AnacondaWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 })
},
Expand Down Expand Up @@ -267,6 +278,20 @@ export const AnacondaWizard = ({ dispatch, storageData, localizationData, runtim
return currentStepId ? step.props.id === currentStepId : !step.props.isHidden;
}) + 1;

if (showStorage) {
return (
<CockpitStorageIntegration
deviceData={storageData.devices}
dispatch={dispatch}
onCritFail={onCritFail}
scenarioPartitioningMapping={scenarioPartitioningMapping}
selectedDisks={selectedDisks}
setShowStorage={setShowStorage}
setStorageScenarioId={setStorageScenarioId}
/>
);
}

return (
<PageSection type={PageSectionTypes.wizard} variant={PageSectionVariants.light}>
<Wizard
Expand Down
1 change: 1 addition & 0 deletions src/components/Common.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ConfContext = createContext();
export const LanguageContext = createContext("");
export const SystemTypeContext = createContext(null);
export const OsReleaseContext = createContext(null);
export const TargetSystemRootContext = createContext(null);

export const FormGroupHelpPopover = ({ helpContent }) => {
return (
Expand Down
34 changes: 20 additions & 14 deletions src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -155,27 +156,32 @@ export const Application = () => {
reportLinkURL={bzReportURL} />}
{!jsError &&
<>
{!showStorage &&
<PageGroup stickyOnBreakpoint={{ default: "top" }}>
<AnacondaHeader
title={title}
reportLinkURL={bzReportURL}
isConnected={state.network.connected}
onCritFail={onCritFail}
/>
</PageGroup>
</PageGroup>}
<AddressContext.Provider value={address}>
<WithDialogs>
<AnacondaWizard
onCritFail={onCritFail}
title={title}
storageData={state.storage}
localizationData={state.localization}
runtimeData={state.runtime}
dispatch={dispatch}
conf={conf}
osRelease={osRelease}
/>
</WithDialogs>
<TargetSystemRootContext.Provider value={conf["Installation Target"].system_root}>
<WithDialogs>
<AnacondaWizard
onCritFail={onCritFail}
title={title}
storageData={state.storage}
localizationData={state.localization}
runtimeData={state.runtime}
dispatch={dispatch}
conf={conf}
osRelease={osRelease}
setShowStorage={setShowStorage}
showStorage={showStorage}
/>
</WithDialogs>
</TargetSystemRootContext.Provider>
</AddressContext.Provider>
</>}
</Page>
Expand Down
9 changes: 8 additions & 1 deletion src/components/review/ReviewConfiguration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,14 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local
<DescriptionListDescription id={idPrefix + "-target-storage"}>
<Stack hasGutter>
{diskSelection.selectedDisks.map(disk => {
return <DeviceRow key={disk} deviceData={deviceData} disk={disk} requests={storageScenarioId === "mount-point-mapping" ? requests : null} />;
return (
<DeviceRow
key={disk}
deviceData={deviceData}
disk={disk}
requests={["mount-point-mapping", "use-configured-storage"].includes(storageScenarioId) ? requests : null}
/>
);
})}
</Stack>
</DescriptionListDescription>
Expand Down
Loading

0 comments on commit 6f82b02

Please sign in to comment.