diff --git a/client/src/actions/index.tsx b/client/src/actions/index.tsx index c069f6ad..d122e215 100644 --- a/client/src/actions/index.tsx +++ b/client/src/actions/index.tsx @@ -19,7 +19,6 @@ import { CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, - FORM_NAME, MANUAL_UPDATE_LINK, DISABLE_PROTECTION_TIMINGS, } from '../helpers/constants'; @@ -511,16 +510,15 @@ export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST'); export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS'); export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE'); -export const findActiveDhcp = (name: any) => async (dispatch: any, getState: any) => { +export const findActiveDhcp = (selectedInterface: any) => async (dispatch: any, getState: any) => { dispatch(findActiveDhcpRequest()); try { const req = { - interface: name, + interface: selectedInterface, }; const activeDhcp = await apiClient.findActiveDhcp(req); dispatch(findActiveDhcpSuccess(activeDhcp)); const { check, interface_name, interfaces } = getState().dhcp; - const selectedInterface = getState().form[FORM_NAME.DHCP_INTERFACES].values.interface_name; const v4 = check?.v4 ?? { static_ip: {}, other_server: {} }; const v6 = check?.v6 ?? { other_server: {} }; diff --git a/client/src/components/Settings/Dhcp/FormDHCPv4.tsx b/client/src/components/Settings/Dhcp/FormDHCPv4.tsx index 9ca03d5d..b10f7b4d 100644 --- a/client/src/components/Settings/Dhcp/FormDHCPv4.tsx +++ b/client/src/components/Settings/Dhcp/FormDHCPv4.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import { useForm } from 'react-hook-form'; +import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; import { UINT32_RANGE } from '../../../helpers/constants'; import { @@ -12,21 +11,10 @@ import { validateNotInRange, validateRequiredValue, } from '../../../helpers/validators'; -import { RootState } from '../../../initialState'; - -type FormValues = { - v4?: { - gateway_ip?: string; - subnet_mask?: string; - range_start?: string; - range_end?: string; - lease_duration?: number; - }; -} +import { DhcpFormValues } from '.'; type FormDHCPv4Props = { processingConfig?: boolean; - initialValues?: FormValues; ipv4placeholders?: { gateway_ip: string; subnet_mask: string; @@ -34,46 +22,28 @@ type FormDHCPv4Props = { range_end: string; lease_duration: string; }; - onSubmit?: (data: FormValues) => Promise | void; -} + interfaces: any; + onSubmit?: (data: DhcpFormValues) => Promise | void; +}; -const FormDHCPv4 = ({ - processingConfig, - initialValues, - ipv4placeholders, - onSubmit -}: FormDHCPv4Props) => { +const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }: FormDHCPv4Props) => { const { t } = useTranslation(); - const interfaces = useSelector((state: RootState) => state.form.DHCP_INTERFACES); - const interface_name = interfaces?.values?.interface_name; - - const isInterfaceIncludesIpv4 = useSelector( - (state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses, - ); - const { register, handleSubmit, formState: { errors, isSubmitting }, watch, - } = useForm({ - mode: 'onChange', - defaultValues: { - v4: initialValues?.v4 || { - gateway_ip: '', - subnet_mask: '', - range_start: '', - range_end: '', - lease_duration: 0, - }, - }, - }); + } = useFormContext(); + + const interfaceName = watch('interface_name'); + const isInterfaceIncludesIpv4 = interfaces?.[interfaceName]?.ipv4_addresses; const formValues = watch('v4'); const isEmptyConfig = !Object.values(formValues || {}).some(Boolean); - const handleFormSubmit = async (data: FormValues) => { + const handleFormSubmit = async (data: DhcpFormValues) => { + // TODO handle submit if (onSubmit) { await onSubmit(data); } @@ -93,15 +63,13 @@ const FormDHCPv4 = ({ {...register('v4.gateway_ip', { validate: { ipv4: validateIpv4, - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), + required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)), notInRange: validateNotInRange, - } + }, })} /> {errors.v4?.gateway_ip && ( -
- {t(errors.v4.gateway_ip.message)} -
+
{t(errors.v4.gateway_ip.message)}
)} @@ -114,15 +82,13 @@ const FormDHCPv4 = ({ disabled={!isInterfaceIncludesIpv4} {...register('v4.subnet_mask', { validate: { - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), + required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)), subnet: validateGatewaySubnetMask, - } + }, })} /> {errors.v4?.subnet_mask && ( -
- {t(errors.v4.subnet_mask.message)} -
+
{t(errors.v4.subnet_mask.message)}
)} @@ -144,7 +110,7 @@ const FormDHCPv4 = ({ validate: { ipv4: validateIpv4, gateway: validateIpForGatewaySubnetMask, - } + }, })} /> {errors.v4?.range_start && ( @@ -165,7 +131,7 @@ const FormDHCPv4 = ({ ipv4: validateIpv4, rangeEnd: validateIpv4RangeEnd, gateway: validateIpForGatewaySubnetMask, - } + }, })} /> {errors.v4?.range_end && ( @@ -189,8 +155,8 @@ const FormDHCPv4 = ({ {...register('v4.lease_duration', { valueAsNumber: true, validate: { - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), - } + required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)), + }, })} /> {errors.v4?.lease_duration && ( @@ -206,8 +172,13 @@ const FormDHCPv4 = ({ diff --git a/client/src/components/Settings/Dhcp/FormDHCPv6.tsx b/client/src/components/Settings/Dhcp/FormDHCPv6.tsx index 36a27120..a3b04e6c 100644 --- a/client/src/components/Settings/Dhcp/FormDHCPv6.tsx +++ b/client/src/components/Settings/Dhcp/FormDHCPv6.tsx @@ -1,11 +1,10 @@ import React from 'react'; -import { useForm } from 'react-hook-form'; +import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; import { UINT32_RANGE } from '../../../helpers/constants'; import { validateIpv6, validateRequiredValue } from '../../../helpers/validators'; -import { RootState } from '../../../initialState'; +import { DhcpFormValues } from '.'; type FormValues = { v6?: { @@ -13,49 +12,30 @@ type FormValues = { range_end?: string; lease_duration?: number; }; -} +}; type FormDHCPv6Props = { processingConfig?: boolean; - initialValues?: FormValues; ipv6placeholders?: { range_start: string; range_end: string; lease_duration: string; }; - onSubmit?: (data: FormValues) => Promise | void; -} + interfaces: any; + onSubmit?: (data: DhcpFormValues) => Promise | void; +}; -const FormDHCPv6 = ({ - processingConfig, - initialValues, - ipv6placeholders, - onSubmit, -}: FormDHCPv6Props) => { +const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }: FormDHCPv6Props) => { const { t } = useTranslation(); - - const interfaces = useSelector((state: RootState) => state.form.DHCP_INTERFACES); - const interface_name = interfaces?.values?.interface_name; - - const isInterfaceIncludesIpv6 = useSelector( - (state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv6_addresses, - ); - const { register, handleSubmit, formState: { errors, isSubmitting }, watch, - } = useForm({ - mode: 'onChange', - defaultValues: { - v6: initialValues?.v6 || { - range_start: '', - range_end: '', - lease_duration: 0, - }, - }, - }); + } = useFormContext(); + + const interfaceName = watch('interface_name'); + const isInterfaceIncludesIpv6 = interfaces?.[interfaceName]?.ipv6_addresses; const formValues = watch('v6'); const isEmptyConfig = !Object.values(formValues || {}).some(Boolean); @@ -85,8 +65,9 @@ const FormDHCPv6 = ({ {...register('v6.range_start', { validate: { ipv6: validateIpv6, - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), - } + required: (value) => + isEmptyConfig ? undefined : validateRequiredValue(value), + }, })} /> {errors.v6?.range_start && ( @@ -105,8 +86,9 @@ const FormDHCPv6 = ({ {...register('v6.range_end', { validate: { ipv6: validateIpv6, - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), - } + required: (value) => + isEmptyConfig ? undefined : validateRequiredValue(value), + }, })} /> {errors.v6?.range_end && ( @@ -133,14 +115,12 @@ const FormDHCPv6 = ({ {...register('v6.lease_duration', { valueAsNumber: true, validate: { - required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value), - } + required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)), + }, })} /> {errors.v6?.lease_duration && ( -
- {t(errors.v6.lease_duration.message)} -
+
{t(errors.v6.lease_duration.message)}
)} @@ -149,8 +129,13 @@ const FormDHCPv6 = ({ diff --git a/client/src/components/Settings/Dhcp/Interfaces.tsx b/client/src/components/Settings/Dhcp/Interfaces.tsx index 83a3718a..ac2d6b02 100644 --- a/client/src/components/Settings/Dhcp/Interfaces.tsx +++ b/client/src/components/Settings/Dhcp/Interfaces.tsx @@ -1,20 +1,11 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { useForm } from 'react-hook-form'; +import { useFormContext } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import { validateRequiredValue } from '../../../helpers/validators'; import { RootState } from '../../../initialState'; - -type FormValues = { - interface_name: string; -}; - -type InterfacesProps = { - initialValues?: { - interface_name: string; - }; -}; +import { DhcpFormValues } from '.'; const renderInterfaces = (interfaces: any) => Object.keys(interfaces).map((item) => { @@ -82,19 +73,15 @@ const renderInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: R ); -const Interfaces = ({ initialValues }: InterfacesProps) => { +const Interfaces = () => { const { t } = useTranslation(); - - const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp); - const { register, watch, formState: { errors }, - } = useForm({ - mode: 'onChange', - defaultValues: initialValues, - }); + } = useFormContext(); + + const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp); const interface_name = watch('interface_name'); @@ -116,24 +103,22 @@ const Interfaces = ({ initialValues }: InterfacesProps) => { disabled={enabled} {...register('interface_name', { validate: validateRequiredValue, - })} - > + })}> {renderInterfaces(interfaces)} {errors.interface_name && ( -
- {t(errors.interface_name.message)} -
+
{t(errors.interface_name.message)}
)} - {interfaceValue && renderInterfaceValues({ - gateway_ip: interfaceValue.gateway_ip, - hardware_address: interfaceValue.hardware_address, - ip_addresses: interfaceValue.ip_addresses - })} + {interfaceValue && + renderInterfaceValues({ + gateway_ip: interfaceValue.gateway_ip, + hardware_address: interfaceValue.hardware_address, + ip_addresses: interfaceValue.ip_addresses, + })} ); }; diff --git a/client/src/components/Settings/Dhcp/index.tsx b/client/src/components/Settings/Dhcp/index.tsx index 9a323923..c978d941 100644 --- a/client/src/components/Settings/Dhcp/index.tsx +++ b/client/src/components/Settings/Dhcp/index.tsx @@ -4,8 +4,8 @@ import { Trans, useTranslation } from 'react-i18next'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import classNames from 'classnames'; -import { destroy } from 'redux-form'; -import { DHCP_DESCRIPTION_PLACEHOLDERS, DHCP_FORM_NAMES, STATUS_RESPONSE, FORM_NAME } from '../../../helpers/constants'; +import { FormProvider, useForm } from 'react-hook-form'; +import { DHCP_DESCRIPTION_PLACEHOLDERS, STATUS_RESPONSE } from '../../../helpers/constants'; import Leases from './Leases'; @@ -40,6 +40,36 @@ import { import './index.css'; import { RootState } from '../../../initialState'; +export type DhcpFormValues = { + v4?: { + gateway_ip?: string; + subnet_mask?: string; + range_start?: string; + range_end?: string; + lease_duration?: number; + }; + v6?: { + range_start?: string; + range_end?: string; + lease_duration?: number; + }; + interface_name?: string; +}; + +const DEFAULT_V4_VALUES = { + gateway_ip: '', + subnet_mask: '', + range_start: '', + range_end: '', + lease_duration: 0, +}; + +const DEFAULT_V6_VALUES = { + range_start: '', + range_end: '', + lease_duration: 0, +}; + const Dhcp = () => { const { t } = useTranslation(); const dispatch = useDispatch(); @@ -65,14 +95,21 @@ const Dhcp = () => { modalType, } = useSelector((state: RootState) => state.dhcp, shallowEqual); - const interface_name = useSelector( - (state: RootState) => state.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name, - ); + const methods = useForm({ + mode: 'onChange', + defaultValues: { + v4: v4 || DEFAULT_V4_VALUES, + v6: v6 || DEFAULT_V6_VALUES, + interface_name: interfaceName || '', + }, + }); + const { watch, reset } = methods; + + const interface_name = watch('interface_name'); const isInterfaceIncludesIpv4 = useSelector( (state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses, ); - - const dhcp = useSelector((state: RootState) => state.form[FORM_NAME.DHCPv4], shallowEqual); + const ipv4Config = watch('v4'); const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4); const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6); @@ -87,6 +124,16 @@ const Dhcp = () => { } }, [dhcp_available]); + useEffect(() => { + if (v4 || v6 || interfaceName) { + reset({ + v4: v4 || DEFAULT_V4_VALUES, + v6: v6 || DEFAULT_V6_VALUES, + interface_name: interfaceName || '', + }); + } + }, [v4, v6, interfaceName, reset]); + useEffect(() => { const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? []; const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? []; @@ -105,7 +152,11 @@ const Dhcp = () => { const clear = () => { // eslint-disable-next-line no-alert if (window.confirm(t('dhcp_reset'))) { - Object.values(DHCP_FORM_NAMES).forEach((formName: any) => dispatch(destroy(formName))); + reset({ + v4: DEFAULT_V4_VALUES, + v6: DEFAULT_V6_VALUES, + interface_name: '', + }); dispatch(resetDhcp()); dispatch(getDhcpStatus()); } @@ -170,9 +221,6 @@ const Dhcp = () => { const toggleModal = () => dispatch(toggleLeaseModal()); - const initialV4 = enteredSomeV4Value ? v4 : {}; - const initialV6 = enteredSomeV6Value ? v6 : {}; - if (processing || processingInterfaces) { return ; } @@ -193,15 +241,13 @@ const Dhcp = () => { const toggleDhcpButton = getToggleDhcpButton(); - const inputtedIPv4values = dhcp?.values?.v4?.gateway_ip && dhcp?.values?.v4?.subnet_mask; + const inputtedIPv4values = ipv4Config.gateway_ip && ipv4Config.subnet_mask; - const isEmptyConfig = !Object.values(dhcp?.values?.v4 ?? {}).some(Boolean); + const isEmptyConfig = !Object.values(ipv4Config).some(Boolean); const disabledLeasesButton = Boolean( - dhcp?.syncErrors || !isInterfaceIncludesIpv4 || isEmptyConfig || processingConfig || !inputtedIPv4values, + !isInterfaceIncludesIpv4 || isEmptyConfig || processingConfig || !inputtedIPv4values, ); - const cidr = inputtedIPv4values - ? `${dhcp?.values?.v4?.gateway_ip}/${subnetMaskToBitMask(dhcp?.values?.v4?.subnet_mask)}` - : ''; + const cidr = inputtedIPv4values ? `${ipv4Config.gateway_ip}/${subnetMaskToBitMask(ipv4Config.subnet_mask)}` : ''; return ( <> @@ -239,29 +285,32 @@ const Dhcp = () => { )} - + + - -
- -
-
+ +
+ +
+
+ + +
+ +
+
+
- -
- -
-
{enabled && (
@@ -283,7 +332,7 @@ const Dhcp = () => { processingDeleting={processingDeleting} processingUpdating={processingUpdating} cidr={cidr} - gatewayIp={dhcp?.values?.v4?.gateway_ip} + gatewayIp={ipv4Config.gateway_ip} />
diff --git a/client/src/components/Settings/Encryption/Form.tsx b/client/src/components/Settings/Encryption/Form.tsx index babcbec9..cfbd9125 100644 --- a/client/src/components/Settings/Encryption/Form.tsx +++ b/client/src/components/Settings/Encryption/Form.tsx @@ -181,7 +181,6 @@ export const Form = ({ if (JSON.stringify(previousValues) !== JSON.stringify(watchedValues)) { // TODO onChange TLS config validation - console.log('TLS config validation'); previousValuesRef.current = watchedValues; } }, [watchedValues]); diff --git a/client/src/components/ui/Controls/InputGroup.tsx b/client/src/components/ui/Controls/InputGroup.tsx deleted file mode 100644 index f0931765..00000000 --- a/client/src/components/ui/Controls/InputGroup.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; - -type Props = { - id?: string; - className?: string; - placeholder?: string; - type?: string; - disabled?: boolean; - autoComplete?: string; - isActionAvailable?: boolean; - removeField?: () => void; - normalizeOnBlur?: (value: string) => string; - value: string; - onChange: (value: string) => void; - onBlur: () => void; -}; - -export const InputGroup = ({ - id, - className, - placeholder, - type, - disabled, - autoComplete, - isActionAvailable, - removeField, - normalizeOnBlur, - value, - onChange, - onBlur, -}: Props) => { - const handleBlur = (event: React.FocusEvent) => { - if (normalizeOnBlur) { - onChange(normalizeOnBlur(event.target.value)); - } - onBlur(); - }; - - return ( -
- onChange(e.target.value)} - onBlur={handleBlur} - /> - {isActionAvailable && ( - - - - )} -
- ); -}; diff --git a/client/src/components/ui/Controls/Textarea.tsx b/client/src/components/ui/Controls/Textarea.tsx index 3c73a19b..002c1348 100644 --- a/client/src/components/ui/Controls/Textarea.tsx +++ b/client/src/components/ui/Controls/Textarea.tsx @@ -7,26 +7,23 @@ interface Props extends ComponentProps<'textarea'> { error?: string; } -export const Textarea = forwardRef( - ({ name, label, className, error, onClick, ...rest }, ref) => ( -
- {label && ( - +export const Textarea = forwardRef(({ name, label, className, error, ...rest }, ref) => ( +
+ {label && ( + + )} +