fix form validation mode
This commit is contained in:
2
client/package.json
vendored
2
client/package.json
vendored
@@ -12,7 +12,7 @@
|
||||
"lint:fix": "eslint './src/**/*.(ts|tsx)' --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:e2e": "npx playwright test install.spec.ts --headed && npx playwright test test/e2e/dashboard",
|
||||
"test:e2e": "npx playwright test tests/e2e --headed",
|
||||
"test:e2e:interactive": "npx playwright test --ui",
|
||||
"test:e2e:debug": "npx playwright test --debug",
|
||||
"test:e2e:codegen": "npx playwright codegen",
|
||||
|
||||
@@ -29,7 +29,7 @@ const Check = ({ onSubmit }: Props) => {
|
||||
handleSubmit,
|
||||
formState: { isDirty, isValid },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
||||
control,
|
||||
formState: { isDirty, isSubmitting },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
domain: currentRewrite?.domain || '',
|
||||
answer: currentRewrite?.answer || '',
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
||||
setValue,
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
|
||||
const history = useHistory();
|
||||
|
||||
const { register, watch, setValue } = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
search: initialValues.search || DEFAULT_LOGS_FILTER.search,
|
||||
response_status: initialValues.response_status || DEFAULT_LOGS_FILTER.response_status,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const Form = ({
|
||||
...defaultFormValues,
|
||||
...initialValues,
|
||||
},
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -23,7 +23,7 @@ type FormDHCPv4Props = {
|
||||
lease_duration: string;
|
||||
};
|
||||
interfaces: any;
|
||||
onSubmit?: (data: DhcpFormValues) => Promise<void> | void;
|
||||
onSubmit?: (data: DhcpFormValues) => void;
|
||||
};
|
||||
|
||||
const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }: FormDHCPv4Props) => {
|
||||
@@ -32,7 +32,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
formState: { errors, isSubmitting },
|
||||
watch,
|
||||
} = useFormContext<DhcpFormValues>();
|
||||
|
||||
@@ -41,25 +41,20 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
|
||||
const formValues = watch('v4');
|
||||
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
||||
|
||||
const handleFormSubmit = async (data: DhcpFormValues) => {
|
||||
// TODO handle submit
|
||||
if (onSubmit) {
|
||||
await onSubmit(data);
|
||||
}
|
||||
};
|
||||
const hasV4Errors = errors.v4 && Object.keys(errors.v4).length > 0;
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return isSubmitting || !isValid || processingConfig || !isInterfaceIncludesIpv4 || isEmptyConfig;
|
||||
}, [isSubmitting, isValid, processingConfig, isInterfaceIncludesIpv4, isEmptyConfig]);
|
||||
return isSubmitting || hasV4Errors || processingConfig || !isInterfaceIncludesIpv4 || isEmptyConfig;
|
||||
}, [isSubmitting, hasV4Errors, processingConfig, isInterfaceIncludesIpv4, isEmptyConfig]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<input
|
||||
data-testid="v4_gateway_ip"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders?.gateway_ip || '')}
|
||||
@@ -80,6 +75,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<input
|
||||
data-testid="v4_subnet_mask"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders?.subnet_mask || '')}
|
||||
@@ -106,6 +102,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
|
||||
<div className="col">
|
||||
<input
|
||||
data-testid="v4_range_start"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders?.range_start || '')}
|
||||
@@ -126,6 +123,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
|
||||
<div className="col">
|
||||
<input
|
||||
data-testid="v4_range_end"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders?.range_end || '')}
|
||||
@@ -150,6 +148,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<input
|
||||
data-testid="v4_lease_duration"
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders?.lease_duration || '')}
|
||||
@@ -173,7 +172,11 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
|
||||
</div>
|
||||
|
||||
<div className="btn-list">
|
||||
<button type="submit" className="btn btn-success btn-standard" disabled={isDisabled}>
|
||||
<button
|
||||
data-testid="v4_save"
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={isDisabled}>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,6 @@ import { UINT32_RANGE } from '../../../helpers/constants';
|
||||
import { validateIpv6, validateRequiredValue } from '../../../helpers/validators';
|
||||
import { DhcpFormValues } from '.';
|
||||
|
||||
type FormValues = {
|
||||
v6?: {
|
||||
range_start?: string;
|
||||
range_end?: string;
|
||||
lease_duration?: number;
|
||||
};
|
||||
};
|
||||
|
||||
type FormDHCPv6Props = {
|
||||
processingConfig?: boolean;
|
||||
ipv6placeholders?: {
|
||||
@@ -40,18 +32,12 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
const formValues = watch('v6');
|
||||
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
||||
|
||||
const handleFormSubmit = async (data: FormValues) => {
|
||||
if (onSubmit) {
|
||||
await onSubmit(data);
|
||||
}
|
||||
};
|
||||
|
||||
const isDisabled = useMemo(() => {
|
||||
return isSubmitting || !isValid || processingConfig || !isInterfaceIncludesIpv6 || isEmptyConfig;
|
||||
}, [isSubmitting, isValid, processingConfig, isInterfaceIncludesIpv6, isEmptyConfig]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
@@ -62,6 +48,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
|
||||
<div className="col">
|
||||
<input
|
||||
data-testid="v6_range_start"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv6placeholders?.range_start || '')}
|
||||
@@ -70,7 +57,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
validate: {
|
||||
ipv6: validateIpv6,
|
||||
required: (value) =>
|
||||
isEmptyConfig ? undefined : validateRequiredValue(value),
|
||||
isInterfaceIncludesIpv6 ? undefined : validateRequiredValue(value),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
@@ -83,6 +70,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
|
||||
<div className="col">
|
||||
<input
|
||||
data-testid="v6_range_end"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv6placeholders?.range_end || '')}
|
||||
@@ -91,7 +79,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
validate: {
|
||||
ipv6: validateIpv6,
|
||||
required: (value) =>
|
||||
isEmptyConfig ? undefined : validateRequiredValue(value),
|
||||
isInterfaceIncludesIpv6 ? undefined : validateRequiredValue(value),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
@@ -110,6 +98,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
<div className="col-lg-6 form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<input
|
||||
data-testid="v6_lease_duration"
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t(ipv6placeholders?.lease_duration || '')}
|
||||
@@ -119,7 +108,8 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
{...register('v6.lease_duration', {
|
||||
valueAsNumber: true,
|
||||
validate: {
|
||||
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
|
||||
required: (value) =>
|
||||
isInterfaceIncludesIpv6 ? undefined : validateRequiredValue(value),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
@@ -130,7 +120,11 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
|
||||
</div>
|
||||
|
||||
<div className="btn-list">
|
||||
<button type="submit" className="btn btn-success btn-standard" disabled={isDisabled}>
|
||||
<button
|
||||
data-testid="v6_save"
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={isDisabled}>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -99,6 +99,7 @@ const Interfaces = () => {
|
||||
</label>
|
||||
<select
|
||||
id="interface_name"
|
||||
data-testid="interface_name"
|
||||
className="form-control custom-select pl-4 col-md"
|
||||
disabled={enabled}
|
||||
{...register('interface_name', {
|
||||
|
||||
@@ -96,7 +96,7 @@ const Dhcp = () => {
|
||||
} = useSelector((state: RootState) => state.dhcp, shallowEqual);
|
||||
|
||||
const methods = useForm<DhcpFormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
v4: v4 || DEFAULT_V4_VALUES,
|
||||
v6: v6 || DEFAULT_V6_VALUES,
|
||||
@@ -127,8 +127,14 @@ const Dhcp = () => {
|
||||
useEffect(() => {
|
||||
if (v4 || v6 || interfaceName) {
|
||||
reset({
|
||||
v4: v4 || DEFAULT_V4_VALUES,
|
||||
v6: v6 || DEFAULT_V6_VALUES,
|
||||
v4: {
|
||||
...DEFAULT_V4_VALUES,
|
||||
...v4,
|
||||
},
|
||||
v6: {
|
||||
...DEFAULT_V6_VALUES,
|
||||
...v6,
|
||||
},
|
||||
interface_name: interfaceName || '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
allowed_clients: initialValues?.allowed_clients || '',
|
||||
disallowed_clients: initialValues?.disallowed_clients || '',
|
||||
|
||||
@@ -53,7 +53,7 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
cache_size: initialValues?.cache_size || 0,
|
||||
cache_ttl_min: initialValues?.cache_ttl_min || 0,
|
||||
|
||||
@@ -71,7 +71,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
control,
|
||||
formState: { errors, isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
upstream_dns: initialValues?.upstream_dns || '',
|
||||
upstream_mode: initialValues?.upstream_mode || DNS_REQUEST_OPTIONS.LOAD_BALANCING,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Checkbox } from '../../ui/Controls/Checkbox';
|
||||
import { Radio } from '../../ui/Controls/Radio';
|
||||
import { Input } from '../../ui/Controls/Input';
|
||||
import { Textarea } from '../../ui/Controls/Textarea';
|
||||
import { EncryptionData } from '../../../initialState';
|
||||
|
||||
const certificateSourceOptions = [
|
||||
{
|
||||
@@ -71,7 +72,7 @@ const validationMessage = (warningValidation: string, isWarning: boolean) => {
|
||||
);
|
||||
};
|
||||
|
||||
export type FormValues = {
|
||||
export type EncryptionFormValues = {
|
||||
enabled: boolean;
|
||||
serve_plain_dns: boolean;
|
||||
server_name: string;
|
||||
@@ -89,7 +90,7 @@ export type FormValues = {
|
||||
};
|
||||
|
||||
type Props = {
|
||||
initialValues: FormValues;
|
||||
initialValues: EncryptionFormValues;
|
||||
processingConfig: boolean;
|
||||
processingValidate: boolean;
|
||||
status_key?: string;
|
||||
@@ -103,10 +104,10 @@ type Props = {
|
||||
key_type?: string;
|
||||
issuer?: string;
|
||||
subject?: string;
|
||||
onSubmit: (...args: unknown[]) => void;
|
||||
onChange: (...args: unknown[]) => void;
|
||||
setTlsConfig: (...args: unknown[]) => void;
|
||||
validateTlsConfig: (...args: unknown[]) => void;
|
||||
onSubmit: (values: EncryptionFormValues) => void;
|
||||
debouncedConfigValidation: (values: EncryptionFormValues) => void;
|
||||
setTlsConfig: (values: Partial<EncryptionData>) => void;
|
||||
validateTlsConfig: (values: Partial<EncryptionData>) => void;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
@@ -141,11 +142,12 @@ export const Form = ({
|
||||
subject,
|
||||
warning_validation,
|
||||
onSubmit,
|
||||
debouncedConfigValidation,
|
||||
setTlsConfig,
|
||||
validateTlsConfig,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const previousValuesRef = useRef<FormValues>(initialValues);
|
||||
const previousValuesRef = useRef<EncryptionFormValues>(initialValues);
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -156,31 +158,35 @@ export const Form = ({
|
||||
setError,
|
||||
getValues,
|
||||
formState: { isSubmitting, isValid },
|
||||
} = useForm<FormValues>({
|
||||
} = useForm<EncryptionFormValues>({
|
||||
defaultValues: {
|
||||
...initialValues,
|
||||
...defaultValues,
|
||||
},
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
});
|
||||
|
||||
const watchedValues = watch();
|
||||
|
||||
const isEnabled = watch('enabled');
|
||||
const servePlainDns = watch('serve_plain_dns');
|
||||
const certificateChain = watch('certificate_chain');
|
||||
const privateKey = watch('private_key');
|
||||
const certificatePath = watch('certificate_path');
|
||||
const privateKeyPath = watch('private_key_path');
|
||||
const certificateSource = watch('certificate_source');
|
||||
const privateKeySaved = watch('private_key_saved');
|
||||
const privateKeySource = watch('key_source');
|
||||
const {
|
||||
enabled: isEnabled,
|
||||
serve_plain_dns: servePlainDns,
|
||||
certificate_chain: certificateChain,
|
||||
private_key: privateKey,
|
||||
certificate_path: certificatePath,
|
||||
private_key_path: privateKeyPath,
|
||||
certificate_source: certificateSource,
|
||||
private_key_saved: privateKeySaved,
|
||||
key_source: privateKeySource,
|
||||
} = watchedValues;
|
||||
|
||||
useEffect(() => {
|
||||
const previousValues = previousValuesRef.current;
|
||||
|
||||
if (JSON.stringify(previousValues) !== JSON.stringify(watchedValues)) {
|
||||
// TODO onChange TLS config validation
|
||||
// TODO(ik) onChange TLS config validation
|
||||
console.log('debouncedConfigValidation');
|
||||
debouncedConfigValidation(watchedValues);
|
||||
previousValuesRef.current = watchedValues;
|
||||
}
|
||||
}, [watchedValues]);
|
||||
@@ -203,7 +209,7 @@ export const Form = ({
|
||||
}
|
||||
};
|
||||
|
||||
const validatePorts = (values: FormValues) => {
|
||||
const validatePorts = (values: EncryptionFormValues) => {
|
||||
const errors: { port_dns_over_tls?: string; port_https?: string } = {};
|
||||
|
||||
if (values.port_dns_over_tls && values.port_https) {
|
||||
@@ -216,12 +222,12 @@ export const Form = ({
|
||||
return errors;
|
||||
};
|
||||
|
||||
const onFormSubmit = (data: FormValues) => {
|
||||
const onFormSubmit = (data: EncryptionFormValues) => {
|
||||
const validationErrors = validatePorts(data);
|
||||
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
Object.entries(validationErrors).forEach(([field, message]) => {
|
||||
setError(field as keyof FormValues, { type: 'manual', message });
|
||||
setError(field as keyof EncryptionFormValues, { type: 'manual', message });
|
||||
});
|
||||
} else {
|
||||
onSubmit(data);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { debounce } from 'lodash';
|
||||
import { DEBOUNCE_TIMEOUT, ENCRYPTION_SOURCE } from '../../../helpers/constants';
|
||||
|
||||
import { Form, FormValues } from './Form';
|
||||
import { EncryptionFormValues, Form } from './Form';
|
||||
import Card from '../../ui/Card';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
@@ -11,8 +11,8 @@ import { EncryptionData } from '../../../initialState';
|
||||
|
||||
type Props = {
|
||||
encryption: EncryptionData;
|
||||
setTlsConfig: (values: EncryptionData) => void;
|
||||
validateTlsConfig: (values: EncryptionData) => void;
|
||||
setTlsConfig: (values: Partial<EncryptionData>) => void;
|
||||
validateTlsConfig: (values: Partial<EncryptionData>) => void;
|
||||
};
|
||||
|
||||
export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Props) => {
|
||||
@@ -24,7 +24,7 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
|
||||
}
|
||||
}, [encryption, validateTlsConfig]);
|
||||
|
||||
const getInitialValues = useCallback((data: any): FormValues => {
|
||||
const getInitialValues = useCallback((data: any): EncryptionFormValues => {
|
||||
const { certificate_chain, private_key, private_key_saved } = data;
|
||||
const certificate_source = certificate_chain ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
|
||||
const key_source = private_key || private_key_saved ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
|
||||
@@ -67,15 +67,16 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
|
||||
[getSubmitValues, setTlsConfig],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
const debouncedConfigValidation = useCallback(
|
||||
debounce((values) => {
|
||||
const submitValues = getSubmitValues(values);
|
||||
|
||||
if (submitValues.enabled) {
|
||||
console.log('validateTlsConfig');
|
||||
validateTlsConfig(submitValues);
|
||||
}
|
||||
}, DEBOUNCE_TIMEOUT),
|
||||
[getSubmitValues, validateTlsConfig],
|
||||
[],
|
||||
);
|
||||
|
||||
const initialValues = getInitialValues(encryption);
|
||||
@@ -94,7 +95,7 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
|
||||
<Form
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
onChange={handleChange}
|
||||
debouncedConfigValidation={debouncedConfigValidation}
|
||||
setTlsConfig={setTlsConfig}
|
||||
validateTlsConfig={validateTlsConfig}
|
||||
{...encryption}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
|
||||
const prevFormValuesRef = useRef<FormValues>(initialValues);
|
||||
|
||||
const { register, watch, control } = useForm({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
enabled: initialValues.enabled || false,
|
||||
anonymize_client_ip: initialValues.anonymize_client_ip || false,
|
||||
|
||||
@@ -55,7 +55,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
...defaultFormValues,
|
||||
...initialValues,
|
||||
|
||||
@@ -67,7 +67,7 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
||||
control,
|
||||
formState: { isValid },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
...defaultFormValues,
|
||||
...initialValues,
|
||||
|
||||
@@ -125,7 +125,7 @@ export const validateGatewaySubnetMask = (_: any, allValues: any) => {
|
||||
* @param allValues
|
||||
*/
|
||||
export const validateIpForGatewaySubnetMask = (value: any, allValues: any) => {
|
||||
if (!allValues || !allValues.v4 || !value) {
|
||||
if (!allValues || !allValues.v4 || !value || !allValues.gateway_ip || !allValues.subnet_mask) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export const Auth = ({ onAuthSubmit }: Props) => {
|
||||
control,
|
||||
formState: { isDirty, isValid },
|
||||
} = useForm<AuthFormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
|
||||
@@ -101,7 +101,7 @@ export const Settings = ({ handleSubmit, handleFix, validateForm, config, interf
|
||||
formState: { isValid },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
});
|
||||
|
||||
const watchFields = watch();
|
||||
|
||||
@@ -21,7 +21,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
|
||||
control,
|
||||
formState: { isValid },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
mode: 'onBlur',
|
||||
defaultValues: {
|
||||
username: '',
|
||||
password: '',
|
||||
@@ -39,6 +39,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="username"
|
||||
type="text"
|
||||
label={t('username_label')}
|
||||
placeholder={t('username_placeholder')}
|
||||
@@ -59,6 +60,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="password"
|
||||
type="password"
|
||||
label={t('username_label')}
|
||||
placeholder={t('password_placeholder')}
|
||||
@@ -71,7 +73,11 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
|
||||
</div>
|
||||
|
||||
<div className="form-footer">
|
||||
<button type="submit" className="btn btn-success btn-block" disabled={processing || !isValid}>
|
||||
<button
|
||||
data-testid="sign_in"
|
||||
type="submit"
|
||||
className="btn btn-success btn-block"
|
||||
disabled={processing || !isValid}>
|
||||
{t('sign_in')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
3
client/tests/constants.ts
Normal file
3
client/tests/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const ADMIN_USERNAME = 'admin';
|
||||
export const ADMIN_PASSWORD = 'superpassword';
|
||||
export const PORT = 3000;
|
||||
62
client/tests/e2e/dhcp.spec.ts
Normal file
62
client/tests/e2e/dhcp.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_PASSWORD, ADMIN_USERNAME } from '../constants';
|
||||
|
||||
const INTERFACE_NAME = 'en0';
|
||||
const RANGE_START = '192.168.1.100';
|
||||
const RANGE_END = '192.168.1.200';
|
||||
const SUBNET_MASK = '255.255.255.0';
|
||||
const LEASE_TIME = '86400';
|
||||
|
||||
test.describe('DHCP Configuration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login.html');
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.getByTestId('sign_in').click();
|
||||
await page.getByText('Settings', { exact: true }).click();
|
||||
await page.goto(`/#dhcp`);
|
||||
});
|
||||
|
||||
test('should select the correct DHCP interface', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
expect(await page.locator('select[name="interface_name"]').inputValue()).toBe(INTERFACE_NAME);
|
||||
});
|
||||
|
||||
test('should configure DHCP IPv4 settings correctly', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_gateway_ip').click();
|
||||
await page.getByTestId('v4_gateway_ip').fill('192.168.1.99');
|
||||
await page.getByTestId('v4_subnet_mask').click();
|
||||
await page.getByTestId('v4_subnet_mask').fill(SUBNET_MASK);
|
||||
await page.getByTestId('v4_range_start').click();
|
||||
await page.getByTestId('v4_range_start').fill(RANGE_START);
|
||||
await page.getByTestId('v4_range_end').click();
|
||||
await page.getByTestId('v4_range_end').fill(RANGE_END);
|
||||
await page.getByTestId('v4_lease_duration').click();
|
||||
await page.getByTestId('v4_lease_duration').fill(LEASE_TIME);
|
||||
await page.getByTestId('v4_save').click();
|
||||
});
|
||||
|
||||
test('should show error for invalid DHCP IPv4 range', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_range_start').click();
|
||||
await page.getByTestId('v4_range_start').fill(RANGE_END);
|
||||
await page.getByTestId('v4_range_end').click();
|
||||
await page.getByTestId('v4_range_end').fill(RANGE_START);
|
||||
await page.locator('.col-12').first().click();
|
||||
await page.getByText('Must be greater than range').click();
|
||||
|
||||
expect(await page.getByText('Must be greater than range').isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
test('should show error for invalid DHCP IPv4 address', async ({ page }) => {
|
||||
await page.getByTestId('interface_name').selectOption(INTERFACE_NAME);
|
||||
await page.getByTestId('v4_gateway_ip').click();
|
||||
await page.getByTestId('v4_gateway_ip').fill('192.168.1.200s');
|
||||
await page.getByText('Invalid IPv4 address').click();
|
||||
|
||||
expect(await page.getByText('Invalid IPv4 address').isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
13
client/tests/e2e/login.spec.ts
Normal file
13
client/tests/e2e/login.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { test } from '@playwright/test';
|
||||
import { ADMIN_PASSWORD, ADMIN_USERNAME } from '../constants';
|
||||
|
||||
test.describe('Login', () => {
|
||||
test('should successfully log in with valid credentials', async ({ page }) => {
|
||||
await page.goto(`/login.html`);
|
||||
await page.getByTestId('username').click();
|
||||
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||
await page.getByTestId('sign_in').click();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user