import React, { useEffect, useMemo, useState } from 'react'; import { Button, Wizard, WizardFooterWrapper, WizardNavItem, WizardStep, useWizardContext, PageSection, } from '@patternfly/react-core'; import { WizardStepType } from '@patternfly/react-core/dist/esm/components/Wizard'; import { useFlag } from '@unleash/proxy-client-react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import DetailsStep from './steps/Details'; import FileSystemStep, { FileSystemContext } from './steps/FileSystem'; import FirstBootStep from './steps/FirstBoot'; import ImageOutputStep from './steps/ImageOutput'; import OscapStep from './steps/Oscap'; import PackagesStep from './steps/Packages'; import RegistrationStep from './steps/Registration'; import RepositoriesStep from './steps/Repositories'; import ReviewStep from './steps/Review'; import ReviewWizardFooter from './steps/Review/Footer/Footer'; import SnapshotStep from './steps/Snapshot'; import Aws from './steps/TargetEnvironment/Aws'; import Azure from './steps/TargetEnvironment/Azure'; import Gcp from './steps/TargetEnvironment/Gcp'; import { useFilesystemValidation, useFirstBootValidation, useDetailsValidation, } from './utilities/useValidation'; import { isAwsAccountIdValid, isAzureTenantGUIDValid, isAzureSubscriptionIdValid, isAzureResourceGroupValid, isGcpEmailValid, } from './validators'; import { RHEL_8, AARCH64 } from '../../constants'; import { useListFeaturesQuery } from '../../store/contentSourcesApi'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import './CreateImageWizard.scss'; import { changeDistribution, changeArchitecture, initializeWizard, selectAwsAccountId, selectAwsShareMethod, selectAwsSourceId, selectAzureResourceGroup, selectAzureShareMethod, selectAzureSource, selectAzureSubscriptionId, selectAzureTenantId, selectGcpEmail, selectGcpShareMethod, selectImageTypes, addImageType, selectSnapshotDate, selectUseLatest, selectRegistrationType, selectActivationKey, } from '../../store/wizardSlice'; import { resolveRelPath } from '../../Utilities/path'; import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader'; type CustomWizardFooterPropType = { disableBack?: boolean; disableNext: boolean; beforeNext?: () => boolean; }; export const CustomWizardFooter = ({ disableBack: disableBack, disableNext: disableNext, beforeNext, }: CustomWizardFooterPropType) => { const { goToNextStep, goToPrevStep, close } = useWizardContext(); return ( ); }; type CreateImageWizardProps = { isEdit?: boolean; }; const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => { const navigate = useNavigate(); const dispatch = useAppDispatch(); const [searchParams] = useSearchParams(); // Remove this and all fallthrough logic when snapshotting is enabled in Prod-stable // =========================TO REMOVE======================= const { data, isSuccess, isFetching, isError } = useListFeaturesQuery(undefined); const snapshotsFlag = useFlag('image-builder.snapshots.enabled'); const snapshottingEnabled = useMemo(() => { if (!snapshotsFlag) return false; // The below checks if other environments permit the snapshot step return !( !isError && !isFetching && isSuccess && data?.snapshots?.accessible === false && data?.snapshots?.enabled === false ); }, [data, isSuccess, isFetching, isError, snapshotsFlag]); // =========================TO REMOVE======================= const isFirstBootEnabled = useFlag('image-builder.firstboot.enabled'); // IMPORTANT: Ensure the wizard starts with a fresh initial state useEffect(() => { dispatch(initializeWizard()); if (searchParams.get('release') === 'rhel8') { dispatch(changeDistribution(RHEL_8)); } if (searchParams.get('arch') === AARCH64) { dispatch(changeArchitecture(AARCH64)); } if (searchParams.get('target') === 'iso') { dispatch(addImageType('image-installer')); } if (searchParams.get('target') === 'qcow2') { dispatch(addImageType('guest-image')); } // This useEffect hook should run *only* on mount and therefore has an empty // dependency array. eslint's exhaustive-deps rule does not support this use. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /* * * Selectors * * */ // Image Output const targetEnvironments = useAppSelector(selectImageTypes); // AWS const awsShareMethod = useAppSelector(selectAwsShareMethod); const awsAccountId = useAppSelector(selectAwsAccountId); const awsSourceId = useAppSelector(selectAwsSourceId); // GCP const gcpShareMethod = useAppSelector(selectGcpShareMethod); const gcpEmail = useAppSelector(selectGcpEmail); // AZURE const azureShareMethod = useAppSelector(selectAzureShareMethod); const azureTenantId = useAppSelector(selectAzureTenantId); const azureSubscriptionId = useAppSelector(selectAzureSubscriptionId); const azureResourceGroup = useAppSelector(selectAzureResourceGroup); const azureSource = useAppSelector(selectAzureSource); // Registration const registrationType = useAppSelector(selectRegistrationType); const activationKey = useAppSelector(selectActivationKey); // Snapshots const snapshotDate = useAppSelector(selectSnapshotDate); const useLatest = useAppSelector(selectUseLatest); const snapshotStepRequiresChoice = !useLatest && !snapshotDate; // Filesystem const [filesystemPristine, setFilesystemPristine] = useState(true); const fileSystemValidation = useFilesystemValidation(); // Firstboot const firstBootValidation = useFirstBootValidation(); // Details const detailsValidation = useDetailsValidation(); let startIndex = 1; // default index if (isEdit) { startIndex = 15; } // Duplicating some of the logic from the Wizard component to allow for custom nav items status // for original code see https://github.com/patternfly/patternfly-react/blob/184c55f8d10e1d94ffd72e09212db56c15387c5e/packages/react-core/src/components/Wizard/WizardNavInternal.tsx#L128 const customStatusNavItem = ( step: WizardStepType, activeStep: WizardStepType, steps: WizardStepType[], goToStepByIndex: (index: number) => void ) => { const isVisitRequired = true; const hasVisitedNextStep = steps.some( (s) => s.index > step.index && s.isVisited ); // Only this code is different from the original const status = (step.isVisited && step.id !== activeStep?.id && step.status) || 'default'; return ( goToStepByIndex(step.index)} status={status} /> ); }; return ( <> navigate(resolveRelPath(''))} isVisitRequired > } > target === 'aws' || target === 'gcp' || target === 'azure' ) } steps={[ } isHidden={!targetEnvironments.includes('aws')} > , } isHidden={!targetEnvironments.includes('gcp')} > , } isHidden={!targetEnvironments.includes('azure')} > , ]} /> } > } > { if (fileSystemValidation.disabledNext) { setFilesystemPristine(false); return false; } return true; }} disableNext={ !filesystemPristine && fileSystemValidation.disabledNext } /> } > } > , } > , } > , ]} /> } > } > } > {/* Intentional prop drilling for simplicity - To be removed */} ); }; export default CreateImageWizard;