diff --git a/src/Components/CreateImageWizardV2/CreateImageWizard.tsx b/src/Components/CreateImageWizardV2/CreateImageWizard.tsx index 839c0c0f..bba7cf54 100644 --- a/src/Components/CreateImageWizardV2/CreateImageWizard.tsx +++ b/src/Components/CreateImageWizardV2/CreateImageWizard.tsx @@ -11,6 +11,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom'; import DetailsStep from './steps/Details'; import FileSystemStep from './steps/FileSystem'; +import { FileSystemStepFooter } from './steps/FileSystem/FileSystemConfiguration'; import ImageOutputStep from './steps/ImageOutput'; import OscapStep from './steps/Oscap'; import PackagesStep from './steps/Packages'; @@ -260,7 +261,7 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => { } + footer={} > diff --git a/src/Components/CreateImageWizardV2/steps/FileSystem/FileSystemConfiguration.tsx b/src/Components/CreateImageWizardV2/steps/FileSystem/FileSystemConfiguration.tsx index b9fac99c..a8874a9f 100644 --- a/src/Components/CreateImageWizardV2/steps/FileSystem/FileSystemConfiguration.tsx +++ b/src/Components/CreateImageWizardV2/steps/FileSystem/FileSystemConfiguration.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Alert, @@ -8,6 +8,8 @@ import { TextContent, TextInput, TextVariants, + useWizardContext, + WizardFooterWrapper, } from '@patternfly/react-core'; import { Select, SelectOption } from '@patternfly/react-core/deprecated'; import { @@ -29,9 +31,16 @@ import { removePartition, selectPartitions, changePartitionUnit, + setIsNextButtonTouched, + selectIsNextButtonTouched, + selectFileSystemPartitionMode, } from '../../../../store/wizardSlice'; import UsrSubDirectoriesDisabled from '../../UsrSubDirectoriesDisabled'; import { ValidatedTextInput } from '../../ValidatedTextInput'; +import { + getDuplicateMountPoints, + isFileSystemConfigValid, +} from '../../validators'; export type Partition = { id: string; @@ -40,12 +49,58 @@ export type Partition = { unit: Units; }; +export const FileSystemStepFooter = () => { + const { goToNextStep, goToPrevStep, close } = useWizardContext(); + const [isValid, setIsValid] = useState(false); + const dispatch = useAppDispatch(); + const [isNextDisabled, setNextDisabled] = useState(false); + const fileSystemPartitionMode = useAppSelector((state) => + selectFileSystemPartitionMode(state) + ); + const partitions = useAppSelector((state) => selectPartitions(state)); + + const onValidate = () => { + dispatch(setIsNextButtonTouched(false)); + if (!isValid) { + setNextDisabled(true); + } else { + goToNextStep(); + } + }; + useEffect(() => { + if ( + fileSystemPartitionMode === 'automatic' || + isFileSystemConfigValid(partitions) + ) { + setIsValid(true); + } else setIsValid(false); + setNextDisabled(false); + dispatch(setIsNextButtonTouched(true)); + }, [partitions, fileSystemPartitionMode, dispatch]); + return ( + + + + + + ); +}; + const FileSystemConfiguration = () => { const partitions = useAppSelector((state) => selectPartitions(state)); const environments = useAppSelector((state) => selectImageTypes(state)); const dispatch = useAppDispatch(); + const isNextButtonPristine = useAppSelector((state) => + selectIsNextButtonTouched(state) + ); const handleAddPartition = () => { const id = uuidv4(); dispatch( @@ -66,6 +121,17 @@ const FileSystemConfiguration = () => { {partitions?.find((partition) => partition?.mountpoint?.includes('/usr') ) && } + {!isNextButtonPristine && + getDuplicateMountPoints(partitions)?.length !== 0 && + getDuplicateMountPoints(partitions)?.length !== undefined && ( +
+ +
+ )} Create partitions for your image by defining mount points and minimum @@ -167,16 +233,29 @@ const getSuffix = (mountpoint: string) => { const Row = ({ partition }: RowPropTypes) => { const dispatch = useAppDispatch(); - + const partitions = useAppSelector((state) => selectPartitions(state)); const handleRemovePartition = (id: string) => { dispatch(removePartition(id)); }; + const isNextButtonPristine = useAppSelector((state) => + selectIsNextButtonTouched(state) + ); + const duplicates = getDuplicateMountPoints(partitions); return ( - + + {!isNextButtonPristine && + duplicates.indexOf(partition.mountpoint) !== -1 && ( + + )} {partition.mountpoint !== '/' && !partition.mountpoint.startsWith('/boot') && diff --git a/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx b/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx index a208ef28..6a34239c 100644 --- a/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx +++ b/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx @@ -111,6 +111,7 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => { fileSystem: { mode: 'automatic', partitions: [], + isNextButtonTouched: true, }, architecture: request.image_requests[0].architecture, diff --git a/src/Components/CreateImageWizardV2/validators.ts b/src/Components/CreateImageWizardV2/validators.ts index 7f89f584..bf3b53df 100644 --- a/src/Components/CreateImageWizardV2/validators.ts +++ b/src/Components/CreateImageWizardV2/validators.ts @@ -1,3 +1,5 @@ +import { Partition } from './steps/FileSystem/FileSystemConfiguration'; + export const isAwsAccountIdValid = (awsAccountId: string | undefined) => { return ( awsAccountId !== undefined && @@ -38,3 +40,23 @@ export const isBlueprintNameValid = (blueprintName: string) => export const isBlueprintDescriptionValid = (blueprintDescription: string) => { return blueprintDescription.length <= 250; }; +export const isFileSystemConfigValid = (partitions: Partition[]) => { + const duplicates = getDuplicateMountPoints(partitions); + return duplicates.length === 0; +}; +export const getDuplicateMountPoints = (partitions: Partition[]): string[] => { + const mountPointSet: Set = new Set(); + const duplicates: string[] = []; + if (!partitions) { + return []; + } + for (const partition of partitions) { + const mountPoint = partition.mountpoint; + if (mountPointSet.has(mountPoint)) { + duplicates.push(mountPoint); + } else { + mountPointSet.add(mountPoint); + } + } + return duplicates; +}; diff --git a/src/store/wizardSlice.ts b/src/store/wizardSlice.ts index bc31a773..3baab078 100644 --- a/src/store/wizardSlice.ts +++ b/src/store/wizardSlice.ts @@ -76,6 +76,7 @@ export type wizardState = { fileSystem: { mode: FileSystemPartitionMode; partitions: Partition[]; + isNextButtonTouched: boolean; }; repositories: { customRepositories: CustomRepository[]; @@ -130,6 +131,7 @@ const initialState: wizardState = { fileSystem: { mode: 'automatic', partitions: [], + isNextButtonTouched: true, }, repositories: { customRepositories: [], @@ -233,6 +235,9 @@ export const selectEnabledServices = (state: RootState) => { export const selectFileSystemPartitionMode = (state: RootState) => { return state.wizard.fileSystem.mode; }; +export const selectIsNextButtonTouched = (state: RootState) => { + return state.wizard.fileSystem.isNextButtonTouched; +}; export const selectPartitions = (state: RootState) => { return state.wizard.fileSystem.partitions; @@ -379,6 +384,10 @@ export const wizardSlice = createSlice({ ) => { state.fileSystem.partitions = action.payload; }, + setIsNextButtonTouched: (state, action: PayloadAction) => { + state.fileSystem.isNextButtonTouched = action.payload; + }, + changeFileSystemPartitionMode: ( state, action: PayloadAction @@ -489,6 +498,7 @@ export const { changeDisabledServices, changeEnabledServices, changeFileSystemConfiguration, + setIsNextButtonTouched, changeFileSystemPartitionMode, addPartition, removePartition,