Wizard: Add validation to ChippingInput
This adds step validation to ChippingInput, allowing to validate imported values.
This commit is contained in:
parent
6ec433f9d3
commit
f11ab64262
8 changed files with 160 additions and 10 deletions
|
|
@ -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 = {
|
||||
|
|
@ -24,6 +26,8 @@ type ChippingInputProps = {
|
|||
item: string;
|
||||
addAction: (value: string) => UnknownAction;
|
||||
removeAction: (value: string) => UnknownAction;
|
||||
stepValidation: StepValidation;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
const ChippingInput = ({
|
||||
|
|
@ -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>,
|
||||
|
|
@ -76,6 +82,11 @@ const ChippingInput = ({
|
|||
addItem(value);
|
||||
};
|
||||
|
||||
const handleRemoveItem = (e: React.MouseEvent, value: string) => {
|
||||
dispatch(removeAction(value));
|
||||
setErrorText('');
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setInputValue('');
|
||||
setErrorText('');
|
||||
|
|
@ -121,11 +132,7 @@ 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>
|
||||
))}
|
||||
|
|
@ -133,7 +140,7 @@ const ChippingInput = ({
|
|||
)}
|
||||
<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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import {
|
|||
useHostnameValidation,
|
||||
useKernelValidation,
|
||||
useUsersValidation,
|
||||
useTimezoneValidation,
|
||||
useFirewallValidation,
|
||||
} from './utilities/useValidation';
|
||||
import {
|
||||
isAwsAccountIdValid,
|
||||
|
|
@ -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
|
||||
|
|
@ -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 />
|
||||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -24,6 +27,8 @@ const PortsInput = () => {
|
|||
item="Port"
|
||||
addAction={addPort}
|
||||
removeAction={removePort}
|
||||
stepValidation={stepValidation}
|
||||
fieldName="ports"
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
@ -28,6 +31,8 @@ const Services = () => {
|
|||
item="Disabled service"
|
||||
addAction={addDisabledFirewallService}
|
||||
removeAction={removeDisabledFirewallService}
|
||||
stepValidation={stepValidation}
|
||||
fieldName="disabledServices"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Enabled services">
|
||||
|
|
@ -39,6 +44,8 @@ const Services = () => {
|
|||
item="Enabled service"
|
||||
addAction={addEnabledFirewallService}
|
||||
removeAction={removeEnabledFirewallService}
|
||||
stepValidation={stepValidation}
|
||||
fieldName="enabledServices"
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -46,6 +49,8 @@ const KernelArguments = () => {
|
|||
item="Kernel argument"
|
||||
addAction={addKernelArg}
|
||||
removeAction={removeKernelArg}
|
||||
stepValidation={stepValidation}
|
||||
fieldName="kernelAppend"
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -24,6 +27,8 @@ const NtpServersInput = () => {
|
|||
item="NTP server"
|
||||
addAction={addNtpServer}
|
||||
removeAction={removeNtpServer}
|
||||
stepValidation={stepValidation}
|
||||
fieldName="ntpServers"
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import {
|
|||
selectUsers,
|
||||
selectUserPasswordByIndex,
|
||||
selectUserSshKeyByIndex,
|
||||
selectNtpServers,
|
||||
selectFirewall,
|
||||
} from '../../../store/wizardSlice';
|
||||
import {
|
||||
getDuplicateMountPoints,
|
||||
|
|
@ -33,6 +35,10 @@ import {
|
|||
isKernelNameValid,
|
||||
isUserNameValid,
|
||||
isSshKeyValid,
|
||||
isNtpServerValid,
|
||||
isKernelArgumentValid,
|
||||
isPortValid,
|
||||
isServiceValid,
|
||||
} from '../validators';
|
||||
|
||||
export type StepValidation = {
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue