add DHCP form
This commit is contained in:
@@ -19,7 +19,6 @@ import {
|
|||||||
CHECK_TIMEOUT,
|
CHECK_TIMEOUT,
|
||||||
STATUS_RESPONSE,
|
STATUS_RESPONSE,
|
||||||
SETTINGS_NAMES,
|
SETTINGS_NAMES,
|
||||||
FORM_NAME,
|
|
||||||
MANUAL_UPDATE_LINK,
|
MANUAL_UPDATE_LINK,
|
||||||
DISABLE_PROTECTION_TIMINGS,
|
DISABLE_PROTECTION_TIMINGS,
|
||||||
} from '../helpers/constants';
|
} 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 findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
|
||||||
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
|
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());
|
dispatch(findActiveDhcpRequest());
|
||||||
try {
|
try {
|
||||||
const req = {
|
const req = {
|
||||||
interface: name,
|
interface: selectedInterface,
|
||||||
};
|
};
|
||||||
const activeDhcp = await apiClient.findActiveDhcp(req);
|
const activeDhcp = await apiClient.findActiveDhcp(req);
|
||||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||||
const { check, interface_name, interfaces } = getState().dhcp;
|
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 v4 = check?.v4 ?? { static_ip: {}, other_server: {} };
|
||||||
const v6 = check?.v6 ?? { other_server: {} };
|
const v6 = check?.v6 ?? { other_server: {} };
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { UINT32_RANGE } from '../../../helpers/constants';
|
import { UINT32_RANGE } from '../../../helpers/constants';
|
||||||
import {
|
import {
|
||||||
@@ -12,21 +11,10 @@ import {
|
|||||||
validateNotInRange,
|
validateNotInRange,
|
||||||
validateRequiredValue,
|
validateRequiredValue,
|
||||||
} from '../../../helpers/validators';
|
} from '../../../helpers/validators';
|
||||||
import { RootState } from '../../../initialState';
|
import { DhcpFormValues } from '.';
|
||||||
|
|
||||||
type FormValues = {
|
|
||||||
v4?: {
|
|
||||||
gateway_ip?: string;
|
|
||||||
subnet_mask?: string;
|
|
||||||
range_start?: string;
|
|
||||||
range_end?: string;
|
|
||||||
lease_duration?: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type FormDHCPv4Props = {
|
type FormDHCPv4Props = {
|
||||||
processingConfig?: boolean;
|
processingConfig?: boolean;
|
||||||
initialValues?: FormValues;
|
|
||||||
ipv4placeholders?: {
|
ipv4placeholders?: {
|
||||||
gateway_ip: string;
|
gateway_ip: string;
|
||||||
subnet_mask: string;
|
subnet_mask: string;
|
||||||
@@ -34,46 +22,28 @@ type FormDHCPv4Props = {
|
|||||||
range_end: string;
|
range_end: string;
|
||||||
lease_duration: string;
|
lease_duration: string;
|
||||||
};
|
};
|
||||||
onSubmit?: (data: FormValues) => Promise<void> | void;
|
interfaces: any;
|
||||||
}
|
onSubmit?: (data: DhcpFormValues) => Promise<void> | void;
|
||||||
|
};
|
||||||
|
|
||||||
const FormDHCPv4 = ({
|
const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }: FormDHCPv4Props) => {
|
||||||
processingConfig,
|
|
||||||
initialValues,
|
|
||||||
ipv4placeholders,
|
|
||||||
onSubmit
|
|
||||||
}: FormDHCPv4Props) => {
|
|
||||||
const { t } = useTranslation();
|
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 {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
watch,
|
watch,
|
||||||
} = useForm<FormValues>({
|
} = useFormContext<DhcpFormValues>();
|
||||||
mode: 'onChange',
|
|
||||||
defaultValues: {
|
const interfaceName = watch('interface_name');
|
||||||
v4: initialValues?.v4 || {
|
const isInterfaceIncludesIpv4 = interfaces?.[interfaceName]?.ipv4_addresses;
|
||||||
gateway_ip: '',
|
|
||||||
subnet_mask: '',
|
|
||||||
range_start: '',
|
|
||||||
range_end: '',
|
|
||||||
lease_duration: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const formValues = watch('v4');
|
const formValues = watch('v4');
|
||||||
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
||||||
|
|
||||||
const handleFormSubmit = async (data: FormValues) => {
|
const handleFormSubmit = async (data: DhcpFormValues) => {
|
||||||
|
// TODO handle submit
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
await onSubmit(data);
|
await onSubmit(data);
|
||||||
}
|
}
|
||||||
@@ -93,15 +63,13 @@ const FormDHCPv4 = ({
|
|||||||
{...register('v4.gateway_ip', {
|
{...register('v4.gateway_ip', {
|
||||||
validate: {
|
validate: {
|
||||||
ipv4: validateIpv4,
|
ipv4: validateIpv4,
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
|
||||||
notInRange: validateNotInRange,
|
notInRange: validateNotInRange,
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v4?.gateway_ip && (
|
{errors.v4?.gateway_ip && (
|
||||||
<div className="form__message form__message--error">
|
<div className="form__message form__message--error">{t(errors.v4.gateway_ip.message)}</div>
|
||||||
{t(errors.v4.gateway_ip.message)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -114,15 +82,13 @@ const FormDHCPv4 = ({
|
|||||||
disabled={!isInterfaceIncludesIpv4}
|
disabled={!isInterfaceIncludesIpv4}
|
||||||
{...register('v4.subnet_mask', {
|
{...register('v4.subnet_mask', {
|
||||||
validate: {
|
validate: {
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
|
||||||
subnet: validateGatewaySubnetMask,
|
subnet: validateGatewaySubnetMask,
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v4?.subnet_mask && (
|
{errors.v4?.subnet_mask && (
|
||||||
<div className="form__message form__message--error">
|
<div className="form__message form__message--error">{t(errors.v4.subnet_mask.message)}</div>
|
||||||
{t(errors.v4.subnet_mask.message)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +110,7 @@ const FormDHCPv4 = ({
|
|||||||
validate: {
|
validate: {
|
||||||
ipv4: validateIpv4,
|
ipv4: validateIpv4,
|
||||||
gateway: validateIpForGatewaySubnetMask,
|
gateway: validateIpForGatewaySubnetMask,
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v4?.range_start && (
|
{errors.v4?.range_start && (
|
||||||
@@ -165,7 +131,7 @@ const FormDHCPv4 = ({
|
|||||||
ipv4: validateIpv4,
|
ipv4: validateIpv4,
|
||||||
rangeEnd: validateIpv4RangeEnd,
|
rangeEnd: validateIpv4RangeEnd,
|
||||||
gateway: validateIpForGatewaySubnetMask,
|
gateway: validateIpForGatewaySubnetMask,
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v4?.range_end && (
|
{errors.v4?.range_end && (
|
||||||
@@ -189,8 +155,8 @@ const FormDHCPv4 = ({
|
|||||||
{...register('v4.lease_duration', {
|
{...register('v4.lease_duration', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
validate: {
|
validate: {
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v4?.lease_duration && (
|
{errors.v4?.lease_duration && (
|
||||||
@@ -206,8 +172,13 @@ const FormDHCPv4 = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standard"
|
className="btn btn-success btn-standard"
|
||||||
disabled={isSubmitting || processingConfig || !isInterfaceIncludesIpv4 || isEmptyConfig || Object.keys(errors).length > 0}
|
disabled={
|
||||||
>
|
isSubmitting ||
|
||||||
|
processingConfig ||
|
||||||
|
!isInterfaceIncludesIpv4 ||
|
||||||
|
isEmptyConfig ||
|
||||||
|
Object.keys(errors).length > 0
|
||||||
|
}>
|
||||||
{t('save_config')}
|
{t('save_config')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { UINT32_RANGE } from '../../../helpers/constants';
|
import { UINT32_RANGE } from '../../../helpers/constants';
|
||||||
import { validateIpv6, validateRequiredValue } from '../../../helpers/validators';
|
import { validateIpv6, validateRequiredValue } from '../../../helpers/validators';
|
||||||
import { RootState } from '../../../initialState';
|
import { DhcpFormValues } from '.';
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
v6?: {
|
v6?: {
|
||||||
@@ -13,49 +12,30 @@ type FormValues = {
|
|||||||
range_end?: string;
|
range_end?: string;
|
||||||
lease_duration?: number;
|
lease_duration?: number;
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
type FormDHCPv6Props = {
|
type FormDHCPv6Props = {
|
||||||
processingConfig?: boolean;
|
processingConfig?: boolean;
|
||||||
initialValues?: FormValues;
|
|
||||||
ipv6placeholders?: {
|
ipv6placeholders?: {
|
||||||
range_start: string;
|
range_start: string;
|
||||||
range_end: string;
|
range_end: string;
|
||||||
lease_duration: string;
|
lease_duration: string;
|
||||||
};
|
};
|
||||||
onSubmit?: (data: FormValues) => Promise<void> | void;
|
interfaces: any;
|
||||||
}
|
onSubmit?: (data: DhcpFormValues) => Promise<void> | void;
|
||||||
|
};
|
||||||
|
|
||||||
const FormDHCPv6 = ({
|
const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }: FormDHCPv6Props) => {
|
||||||
processingConfig,
|
|
||||||
initialValues,
|
|
||||||
ipv6placeholders,
|
|
||||||
onSubmit,
|
|
||||||
}: FormDHCPv6Props) => {
|
|
||||||
const { t } = useTranslation();
|
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 {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
watch,
|
watch,
|
||||||
} = useForm<FormValues>({
|
} = useFormContext<DhcpFormValues>();
|
||||||
mode: 'onChange',
|
|
||||||
defaultValues: {
|
const interfaceName = watch('interface_name');
|
||||||
v6: initialValues?.v6 || {
|
const isInterfaceIncludesIpv6 = interfaces?.[interfaceName]?.ipv6_addresses;
|
||||||
range_start: '',
|
|
||||||
range_end: '',
|
|
||||||
lease_duration: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const formValues = watch('v6');
|
const formValues = watch('v6');
|
||||||
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
|
||||||
@@ -85,8 +65,9 @@ const FormDHCPv6 = ({
|
|||||||
{...register('v6.range_start', {
|
{...register('v6.range_start', {
|
||||||
validate: {
|
validate: {
|
||||||
ipv6: validateIpv6,
|
ipv6: validateIpv6,
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) =>
|
||||||
}
|
isEmptyConfig ? undefined : validateRequiredValue(value),
|
||||||
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v6?.range_start && (
|
{errors.v6?.range_start && (
|
||||||
@@ -105,8 +86,9 @@ const FormDHCPv6 = ({
|
|||||||
{...register('v6.range_end', {
|
{...register('v6.range_end', {
|
||||||
validate: {
|
validate: {
|
||||||
ipv6: validateIpv6,
|
ipv6: validateIpv6,
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) =>
|
||||||
}
|
isEmptyConfig ? undefined : validateRequiredValue(value),
|
||||||
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v6?.range_end && (
|
{errors.v6?.range_end && (
|
||||||
@@ -133,14 +115,12 @@ const FormDHCPv6 = ({
|
|||||||
{...register('v6.lease_duration', {
|
{...register('v6.lease_duration', {
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
validate: {
|
validate: {
|
||||||
required: (value) => isEmptyConfig ? undefined : validateRequiredValue(value),
|
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
|
||||||
}
|
},
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.v6?.lease_duration && (
|
{errors.v6?.lease_duration && (
|
||||||
<div className="form__message form__message--error">
|
<div className="form__message form__message--error">{t(errors.v6.lease_duration.message)}</div>
|
||||||
{t(errors.v6.lease_duration.message)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,8 +129,13 @@ const FormDHCPv6 = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-success btn-standard"
|
className="btn btn-success btn-standard"
|
||||||
disabled={isSubmitting || processingConfig || !isInterfaceIncludesIpv6 || isEmptyConfig || Object.keys(errors).length > 0}
|
disabled={
|
||||||
>
|
isSubmitting ||
|
||||||
|
processingConfig ||
|
||||||
|
!isInterfaceIncludesIpv6 ||
|
||||||
|
isEmptyConfig ||
|
||||||
|
Object.keys(errors).length > 0
|
||||||
|
}>
|
||||||
{t('save_config')}
|
{t('save_config')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { validateRequiredValue } from '../../../helpers/validators';
|
import { validateRequiredValue } from '../../../helpers/validators';
|
||||||
import { RootState } from '../../../initialState';
|
import { RootState } from '../../../initialState';
|
||||||
|
import { DhcpFormValues } from '.';
|
||||||
type FormValues = {
|
|
||||||
interface_name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type InterfacesProps = {
|
|
||||||
initialValues?: {
|
|
||||||
interface_name: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderInterfaces = (interfaces: any) =>
|
const renderInterfaces = (interfaces: any) =>
|
||||||
Object.keys(interfaces).map((item) => {
|
Object.keys(interfaces).map((item) => {
|
||||||
@@ -82,19 +73,15 @@ const renderInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: R
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Interfaces = ({ initialValues }: InterfacesProps) => {
|
const Interfaces = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
watch,
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<FormValues>({
|
} = useFormContext<DhcpFormValues>();
|
||||||
mode: 'onChange',
|
|
||||||
defaultValues: initialValues,
|
const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp);
|
||||||
});
|
|
||||||
|
|
||||||
const interface_name = watch('interface_name');
|
const interface_name = watch('interface_name');
|
||||||
|
|
||||||
@@ -116,24 +103,22 @@ const Interfaces = ({ initialValues }: InterfacesProps) => {
|
|||||||
disabled={enabled}
|
disabled={enabled}
|
||||||
{...register('interface_name', {
|
{...register('interface_name', {
|
||||||
validate: validateRequiredValue,
|
validate: validateRequiredValue,
|
||||||
})}
|
})}>
|
||||||
>
|
|
||||||
<option value="" disabled={enabled}>
|
<option value="" disabled={enabled}>
|
||||||
{t('dhcp_interface_select')}
|
{t('dhcp_interface_select')}
|
||||||
</option>
|
</option>
|
||||||
{renderInterfaces(interfaces)}
|
{renderInterfaces(interfaces)}
|
||||||
</select>
|
</select>
|
||||||
{errors.interface_name && (
|
{errors.interface_name && (
|
||||||
<div className="form__message form__message--error">
|
<div className="form__message form__message--error">{t(errors.interface_name.message)}</div>
|
||||||
{t(errors.interface_name.message)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{interfaceValue && renderInterfaceValues({
|
{interfaceValue &&
|
||||||
gateway_ip: interfaceValue.gateway_ip,
|
renderInterfaceValues({
|
||||||
hardware_address: interfaceValue.hardware_address,
|
gateway_ip: interfaceValue.gateway_ip,
|
||||||
ip_addresses: interfaceValue.ip_addresses
|
hardware_address: interfaceValue.hardware_address,
|
||||||
})}
|
ip_addresses: interfaceValue.ip_addresses,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { destroy } from 'redux-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { DHCP_DESCRIPTION_PLACEHOLDERS, DHCP_FORM_NAMES, STATUS_RESPONSE, FORM_NAME } from '../../../helpers/constants';
|
import { DHCP_DESCRIPTION_PLACEHOLDERS, STATUS_RESPONSE } from '../../../helpers/constants';
|
||||||
|
|
||||||
import Leases from './Leases';
|
import Leases from './Leases';
|
||||||
|
|
||||||
@@ -40,6 +40,36 @@ import {
|
|||||||
import './index.css';
|
import './index.css';
|
||||||
import { RootState } from '../../../initialState';
|
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 Dhcp = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -65,14 +95,21 @@ const Dhcp = () => {
|
|||||||
modalType,
|
modalType,
|
||||||
} = useSelector((state: RootState) => state.dhcp, shallowEqual);
|
} = useSelector((state: RootState) => state.dhcp, shallowEqual);
|
||||||
|
|
||||||
const interface_name = useSelector(
|
const methods = useForm<DhcpFormValues>({
|
||||||
(state: RootState) => state.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
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(
|
const isInterfaceIncludesIpv4 = useSelector(
|
||||||
(state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
|
(state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
|
||||||
);
|
);
|
||||||
|
const ipv4Config = watch('v4');
|
||||||
const dhcp = useSelector((state: RootState) => state.form[FORM_NAME.DHCPv4], shallowEqual);
|
|
||||||
|
|
||||||
const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4);
|
const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4);
|
||||||
const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6);
|
const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6);
|
||||||
@@ -87,6 +124,16 @@ const Dhcp = () => {
|
|||||||
}
|
}
|
||||||
}, [dhcp_available]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
||||||
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
||||||
@@ -105,7 +152,11 @@ const Dhcp = () => {
|
|||||||
const clear = () => {
|
const clear = () => {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if (window.confirm(t('dhcp_reset'))) {
|
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(resetDhcp());
|
||||||
dispatch(getDhcpStatus());
|
dispatch(getDhcpStatus());
|
||||||
}
|
}
|
||||||
@@ -170,9 +221,6 @@ const Dhcp = () => {
|
|||||||
|
|
||||||
const toggleModal = () => dispatch(toggleLeaseModal());
|
const toggleModal = () => dispatch(toggleLeaseModal());
|
||||||
|
|
||||||
const initialV4 = enteredSomeV4Value ? v4 : {};
|
|
||||||
const initialV6 = enteredSomeV6Value ? v6 : {};
|
|
||||||
|
|
||||||
if (processing || processingInterfaces) {
|
if (processing || processingInterfaces) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
@@ -193,15 +241,13 @@ const Dhcp = () => {
|
|||||||
|
|
||||||
const toggleDhcpButton = getToggleDhcpButton();
|
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(
|
const disabledLeasesButton = Boolean(
|
||||||
dhcp?.syncErrors || !isInterfaceIncludesIpv4 || isEmptyConfig || processingConfig || !inputtedIPv4values,
|
!isInterfaceIncludesIpv4 || isEmptyConfig || processingConfig || !inputtedIPv4values,
|
||||||
);
|
);
|
||||||
const cidr = inputtedIPv4values
|
const cidr = inputtedIPv4values ? `${ipv4Config.gateway_ip}/${subnetMaskToBitMask(ipv4Config.subnet_mask)}` : '';
|
||||||
? `${dhcp?.values?.v4?.gateway_ip}/${subnetMaskToBitMask(dhcp?.values?.v4?.subnet_mask)}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -239,29 +285,32 @@ const Dhcp = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Interfaces initialValues={{ interface_name: interfaceName }} />
|
<FormProvider {...methods}>
|
||||||
|
<Interfaces />
|
||||||
|
|
||||||
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
|
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
|
||||||
<div>
|
<div>
|
||||||
<FormDHCPv4
|
<FormDHCPv4
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
initialValues={{ v4: initialV4 }}
|
processingConfig={processingConfig}
|
||||||
processingConfig={processingConfig}
|
ipv4placeholders={ipv4placeholders}
|
||||||
ipv4placeholders={ipv4placeholders}
|
interfaces={interfaces}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card title={t('dhcp_ipv6_settings')} bodyType="card-body box-body--settings">
|
||||||
|
<div>
|
||||||
|
<FormDHCPv6
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
processingConfig={processingConfig}
|
||||||
|
ipv6placeholders={ipv6placeholders}
|
||||||
|
interfaces={interfaces}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</FormProvider>
|
||||||
|
|
||||||
<Card title={t('dhcp_ipv6_settings')} bodyType="card-body box-body--settings">
|
|
||||||
<div>
|
|
||||||
<FormDHCPv6
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
initialValues={{ v6: initialV6 }}
|
|
||||||
processingConfig={processingConfig}
|
|
||||||
ipv6placeholders={ipv6placeholders}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
{enabled && (
|
{enabled && (
|
||||||
<Card title={t('dhcp_leases')} bodyType="card-body box-body--settings">
|
<Card title={t('dhcp_leases')} bodyType="card-body box-body--settings">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@@ -283,7 +332,7 @@ const Dhcp = () => {
|
|||||||
processingDeleting={processingDeleting}
|
processingDeleting={processingDeleting}
|
||||||
processingUpdating={processingUpdating}
|
processingUpdating={processingUpdating}
|
||||||
cidr={cidr}
|
cidr={cidr}
|
||||||
gatewayIp={dhcp?.values?.v4?.gateway_ip}
|
gatewayIp={ipv4Config.gateway_ip}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="btn-list mt-2">
|
<div className="btn-list mt-2">
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ export const Form = ({
|
|||||||
|
|
||||||
if (JSON.stringify(previousValues) !== JSON.stringify(watchedValues)) {
|
if (JSON.stringify(previousValues) !== JSON.stringify(watchedValues)) {
|
||||||
// TODO onChange TLS config validation
|
// TODO onChange TLS config validation
|
||||||
console.log('TLS config validation');
|
|
||||||
previousValuesRef.current = watchedValues;
|
previousValuesRef.current = watchedValues;
|
||||||
}
|
}
|
||||||
}, [watchedValues]);
|
}, [watchedValues]);
|
||||||
|
|||||||
@@ -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<HTMLInputElement>) => {
|
|
||||||
if (normalizeOnBlur) {
|
|
||||||
onChange(normalizeOnBlur(event.target.value));
|
|
||||||
}
|
|
||||||
onBlur();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="input-group">
|
|
||||||
<input
|
|
||||||
id={id}
|
|
||||||
placeholder={placeholder}
|
|
||||||
type={type}
|
|
||||||
className={className}
|
|
||||||
disabled={disabled}
|
|
||||||
autoComplete={autoComplete}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => onChange(e.target.value)}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
{isActionAvailable && (
|
|
||||||
<span className="input-group-append">
|
|
||||||
<button type="button" className="btn btn-secondary btn-icon btn-icon--green" onClick={removeField}>
|
|
||||||
<svg className="icon icon--24">
|
|
||||||
<use xlinkHref="#cross" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -7,26 +7,23 @@ interface Props extends ComponentProps<'textarea'> {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(
|
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(({ name, label, className, error, ...rest }, ref) => (
|
||||||
({ name, label, className, error, onClick, ...rest }, ref) => (
|
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
{label && (
|
||||||
{label && (
|
<label className="form__label" htmlFor={name}>
|
||||||
<label className="form__label" htmlFor={name}>
|
{label}
|
||||||
{label}
|
</label>
|
||||||
</label>
|
)}
|
||||||
|
<textarea
|
||||||
|
className={clsx(
|
||||||
|
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
<textarea
|
ref={ref}
|
||||||
onClick={onClick}
|
{...rest}
|
||||||
className={clsx(
|
/>
|
||||||
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
{error && <div className="form__message form__message--error">{error}</div>}
|
||||||
className,
|
</div>
|
||||||
)}
|
));
|
||||||
ref={ref}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
{error && <div className="form__message form__message--error">{error}</div>}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Textarea.displayName = 'Textarea';
|
Textarea.displayName = 'Textarea';
|
||||||
|
|||||||
@@ -757,12 +757,9 @@ type NestedObject = {
|
|||||||
order: number;
|
order: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getObjectKeysSorted = <
|
export const getObjectKeysSorted = <T extends Record<string, NestedObject>, K extends keyof NestedObject>(
|
||||||
T extends Record<string, NestedObject>,
|
|
||||||
K extends keyof NestedObject
|
|
||||||
>(
|
|
||||||
object: T,
|
object: T,
|
||||||
sortKey: K
|
sortKey: K,
|
||||||
): string[] => {
|
): string[] => {
|
||||||
return Object.entries(object)
|
return Object.entries(object)
|
||||||
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))
|
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))
|
||||||
|
|||||||
Reference in New Issue
Block a user