diff --git a/src/Components/CreateImageWizard/LabelInput.tsx b/src/Components/CreateImageWizard/LabelInput.tsx index 0f91f624..031fa3e7 100644 --- a/src/Components/CreateImageWizard/LabelInput.tsx +++ b/src/Components/CreateImageWizard/LabelInput.tsx @@ -48,30 +48,36 @@ const LabelInput = ({ const dispatch = useAppDispatch(); const [inputValue, setInputValue] = useState(''); - const [errorText, setErrorText] = useState(stepValidation.errors[fieldName]); + const [onStepInputErrorText, setOnStepInputErrorText] = useState(''); + let [invalidImports, duplicateImports] = ['', '']; + + if (stepValidation.errors[fieldName]) { + [invalidImports, duplicateImports] = + stepValidation.errors[fieldName].split('|'); + } const onTextInputChange = ( _event: React.FormEvent, value: string ) => { setInputValue(value); - setErrorText(''); + setOnStepInputErrorText(''); }; const addItem = (value: string) => { if (list?.includes(value) || requiredList?.includes(value)) { - setErrorText(`${item} already exists.`); + setOnStepInputErrorText(`${item} already exists.`); return; } if (!validator(value)) { - setErrorText('Invalid format.'); + setOnStepInputErrorText('Invalid format.'); return; } dispatch(addAction(value)); setInputValue(''); - setErrorText(''); + setOnStepInputErrorText(''); }; const handleKeyDown = (e: React.KeyboardEvent, value: string) => { @@ -87,14 +93,18 @@ const LabelInput = ({ const handleRemoveItem = (e: React.MouseEvent, value: string) => { dispatch(removeAction(value)); - setErrorText(''); }; const handleClear = () => { setInputValue(''); - setErrorText(''); + setOnStepInputErrorText(''); }; + const errors = []; + if (onStepInputErrorText) errors.push(onStepInputErrorText); + if (invalidImports) errors.push(invalidImports); + if (duplicateImports) errors.push(duplicateImports); + return ( <> @@ -125,9 +135,13 @@ const LabelInput = ({ /> - {errorText && ( + {errors.length > 0 && ( - {errorText} + {errors.map((error, index) => ( + + {error} + + ))} )} {requiredList && requiredList.length > 0 && ( diff --git a/src/Components/CreateImageWizard/utilities/getListOfDuplicates.ts b/src/Components/CreateImageWizard/utilities/getListOfDuplicates.ts new file mode 100644 index 00000000..f258bf16 --- /dev/null +++ b/src/Components/CreateImageWizard/utilities/getListOfDuplicates.ts @@ -0,0 +1,6 @@ +export const getListOfDuplicates = (list: string[]) => { + const duplicates = list.filter((item, index) => list.indexOf(item) !== index); + const uniqueDuplicates = [...new Set(duplicates)]; + + return uniqueDuplicates; +}; diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx index 90d75ab2..93e49677 100644 --- a/src/Components/CreateImageWizard/utilities/useValidation.tsx +++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from 'react'; import { CheckCircleIcon } from '@patternfly/react-icons'; import { jwtDecode, JwtPayload } from 'jwt-decode'; +import { getListOfDuplicates } from './getListOfDuplicates'; + import { UNIQUE_VALIDATION_DELAY } from '../../../constants'; import { useLazyGetBlueprintsQuery } from '../../../store/backendApi'; import { useAppSelector } from '../../../store/hooks'; @@ -281,14 +283,26 @@ export function useTimezoneValidation(): StepValidation { } } + const duplicateNtpServers = getListOfDuplicates(ntpServers || []); + const timezoneError = timezone && !timezones.includes(timezone) ? 'Unknown timezone' : ''; const ntpServersError = invalidServers.length > 0 ? `Invalid NTP servers: ${invalidServers}` : ''; + const duplicateNtpServersError = + duplicateNtpServers.length > 0 + ? `Includes duplicate NTP servers: ${duplicateNtpServers.join(', ')}` + : ''; return { - errors: { timezone: timezoneError, ntpServers: ntpServersError }, - disabledNext: timezoneError !== '' || invalidServers.length > 0, + errors: { + timezone: timezoneError, + ntpServers: ntpServersError + '|' + duplicateNtpServersError, + }, + disabledNext: + timezoneError !== '' || + invalidServers.length > 0 || + duplicateNtpServers.length > 0, }; } @@ -363,15 +377,28 @@ export function useKernelValidation(): StepValidation { } } + const duplicateKernelArgs = getListOfDuplicates(kernel.append); + const kernelNameError = kernel.name && !isKernelNameValid(kernel.name) ? 'Invalid format.' : ''; const kernelAppendError = invalidArgs.length > 0 ? `Invalid kernel arguments: ${invalidArgs}` : ''; + const duplicateKernelArgsError = + duplicateKernelArgs.length > 0 + ? `Includes duplicate kernel arguments: ${duplicateKernelArgs.join(', ')}` + : ''; + return { - errors: { kernel: kernelNameError, kernelAppend: kernelAppendError }, - disabledNext: kernelNameError !== '' || kernelAppendError !== '', + errors: { + kernel: kernelNameError, + kernelAppend: kernelAppendError + '|' + duplicateKernelArgsError, + }, + disabledNext: + kernelNameError !== '' || + kernelAppendError !== '' || + duplicateKernelArgs.length > 0, }; } @@ -405,8 +432,32 @@ export function useFirewallValidation(): StepValidation { } } + const duplicatePorts = getListOfDuplicates(firewall.ports); + const duplicateDisabledServices = getListOfDuplicates( + firewall.services.disabled + ); + const duplicateEnabledServices = getListOfDuplicates( + firewall.services.enabled + ); + const portsError = invalidPorts.length > 0 ? `Invalid ports: ${invalidPorts}` : ''; + const duplicatePortsError = + duplicatePorts.length > 0 + ? `Includes duplicate ports: ${duplicatePorts.join(', ')}` + : ''; + const duplicateDisabledServicesError = + duplicateDisabledServices.length > 0 + ? `Includes duplicate disabled services: ${duplicateDisabledServices.join( + ', ' + )}` + : ''; + const duplicateEnabledServicesError = + duplicateEnabledServices.length > 0 + ? `Includes duplicate enabled services: ${duplicateEnabledServices.join( + ', ' + )}` + : ''; const disabledServicesError = invalidDisabled.length > 0 ? `Invalid disabled services: ${invalidDisabled}` @@ -418,14 +469,19 @@ export function useFirewallValidation(): StepValidation { return { errors: { - ports: portsError, - disabledServices: disabledServicesError, - enabledServices: enabledServicesError, + ports: portsError + '|' + duplicatePortsError, + disabledServices: + disabledServicesError + '|' + duplicateDisabledServicesError, + enabledServices: + enabledServicesError + '|' + duplicateEnabledServicesError, }, disabledNext: invalidPorts.length > 0 || invalidDisabled.length > 0 || - invalidEnabled.length > 0, + invalidEnabled.length > 0 || + duplicatePorts.length > 0 || + duplicateDisabledServices.length > 0 || + duplicateEnabledServices.length > 0, }; } @@ -460,6 +516,10 @@ export function useServicesValidation(): StepValidation { } } + const duplicateDisabledServices = getListOfDuplicates(services.disabled); + const duplicateMaskedServices = getListOfDuplicates(services.masked); + const duplicateEnabledServices = getListOfDuplicates(services.enabled); + const disabledSystemdServicesError = invalidDisabled.length > 0 ? `Invalid disabled services: ${invalidDisabled}` @@ -470,17 +530,41 @@ export function useServicesValidation(): StepValidation { invalidEnabled.length > 0 ? `Invalid enabled services: ${invalidEnabled}` : ''; + const duplicateDisabledServicesError = + duplicateDisabledServices.length > 0 + ? `Includes duplicate disabled services: ${duplicateDisabledServices.join( + ', ' + )}` + : ''; + const duplicateMaskedServicesError = + duplicateMaskedServices.length > 0 + ? `Includes duplicate masked services: ${duplicateMaskedServices.join( + ', ' + )}` + : ''; + const duplicateEnabledServicesError = + duplicateEnabledServices.length > 0 + ? `Includes duplicate enabled services: ${duplicateEnabledServices.join( + ', ' + )}` + : ''; return { errors: { - disabledSystemdServices: disabledSystemdServicesError, - maskedSystemdServices: maskedSystemdServicesError, - enabledSystemdServices: enabledSystemdServicesError, + disabledSystemdServices: + disabledSystemdServicesError + '|' + duplicateDisabledServicesError, + maskedSystemdServices: + maskedSystemdServicesError + '|' + duplicateMaskedServicesError, + enabledSystemdServices: + enabledSystemdServicesError + '|' + duplicateEnabledServicesError, }, disabledNext: invalidDisabled.length > 0 || invalidMasked.length > 0 || - invalidEnabled.length > 0, + invalidEnabled.length > 0 || + duplicateDisabledServices.length > 0 || + duplicateMaskedServices.length > 0 || + duplicateEnabledServices.length > 0, }; } @@ -544,20 +628,27 @@ export function useUsersValidation(): UsersStepValidation { } } - const groupsError = + const duplicateGroups = getListOfDuplicates(users[index].groups); + + const invalidError = invalidGroups.length > 0 ? `Invalid user groups: ${invalidGroups}` : ''; + const duplicateError = + duplicateGroups.length > 0 + ? `Includes duplicate groups: ${duplicateGroups.join(', ')}` + : ''; if ( userNameError || sshKeyError || (users[index].password && !isPasswordValid) || - groupsError + invalidError || + duplicateError ) { - errors[`${index}`] = { + errors[index] = { userName: userNameError, userSshKey: sshKeyError, userPassword: passwordError, - groups: groupsError, + groups: invalidError + '|' + duplicateError, }; } }