Skip to content

Commit

Permalink
Wizard: Add validation to ChippingInput
Browse files Browse the repository at this point in the history
This adds step validation to ChippingInput, allowing to validate imported values.
  • Loading branch information
regexowl committed Feb 4, 2025
1 parent 6ec433f commit f11ab64
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 10 deletions.
21 changes: 14 additions & 7 deletions src/Components/CreateImageWizard/ChippingInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
import { PlusCircleIcon, TimesIcon } from '@patternfly/react-icons';
import { UnknownAction } from 'redux';

import { StepValidation } from './utilities/useValidation';

import { useAppDispatch } from '../../store/hooks';

type ChippingInputProps = {
Expand All @@ -24,6 +26,8 @@ type ChippingInputProps = {
item: string;
addAction: (value: string) => UnknownAction;
removeAction: (value: string) => UnknownAction;
stepValidation: StepValidation;
fieldName: string;
};

const ChippingInput = ({
Expand All @@ -35,11 +39,13 @@ const ChippingInput = ({
item,
addAction,
removeAction,
stepValidation,
fieldName,
}: ChippingInputProps) => {
const dispatch = useAppDispatch();

const [inputValue, setInputValue] = useState('');
const [errorText, setErrorText] = useState('');
const [errorText, setErrorText] = useState(stepValidation.errors[fieldName]);

const onTextInputChange = (
_event: React.FormEvent<HTMLInputElement>,
Expand Down Expand Up @@ -76,6 +82,11 @@ const ChippingInput = ({
addItem(value);
};

const handleRemoveItem = (e: React.MouseEvent, value: string) => {
dispatch(removeAction(value));
setErrorText('');
};

const handleClear = () => {
setInputValue('');
setErrorText('');
Expand Down Expand Up @@ -121,19 +132,15 @@ const ChippingInput = ({
className="pf-v5-u-mt-sm pf-v5-u-w-100"
>
{requiredList.map((item) => (
<Chip
key={item}
onClick={() => dispatch(removeAction(item))}
isReadOnly
>
<Chip key={item} isReadOnly>
{item}
</Chip>
))}
</ChipGroup>
)}
<ChipGroup numChips={20} className="pf-v5-u-mt-sm pf-v5-u-w-100">
{list?.map((item) => (
<Chip key={item} onClick={() => dispatch(removeAction(item))}>
<Chip key={item} onClick={(e) => handleRemoveItem(e, item)}>
{item}
</Chip>
))}
Expand Down
18 changes: 16 additions & 2 deletions src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
useHostnameValidation,
useKernelValidation,
useUsersValidation,
useTimezoneValidation,
useFirewallValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
Expand Down Expand Up @@ -244,10 +246,14 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
// Filesystem
const [filesystemPristine, setFilesystemPristine] = useState(true);
const fileSystemValidation = useFilesystemValidation();
// Timezone
const timezoneValidation = useTimezoneValidation();
// Hostname
const hostnameValidation = useHostnameValidation();
// Kernel
const kernelValidation = useKernelValidation();
// Firewall
const firewallValidation = useFirewallValidation();
// Firstboot
const firstBootValidation = useFirstBootValidation();
// Details
Expand Down Expand Up @@ -505,8 +511,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-timezone"
navItem={customStatusNavItem}
isHidden={!isTimezoneEnabled}
status={timezoneValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={timezoneValidation.disabledNext}
optional={true}
/>
}
>
<TimezoneStep />
Expand Down Expand Up @@ -561,8 +571,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-firewall"
navItem={customStatusNavItem}
isHidden={!isFirewallEnabled}
status={firewallValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={firewallValidation.disabledNext}
optional={true}
/>
}
>
<FirewallStep />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
selectFirewall,
} from '../../../../../store/wizardSlice';
import ChippingInput from '../../../ChippingInput';
import { useFirewallValidation } from '../../../utilities/useValidation';
import { isPortValid } from '../../../validators';

const PortsInput = () => {
const ports = useAppSelector(selectFirewall).ports;

const stepValidation = useFirewallValidation();

return (
<FormGroup label="Ports">
<ChippingInput
Expand All @@ -24,6 +27,8 @@ const PortsInput = () => {
item="Port"
addAction={addPort}
removeAction={removePort}
stepValidation={stepValidation}
fieldName="ports"
/>
</FormGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import {
selectFirewall,
} from '../../../../../store/wizardSlice';
import ChippingInput from '../../../ChippingInput';
import { useFirewallValidation } from '../../../utilities/useValidation';
import { isServiceValid } from '../../../validators';

const Services = () => {
const disabledServices = useAppSelector(selectFirewall).services.disabled;
const enabledServices = useAppSelector(selectFirewall).services.enabled;

const stepValidation = useFirewallValidation();

return (
<>
<FormGroup label="Disabled services">
Expand All @@ -28,6 +31,8 @@ const Services = () => {
item="Disabled service"
addAction={addDisabledFirewallService}
removeAction={removeDisabledFirewallService}
stepValidation={stepValidation}
fieldName="disabledServices"
/>
</FormGroup>
<FormGroup label="Enabled services">
Expand All @@ -39,6 +44,8 @@ const Services = () => {
item="Enabled service"
addAction={addEnabledFirewallService}
removeAction={removeEnabledFirewallService}
stepValidation={stepValidation}
fieldName="enabledServices"
/>
</FormGroup>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import {
selectKernel,
} from '../../../../../store/wizardSlice';
import ChippingInput from '../../../ChippingInput';
import { useKernelValidation } from '../../../utilities/useValidation';
import { isKernelArgumentValid } from '../../../validators';

const KernelArguments = () => {
const kernelAppend = useAppSelector(selectKernel).append;

const stepValidation = useKernelValidation();

const release = useAppSelector(selectDistribution);
const complianceProfileID = useAppSelector(selectComplianceProfileID);

Expand Down Expand Up @@ -46,6 +49,8 @@ const KernelArguments = () => {
item="Kernel argument"
addAction={addKernelArg}
removeAction={removeKernelArg}
stepValidation={stepValidation}
fieldName="kernelAppend"
/>
</FormGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
selectNtpServers,
} from '../../../../../store/wizardSlice';
import ChippingInput from '../../../ChippingInput';
import { useTimezoneValidation } from '../../../utilities/useValidation';
import { isNtpServerValid } from '../../../validators';

const NtpServersInput = () => {
const ntpServers = useAppSelector(selectNtpServers);

const stepValidation = useTimezoneValidation();

return (
<FormGroup isRequired={false} label="NTP servers">
<ChippingInput
Expand All @@ -24,6 +27,8 @@ const NtpServersInput = () => {
item="NTP server"
addAction={addNtpServer}
removeAction={removeNtpServer}
stepValidation={stepValidation}
fieldName="ntpServers"
/>
</FormGroup>
);
Expand Down
107 changes: 107 additions & 0 deletions src/Components/CreateImageWizard/utilities/useValidation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
selectUsers,
selectUserPasswordByIndex,
selectUserSshKeyByIndex,
selectNtpServers,
selectFirewall,
} from '../../../store/wizardSlice';
import {
getDuplicateMountPoints,
Expand All @@ -33,6 +35,10 @@ import {
isKernelNameValid,
isUserNameValid,
isSshKeyValid,
isNtpServerValid,
isKernelArgumentValid,
isPortValid,
isServiceValid,
} from '../validators';

export type StepValidation = {
Expand All @@ -46,16 +52,20 @@ export function useIsBlueprintValid(): boolean {
const registration = useRegistrationValidation();
const filesystem = useFilesystemValidation();
const snapshot = useSnapshotValidation();
const timezone = useTimezoneValidation();
const hostname = useHostnameValidation();
const kernel = useKernelValidation();
const firewall = useFirewallValidation();
const firstBoot = useFirstBootValidation();
const details = useDetailsValidation();
return (
!registration.disabledNext &&
!filesystem.disabledNext &&
!snapshot.disabledNext &&
!timezone.disabledNext &&
!hostname.disabledNext &&
!kernel.disabledNext &&
!firewall.disabledNext &&
!firstBoot.disabledNext &&
!details.disabledNext
);
Expand Down Expand Up @@ -130,6 +140,29 @@ export function useSnapshotValidation(): StepValidation {
return { errors: {}, disabledNext: false };
}

export function useTimezoneValidation(): StepValidation {
const ntpServers = useAppSelector(selectNtpServers);

if (ntpServers) {
const invalidServers = [];

for (const server of ntpServers) {
if (!isNtpServerValid(server)) {
invalidServers.push(server);
}
}

if (invalidServers.length > 0) {
return {
errors: { ntpServers: `Invalid ntpServers: ${invalidServers}` },
disabledNext: true,
};
}
}

return { errors: {}, disabledNext: false };
}

export function useFirstBootValidation(): StepValidation {
const script = useAppSelector(selectFirstBootScript);
let hasShebang = false;
Expand Down Expand Up @@ -174,9 +207,83 @@ export function useKernelValidation(): StepValidation {
disabledNext: true,
};
}

if (kernel.append.length > 0) {
const invalidArgs = [];

for (const arg of kernel.append) {
if (!isKernelArgumentValid(arg)) {
invalidArgs.push(arg);
}
}

if (invalidArgs.length > 0) {
return {
errors: { kernelAppend: `Invalid kernel arguments: ${invalidArgs}` },
disabledNext: true,
};
}
}

return { errors: {}, disabledNext: false };
}

export function useFirewallValidation(): StepValidation {
const firewall = useAppSelector(selectFirewall);
const errors = {};
const invalidPorts = [];
const invalidDisabled = [];
const invalidEnabled = [];

if (firewall.ports.length > 0) {
for (const port of firewall.ports) {
if (!isPortValid(port)) {
invalidPorts.push(port);
}
}

if (invalidPorts.length > 0) {
Object.assign(errors, { ports: `Invalid ports: ${invalidPorts}` });
}
}

if (firewall.services.disabled.length > 0) {
for (const s of firewall.services.disabled) {
if (!isServiceValid(s)) {
invalidDisabled.push(s);
}
}

if (invalidDisabled.length > 0) {
Object.assign(errors, {
disabledServices: `Invalid disabled services: ${invalidDisabled}`,
});
}
}

if (firewall.services.enabled.length > 0) {
for (const s of firewall.services.enabled) {
if (!isServiceValid(s)) {
invalidEnabled.push(s);
}
}

if (invalidEnabled.length > 0) {
Object.assign(errors, {
enabledServices: `Invalid enabled services: ${invalidEnabled}`,
});
}
}

return {
errors,
disabledNext:
invalidPorts.length > 0 ||
invalidDisabled.length > 0 ||
invalidEnabled.length > 0,
};
}

export function useUsersValidation(): StepValidation {
const index = 0;
const userNameSelector = selectUserNameByIndex(index);
Expand Down
2 changes: 1 addition & 1 deletion src/Components/CreateImageWizard/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const isKernelArgumentValid = (arg: string) => {
return true;
}

return /^[a-zA-Z0-9=-_,"']*$/.test(arg);
return /^[a-zA-Z0-9=-_,."']*$/.test(arg);
};

export const isPortValid = (port: string) => {
Expand Down

0 comments on commit f11ab64

Please sign in to comment.