From 252ca0e8a6e3ff273984f0d3bcbf0743806648a5 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 13 Oct 2023 15:58:15 +0200 Subject: [PATCH 1/5] webui: pass the properties only to the components that use these --- ui/webui/src/components/AnacondaWizard.jsx | 32 +++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index f14d287b855..a3affba4470 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -104,7 +104,16 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo : []), { component: InstallationMethod, - data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection, dispatch }, + data: { + deviceData: storageData.devices, + diskSelection: storageData.diskSelection, + dispatch, + storageScenarioId, + setStorageScenarioId: (scenarioId) => { + window.sessionStorage.setItem("storage-scenario-id", scenarioId); + setStorageScenarioId(scenarioId); + } + }, id: "installation-method", label: _("Installation method"), }, @@ -113,13 +122,22 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo label: _("Disk configuration"), steps: [{ component: MountPointMapping, - data: { deviceData: storageData.devices, diskSelection: storageData.diskSelection, partitioningData: storageData.partitioning, requiredMountPoints, dispatch, reusePartitioning, setReusePartitioning }, + data: { + deviceData: storageData.devices, + diskSelection: storageData.diskSelection, + dispatch, + partitioningData: storageData.partitioning, + requiredMountPoints, + reusePartitioning, + setReusePartitioning, + }, id: "mount-point-mapping", label: _("Manual disk configuration"), isHidden: storageScenarioId !== "mount-point-mapping" }, { component: DiskEncryption, + data: { storageEncryption, setStorageEncryption }, id: "disk-encryption", label: _("Disk encryption"), isHidden: storageScenarioId === "mount-point-mapping" @@ -133,7 +151,8 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo requests: storageData.partitioning ? storageData.partitioning.requests : null, language, localizationData, - osRelease + osRelease, + storageScenarioId, }, id: "installation-review", label: _("Review and install"), @@ -199,15 +218,8 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo stepNotification={stepNotification} isFormDisabled={isFormDisabled} setIsFormDisabled={setIsFormDisabled} - storageEncryption={storageEncryption} - setStorageEncryption={setStorageEncryption} - storageScenarioId={storageScenarioId} isBootIso={isBootIso} osRelease={osRelease} - setStorageScenarioId={(scenarioId) => { - window.sessionStorage.setItem("storage-scenario-id", scenarioId); - setStorageScenarioId(scenarioId); - }} {...s.data} /> ), From 234eb868443a696e253f6872c34917a9999b05d4 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 13 Oct 2023 16:24:46 +0200 Subject: [PATCH 2/5] webui: sort some state variables alphabetically --- ui/webui/src/components/AnacondaWizard.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index a3affba4470..b7191b8399c 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -54,13 +54,13 @@ const _ = cockpit.gettext; const N_ = cockpit.noop; export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, localizationData, onCritFail, onAddErrorNotification, title, conf }) => { + const [isFormDisabled, setIsFormDisabled] = useState(false); const [isFormValid, setIsFormValid] = useState(false); + const [requiredMountPoints, setRequiredMountPoints] = useState(); + const [reusePartitioning, setReusePartitioning] = useState(false); const [stepNotification, setStepNotification] = useState(); - const [isFormDisabled, setIsFormDisabled] = useState(false); const [storageEncryption, setStorageEncryption] = useState(getStorageEncryptionState()); const [storageScenarioId, setStorageScenarioId] = useState(window.sessionStorage.getItem("storage-scenario-id") || getDefaultScenario().id); - const [reusePartitioning, setReusePartitioning] = useState(false); - const [requiredMountPoints, setRequiredMountPoints] = useState(); const availableDevices = useMemo(() => { return Object.keys(storageData.devices); From 32c3fbd82f819f9766247051528d49e26b01a1cf Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 13 Oct 2023 16:27:36 +0200 Subject: [PATCH 3/5] webui: let's be consistent on how we hide steps Reuse the isHidden existing logic instead of doing this with the spread operator and conditional. --- ui/webui/src/components/AnacondaWizard.jsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index b7191b8399c..3c0cb17bffc 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -94,14 +94,13 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo } }, [localizationData]); const stepsOrder = [ - ...(isBootIso - ? [{ - component: InstallationLanguage, - data: { dispatch, languages: localizationData.languages, language: localizationData.language, commonLocales: localizationData.commonLocales }, - id: "installation-language", - label: _("Welcome"), - }] - : []), + { + component: InstallationLanguage, + data: { dispatch, languages: localizationData.languages, language: localizationData.language, commonLocales: localizationData.commonLocales }, + id: "installation-language", + label: _("Welcome"), + isHidden: !isBootIso, + }, { component: InstallationMethod, data: { From a6080aa312c320d11e6a351a9371912e37b2ec73 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 13 Oct 2023 18:16:19 +0200 Subject: [PATCH 4/5] webui: Drop global notifications in favor of the per page notifications For the Web UI we need to keep three exception notifications types: * Critical Error; this opens the 'Report' modal and blocks the installation. It must be used for unexpected Exceptions from the Backend, for example when fetching state data. * Per page notifications; these should be created as a result of submission of some configuration which is not accepted by the backend; the user should be able to react to the error by changing their choices. * Inline errors; these are feedback to the user from cheap sanity checks for the validity of the forms. The dropped 'onAddErrorNotification' method was adding `Toast Alerts` centrally on the app, which does not fall under any of these categories. Replace the two occurances of its usage with the per-page notifications. Additionally keep the functionality of displaying per-page notifications in the AnacondaPage component so that the code can be shared between all pages. --- ui/webui/src/components/AnacondaPage.jsx | 10 ++++- ui/webui/src/components/AnacondaWizard.jsx | 38 +++++++++++-------- ui/webui/src/components/app.jsx | 37 ------------------ .../localization/InstallationLanguage.jsx | 25 +++++------- .../components/review/ReviewConfiguration.jsx | 6 +-- .../src/components/storage/DiskEncryption.jsx | 5 +-- .../components/storage/InstallationMethod.jsx | 5 +-- .../components/storage/MountPointMapping.jsx | 24 ++++-------- 8 files changed, 54 insertions(+), 96 deletions(-) diff --git a/ui/webui/src/components/AnacondaPage.jsx b/ui/webui/src/components/AnacondaPage.jsx index d51cf9ada19..0937e486c57 100644 --- a/ui/webui/src/components/AnacondaPage.jsx +++ b/ui/webui/src/components/AnacondaPage.jsx @@ -14,13 +14,19 @@ * You should have received a copy of the GNU Lesser General Public License * along with This program; If not, see . */ -import { Stack, Title } from "@patternfly/react-core"; +import { Alert, Stack, Title } from "@patternfly/react-core"; import React from "react"; -export const AnacondaPage = ({ title, children }) => { +export const AnacondaPage = ({ title, children, step, stepNotification }) => { return ( {title && {title}} + {stepNotification && stepNotification.step === step && + } {children} ); diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index 3c0cb17bffc..d1bc8f18d14 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -35,6 +35,7 @@ import { WizardContextConsumer } from "@patternfly/react-core/deprecated"; +import { AnacondaPage } from "./AnacondaPage.jsx"; import { InstallationMethod } from "./storage/InstallationMethod.jsx"; import { getScenario, getDefaultScenario } from "./storage/InstallationScenario.jsx"; import { MountPointMapping } from "./storage/MountPointMapping.jsx"; @@ -53,7 +54,7 @@ import { const _ = cockpit.gettext; const N_ = cockpit.noop; -export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, localizationData, onCritFail, onAddErrorNotification, title, conf }) => { +export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, localizationData, onCritFail, title, conf }) => { const [isFormDisabled, setIsFormDisabled] = useState(false); const [isFormValid, setIsFormValid] = useState(false); const [requiredMountPoints, setRequiredMountPoints] = useState(); @@ -100,6 +101,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo id: "installation-language", label: _("Welcome"), isHidden: !isBootIso, + title: cockpit.format(_("Welcome to $0"), osRelease.NAME), }, { component: InstallationMethod, @@ -115,6 +117,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo }, id: "installation-method", label: _("Installation method"), + title: !isBootIso ? cockpit.format(_("Welcome. Let's install $0 now."), osRelease.REDHAT_SUPPORT_PRODUCT) : null }, { id: "disk-configuration", @@ -132,14 +135,16 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo }, id: "mount-point-mapping", label: _("Manual disk configuration"), - isHidden: storageScenarioId !== "mount-point-mapping" + isHidden: storageScenarioId !== "mount-point-mapping", + title: _("Manual disk configuration: Mount point mapping") }, { component: DiskEncryption, data: { storageEncryption, setStorageEncryption }, id: "disk-encryption", label: _("Disk encryption"), - isHidden: storageScenarioId === "mount-point-mapping" + isHidden: storageScenarioId === "mount-point-mapping", + title: _("Encrypt the selected devices?") }] }, { @@ -155,6 +160,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo }, id: "installation-review", label: _("Review and install"), + title: _("Review and install") }, { component: InstallationProgress, @@ -209,18 +215,20 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo step = ({ ...step, component: ( - + + setStepNotification({ step: s.id, ...ex })} + stepNotification={stepNotification} + isFormDisabled={isFormDisabled} + setIsFormDisabled={setIsFormDisabled} + isBootIso={isBootIso} + osRelease={osRelease} + {...s.data} + /> + ), }); } else if (s.steps) { diff --git a/ui/webui/src/components/app.jsx b/ui/webui/src/components/app.jsx index 1399a77e94a..6a6edc3f421 100644 --- a/ui/webui/src/components/app.jsx +++ b/ui/webui/src/components/app.jsx @@ -19,7 +19,6 @@ import cockpit from "cockpit"; import React, { useEffect, useState } from "react"; import { - AlertGroup, AlertVariant, AlertActionCloseButton, Alert, Page, PageGroup, } from "@patternfly/react-core"; @@ -51,7 +50,6 @@ export const Application = () => { const [beta, setBeta] = useState(); const [conf, setConf] = useState(); const [language, setLanguage] = useState(); - const [notifications, setNotifications] = useState({}); const [osRelease, setOsRelease] = useState(""); const [state, dispatch] = useReducerWithThunk(reducer, initialState); const [storeInitilized, setStoreInitialized] = useState(false); @@ -103,17 +101,6 @@ export const Application = () => { readOsRelease().then(osRelease => setOsRelease(osRelease)); }, [dispatch]); - const onAddNotification = (notificationProps) => { - setNotifications({ - ...notifications, - [notifications.length]: { index: notifications.length, ...notificationProps } - }); - }; - - const onAddErrorNotification = ex => { - onAddNotification({ title: ex.name, message: ex.message, variant: "danger" }); - }; - // Postpone rendering anything until we read the dbus address and the default configuration if (!criticalError && (!address || !conf || beta === undefined || !osRelease || !storeInitilized)) { debug("Loading initial data..."); @@ -136,29 +123,6 @@ export const Application = () => { - {Object.keys(notifications).length > 0 && - - {Object.keys(notifications).map(idx => { - const notification = notifications[idx]; - const newNotifications = { ...notifications }; - delete newNotifications[notification.index]; - - return ( - setNotifications(newNotifications)} - /> - } - key={notification.index}> - {notification.message} - - ); - })} - } { setLocale({ locale: getLocaleId(localeItem) })) - .catch(this.props.onAddErrorNotification); + .catch(ex => { + console.info({ ex }); + this.props.setStepNotification(ex); + }); this.setState({ lang: item }); this.updateNativeName(localeItem); fetch("po.js").then(response => response.text()) @@ -284,21 +285,15 @@ class LanguageSelector extends React.Component { } LanguageSelector.contextType = AddressContext; -export const InstallationLanguage = ({ idPrefix, languages, language, commonLocales, setIsFormValid, onAddErrorNotification }) => { - const [nativeName, setNativeName] = React.useState(false); +export const InstallationLanguage = ({ idPrefix, languages, language, commonLocales, setIsFormValid, setStepNotification }) => { + const [nativeName, setNativeName] = useState(false); const { setLanguage } = React.useContext(LanguageContext); - const [distributionName, setDistributionName] = useState(""); - - useEffect(() => { - readOsRelease().then(osRelease => setDistributionName(osRelease.NAME)); - }, []); - useEffect(() => { setIsFormValid(language !== ""); }, [language, setIsFormValid]); return ( - + <> @@ -324,12 +319,12 @@ export const InstallationLanguage = ({ idPrefix, languages, language, commonLoca commonLocales={commonLocales} language={language} setIsFormValid={setIsFormValid} - onAddErrorNotification={onAddErrorNotification} + setStepNotification={setStepNotification} setNativeName={setNativeName} reRenderApp={setLanguage} /> </FormGroup> </Form> - </AnacondaPage> + </> ); }; diff --git a/ui/webui/src/components/review/ReviewConfiguration.jsx b/ui/webui/src/components/review/ReviewConfiguration.jsx index 492c8f84627..2f2612fd9fb 100644 --- a/ui/webui/src/components/review/ReviewConfiguration.jsx +++ b/ui/webui/src/components/review/ReviewConfiguration.jsx @@ -33,8 +33,6 @@ import { } from "../../apis/storage.js"; import { checkDeviceInSubTree } from "../../helpers/storage.js"; -import { AnacondaPage } from "../AnacondaPage.jsx"; - import { getScenario } from "../storage/InstallationScenario.jsx"; import "./ReviewConfiguration.scss"; @@ -116,7 +114,7 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local }, [setIsFormValid]); return ( - <AnacondaPage title={_("Review and install")}> + <> <ReviewDescriptionList> <DescriptionListGroup> <DescriptionListTerm> @@ -172,7 +170,7 @@ export const ReviewConfiguration = ({ deviceData, diskSelection, language, local </DescriptionListDescription> </DescriptionListGroup> </ReviewDescriptionList> - </AnacondaPage> + </> ); }; diff --git a/ui/webui/src/components/storage/DiskEncryption.jsx b/ui/webui/src/components/storage/DiskEncryption.jsx index f5c1f0a3374..30589327dbb 100644 --- a/ui/webui/src/components/storage/DiskEncryption.jsx +++ b/ui/webui/src/components/storage/DiskEncryption.jsx @@ -45,7 +45,6 @@ import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclam import ExclamationTriangleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon"; import CheckCircleIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon"; -import { AnacondaPage } from "../AnacondaPage.jsx"; import "./DiskEncryption.scss"; import { getPasswordPolicies } from "../../apis/runtime.js"; @@ -363,7 +362,7 @@ export const DiskEncryption = ({ } return ( - <AnacondaPage title={_("Encrypt the selected devices?")}> + <> <TextContent> <Text component={TextVariants.p}> {_("Encryption helps secure your data, to prevent others from accessing it.")} @@ -375,6 +374,6 @@ export const DiskEncryption = ({ <Form> {encryptedDevicesCheckbox(isEncrypted ? passphraseForm : null)} </Form> - </AnacondaPage> + </> ); }; diff --git a/ui/webui/src/components/storage/InstallationMethod.jsx b/ui/webui/src/components/storage/InstallationMethod.jsx index 150431d59b1..686866d5b37 100644 --- a/ui/webui/src/components/storage/InstallationMethod.jsx +++ b/ui/webui/src/components/storage/InstallationMethod.jsx @@ -50,7 +50,6 @@ import { } from "../../apis/storage.js"; import { getDevicesAction, getDiskSelectionAction } from "../../actions/storage-actions.js"; -import { AnacondaPage } from "../AnacondaPage.jsx"; import { debug } from "../../helpers/log.js"; import { checkIfArraysAreEqual } from "../../helpers/utils.js"; @@ -455,7 +454,7 @@ export const InstallationMethod = ({ storageScenarioId, }) => { return ( - <AnacondaPage title={!isBootIso ? cockpit.format(_("Welcome. Let's install $0 now."), osRelease.REDHAT_SUPPORT_PRODUCT) : null}> + <> <Form className={idPrefix + "-selector"} id={idPrefix + "-selector-form"} @@ -491,6 +490,6 @@ export const InstallationMethod = ({ storageScenarioId={storageScenarioId} /> </Form> - </AnacondaPage> + </> ); }; diff --git a/ui/webui/src/components/storage/MountPointMapping.jsx b/ui/webui/src/components/storage/MountPointMapping.jsx index 60a24c87788..5b913af6c65 100644 --- a/ui/webui/src/components/storage/MountPointMapping.jsx +++ b/ui/webui/src/components/storage/MountPointMapping.jsx @@ -19,7 +19,6 @@ import cockpit from "cockpit"; import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; import { - Alert, Button, Switch, Flex, @@ -40,7 +39,6 @@ import { TrashIcon } from "@patternfly/react-icons"; import { ListingTable } from "cockpit-components-table.jsx"; import { EmptyStatePanel } from "cockpit-components-empty-state.jsx"; -import { AnacondaPage } from "../AnacondaPage.jsx"; import { EncryptedDevices } from "./EncryptedDevices.jsx"; import { @@ -479,7 +477,7 @@ const RequestsTable = ({ deviceData, idPrefix, lockedLUKSDevices, - onAddErrorNotification, + setStepNotification, partitioningDataPath, requests, requiredMountPoints, @@ -533,12 +531,12 @@ const RequestsTable = ({ newRequests, partitioning: partitioningDataPath }).catch(ex => { - onAddErrorNotification(ex); + setStepNotification(ex); setIsFormValid(false); }); setUnappliedRequests(newRequests); - }, [setIsFormValid, deviceData, unappliedRequests, requests, partitioningDataPath, onAddErrorNotification]); + }, [setIsFormValid, deviceData, unappliedRequests, requests, partitioningDataPath, setStepNotification]); return ( <> @@ -601,13 +599,12 @@ export const MountPointMapping = ({ diskSelection, dispatch, idPrefix, - onAddErrorNotification, partitioningData, requiredMountPoints, reusePartitioning, setIsFormValid, setReusePartitioning, - stepNotification, + setStepNotification, }) => { const [usedPartitioning, setUsedPartitioning] = useState(partitioningData?.path); const [skipUnlock, setSkipUnlock] = useState(false); @@ -637,14 +634,7 @@ export const MountPointMapping = ({ const showLuksUnlock = lockedLUKSDevices?.length > 0 && !skipUnlock; return ( - <AnacondaPage title={_("Manual disk configuration: Mount point mapping")}> - {stepNotification && stepNotification.step === "mount-point-mapping" && - <Alert - isInline - title={stepNotification.message} - variant="danger" - />} - + <> {showLuksUnlock && ( <EncryptedDevices @@ -665,13 +655,13 @@ export const MountPointMapping = ({ deviceData={deviceData} idPrefix={idPrefix + "-table"} lockedLUKSDevices={lockedLUKSDevices} - onAddErrorNotification={onAddErrorNotification} + setStepNotification={setStepNotification} partitioningDataPath={partitioningData?.path} requests={partitioningData?.requests} requiredMountPoints={requiredMountPoints} setIsFormValid={setIsFormValid} /> ))} - </AnacondaPage> + </> ); }; From ffee48df6aed5b2d9bf5ae71aef88d5e3b40f719 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou <kkoukiou@redhat.com> Date: Fri, 13 Oct 2023 19:01:10 +0200 Subject: [PATCH 5/5] webui: move per-page title, id, label and hidden state into the components In order to cleanup a bit the AnacondaWizard from keeping the per-step knowledge. --- ui/webui/src/components/AnacondaWizard.jsx | 34 ++++++------------- .../localization/InstallationLanguage.jsx | 9 +++++ .../components/review/ReviewConfiguration.jsx | 8 +++++ .../src/components/storage/DiskEncryption.jsx | 9 +++++ .../components/storage/InstallationMethod.jsx | 8 +++++ .../components/storage/MountPointMapping.jsx | 9 +++++ 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/ui/webui/src/components/AnacondaWizard.jsx b/ui/webui/src/components/AnacondaWizard.jsx index d1bc8f18d14..c1537219113 100644 --- a/ui/webui/src/components/AnacondaWizard.jsx +++ b/ui/webui/src/components/AnacondaWizard.jsx @@ -36,13 +36,13 @@ import { } from "@patternfly/react-core/deprecated"; import { AnacondaPage } from "./AnacondaPage.jsx"; -import { InstallationMethod } from "./storage/InstallationMethod.jsx"; +import { InstallationMethod, getPageProps as getInstallationMethodProps } from "./storage/InstallationMethod.jsx"; import { getScenario, getDefaultScenario } from "./storage/InstallationScenario.jsx"; -import { MountPointMapping } from "./storage/MountPointMapping.jsx"; -import { DiskEncryption, getStorageEncryptionState } from "./storage/DiskEncryption.jsx"; -import { InstallationLanguage } from "./localization/InstallationLanguage.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"; import { InstallationProgress } from "./installation/InstallationProgress.jsx"; -import { ReviewConfiguration, ReviewConfigurationConfirmModal } from "./review/ReviewConfiguration.jsx"; +import { ReviewConfiguration, ReviewConfigurationConfirmModal, getPageProps as getReviewConfigurationProps } from "./review/ReviewConfiguration.jsx"; import { exitGui } from "../helpers/exit.js"; import { usePageLocation } from "hooks"; import { @@ -98,10 +98,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo { component: InstallationLanguage, data: { dispatch, languages: localizationData.languages, language: localizationData.language, commonLocales: localizationData.commonLocales }, - id: "installation-language", - label: _("Welcome"), - isHidden: !isBootIso, - title: cockpit.format(_("Welcome to $0"), osRelease.NAME), + ...getInstallationLanguageProps({ isBootIso, osRelease }) }, { component: InstallationMethod, @@ -115,9 +112,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo setStorageScenarioId(scenarioId); } }, - id: "installation-method", - label: _("Installation method"), - title: !isBootIso ? cockpit.format(_("Welcome. Let's install $0 now."), osRelease.REDHAT_SUPPORT_PRODUCT) : null + ...getInstallationMethodProps({ isBootIso, osRelease }) }, { id: "disk-configuration", @@ -133,18 +128,11 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo reusePartitioning, setReusePartitioning, }, - id: "mount-point-mapping", - label: _("Manual disk configuration"), - isHidden: storageScenarioId !== "mount-point-mapping", - title: _("Manual disk configuration: Mount point mapping") - + ...getMountPointMappingProps({ storageScenarioId }) }, { component: DiskEncryption, data: { storageEncryption, setStorageEncryption }, - id: "disk-encryption", - label: _("Disk encryption"), - isHidden: storageScenarioId === "mount-point-mapping", - title: _("Encrypt the selected devices?") + ...getDiskEncryptionProps({ storageScenarioId }) }] }, { @@ -158,9 +146,7 @@ export const AnacondaWizard = ({ dispatch, isBootIso, osRelease, storageData, lo osRelease, storageScenarioId, }, - id: "installation-review", - label: _("Review and install"), - title: _("Review and install") + ...getReviewConfigurationProps() }, { component: InstallationProgress, diff --git a/ui/webui/src/components/localization/InstallationLanguage.jsx b/ui/webui/src/components/localization/InstallationLanguage.jsx index eb072b4ceb6..8d34c4d584c 100644 --- a/ui/webui/src/components/localization/InstallationLanguage.jsx +++ b/ui/webui/src/components/localization/InstallationLanguage.jsx @@ -328,3 +328,12 @@ export const InstallationLanguage = ({ idPrefix, languages, language, commonLoca </> ); }; + +export const getPageProps = ({ isBootIso, osRelease }) => { + return ({ + id: "installation-language", + label: _("Welcome"), + isHidden: !isBootIso, + title: cockpit.format(_("Welcome to $0"), osRelease.NAME), + }); +}; diff --git a/ui/webui/src/components/review/ReviewConfiguration.jsx b/ui/webui/src/components/review/ReviewConfiguration.jsx index 2f2612fd9fb..dc5bd26e8be 100644 --- a/ui/webui/src/components/review/ReviewConfiguration.jsx +++ b/ui/webui/src/components/review/ReviewConfiguration.jsx @@ -207,3 +207,11 @@ export const ReviewConfigurationConfirmModal = ({ idPrefix, onNext, setNextWaits </Modal> ); }; + +export const getPageProps = () => { + return ({ + id: "installation-review", + label: _("Review and install"), + title: _("Review and install") + }); +}; diff --git a/ui/webui/src/components/storage/DiskEncryption.jsx b/ui/webui/src/components/storage/DiskEncryption.jsx index 30589327dbb..72e6e3bb879 100644 --- a/ui/webui/src/components/storage/DiskEncryption.jsx +++ b/ui/webui/src/components/storage/DiskEncryption.jsx @@ -377,3 +377,12 @@ export const DiskEncryption = ({ </> ); }; + +export const getPageProps = ({ storageScenarioId }) => { + return ({ + id: "disk-encryption", + label: _("Disk encryption"), + isHidden: storageScenarioId === "mount-point-mapping", + title: _("Encrypt the selected devices?") + }); +}; diff --git a/ui/webui/src/components/storage/InstallationMethod.jsx b/ui/webui/src/components/storage/InstallationMethod.jsx index 686866d5b37..757c5f94543 100644 --- a/ui/webui/src/components/storage/InstallationMethod.jsx +++ b/ui/webui/src/components/storage/InstallationMethod.jsx @@ -493,3 +493,11 @@ export const InstallationMethod = ({ </> ); }; + +export const getPageProps = ({ isBootIso, osRelease }) => { + return ({ + id: "installation-method", + label: _("Installation method"), + title: !isBootIso ? cockpit.format(_("Welcome. Let's install $0 now."), osRelease.REDHAT_SUPPORT_PRODUCT) : null + }); +}; diff --git a/ui/webui/src/components/storage/MountPointMapping.jsx b/ui/webui/src/components/storage/MountPointMapping.jsx index 5b913af6c65..b422bf3f3db 100644 --- a/ui/webui/src/components/storage/MountPointMapping.jsx +++ b/ui/webui/src/components/storage/MountPointMapping.jsx @@ -665,3 +665,12 @@ export const MountPointMapping = ({ </> ); }; + +export const getPageProps = ({ storageScenarioId }) => { + return ({ + id: "mount-point-mapping", + label: _("Manual disk configuration"), + isHidden: storageScenarioId !== "mount-point-mapping", + title: _("Manual disk configuration: Mount point mapping") + }); +};