* Copyright (C) 2023 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from "cockpit";
import React, { useContext, useEffect, useMemo } from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { FormGroup, FormSection } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { Radio } from "@patternfly/react-core/dist/esm/components/Radio/index.js";
import { Title } from "@patternfly/react-core/dist/esm/components/Title/index.js";
import { setStorageScenarioAction } from "../../../actions/storage-actions.js";
import {
getLockedEncryptedDevices,
} from "../../../helpers/storage.js";
import { PageContext, StorageContext } from "../../../contexts/Common.jsx";
import {
useLaunchStorageEditor,
useOriginalDevices,
} from "../../../hooks/Storage.jsx";
import { scenarios, useScenariosAvailability } from "../scenarios/index.js";
import { USE_CONFIGURED_STORAGE_SCENARIO_IDS } from "../scenarios/use-configured-storage/index.js";
import { StorageReview } from "../StorageReview.jsx";
import { EncryptedDevices } from "./EncryptedDevices.jsx";
import "./InstallationScenario.scss";
const _ = cockpit.gettext;
export const useScenario = () => {
const { storageScenarioId } = useContext(StorageContext);
return useMemo(
() => scenarios.find(s => s.id === storageScenarioId) || {},
[storageScenarioId]
);
};
const InstallationScenarioSelector = ({
dispatch,
idPrefix,
}) => {
const { isFormDisabled } = useContext(PageContext) ?? {};
const { appliedPartitioning, storageScenarioId } = useContext(StorageContext);
const scenarioAvailability = useScenariosAvailability();
const scenarioAvailabilityLoading = scenarioAvailability === undefined;
useEffect(() => {
let selectedScenarioId = "";
if (scenarioAvailabilityLoading) {
return;
}
if (appliedPartitioning && !USE_CONFIGURED_STORAGE_SCENARIO_IDS.includes(storageScenarioId)) {
return;
}
if (storageScenarioId && !scenarioAvailability[storageScenarioId].hidden && scenarioAvailability[storageScenarioId].available) {
selectedScenarioId = storageScenarioId;
} else {
selectedScenarioId = scenarios.find(scenario => (
scenarioAvailability[scenario.id].available &&
!scenarioAvailability[scenario.id].hidden
))?.id;
}
if (selectedScenarioId) {
dispatch(setStorageScenarioAction(selectedScenarioId));
}
}, [appliedPartitioning, dispatch, scenarioAvailability, scenarioAvailabilityLoading, storageScenarioId]);
const onScenarioToggled = (scenarioId) => {
dispatch(setStorageScenarioAction(scenarioId));
};
if (scenarioAvailabilityLoading) {
return null;
}
const scenarioItems = scenarios.filter(scenario => !scenarioAvailability[scenario.id].hidden).map(scenario => (
<Radio
className={idPrefix + "-scenario"}
key={scenario.id}
id={idPrefix + "-scenario-" + scenario.id}
value={scenario.id}
name={idPrefix + "-scenario"}
label={scenario.getLabel()}
isDisabled={!scenarioAvailability[scenario.id].available || isFormDisabled}
isChecked={storageScenarioId === scenario.id}
onChange={() => onScenarioToggled(scenario.id)}
description={scenario.getDetail()}
body={
<>
{scenarioAvailability[scenario.id].reason &&
<span className={idPrefix + "-scenario-disabled-reason"}>
{scenarioAvailability[scenario.id].reason}
</span>}
<span className={idPrefix + "-scenario-disabled-shorthint"}>{scenarioAvailability[scenario.id].hint}</span>
{scenarioAvailability[scenario.id].showReview && <span className={idPrefix + "-scenario-review"}><StorageReview /></span>}
{scenario.action && <scenario.action availability={scenarioAvailability[scenario.id]} />}
</>
} />
));
return scenarioItems;
};
const ModifyStorageButton = ({ setShowStorage }) => {
const launchStorageEditor = useLaunchStorageEditor({ setShowStorage });
return (
<Button
id="modify-storage"
variant="link"
isInline
onClick={launchStorageEditor}
>
{_("If none of the options above apply, use the storage editor to create a custom partitioning before proceeding.")}
</Button>
);
};
export const InstallationScenario = ({
dispatch,
idPrefix,
isFirstScreen,
setIsScenarioValid,
setShowStorage,
}) => {
const headingLevel = isFirstScreen ? "h3" : "h2";
const { diskSelection, storageScenarioId } = useContext(StorageContext);
const devices = useOriginalDevices();
const scenarioAvailability = useScenariosAvailability();
const noScenariosAvailable = !Object.values(scenarioAvailability || {}).some(scenario => (
scenario.available && !scenario.hidden
));
const lockedEncryptedDevices = useMemo(
() => getLockedEncryptedDevices(diskSelection.selectedDisks, devices),
[devices, diskSelection.selectedDisks]
);
const showEncryptedUnlock = lockedEncryptedDevices?.length > 0;
useEffect(() => {
setIsScenarioValid(!!storageScenarioId);
}, [storageScenarioId, setIsScenarioValid]);
if (noScenariosAvailable && !showEncryptedUnlock) {
return null;
}
return (
<FormSection
title={<Title headingLevel={headingLevel}>{_("How would you like to install?")}</Title>}
>
<FormGroup className={idPrefix + "-scenario-group"} isStack data-scenario={storageScenarioId}>
{showEncryptedUnlock &&
(
<EncryptedDevices
dispatch={dispatch}
idPrefix={idPrefix}
lockedEncryptedDevices={lockedEncryptedDevices}
/>
)}
<InstallationScenarioSelector
dispatch={dispatch}
idPrefix={idPrefix}
/>
<ModifyStorageButton setShowStorage={setShowStorage} />
</FormGroup>
</FormSection>
);
};