add DHCP form
This commit is contained in:
@@ -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: {} };
|
||||
|
||||
|
||||
@@ -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> | void;
|
||||
}
|
||||
interfaces: any;
|
||||
onSubmit?: (data: DhcpFormValues) => Promise<void> | 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<FormValues>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
v4: initialValues?.v4 || {
|
||||
gateway_ip: '',
|
||||
subnet_mask: '',
|
||||
range_start: '',
|
||||
range_end: '',
|
||||
lease_duration: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
} = useFormContext<DhcpFormValues>();
|
||||
|
||||
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 && (
|
||||
<div className="form__message form__message--error">
|
||||
{t(errors.v4.gateway_ip.message)}
|
||||
</div>
|
||||
<div className="form__message form__message--error">{t(errors.v4.gateway_ip.message)}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 && (
|
||||
<div className="form__message form__message--error">
|
||||
{t(errors.v4.subnet_mask.message)}
|
||||
</div>
|
||||
<div className="form__message form__message--error">{t(errors.v4.subnet_mask.message)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 = ({
|
||||
<button
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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> | void;
|
||||
}
|
||||
interfaces: any;
|
||||
onSubmit?: (data: DhcpFormValues) => Promise<void> | 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<FormValues>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
v6: initialValues?.v6 || {
|
||||
range_start: '',
|
||||
range_end: '',
|
||||
lease_duration: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
} = useFormContext<DhcpFormValues>();
|
||||
|
||||
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 && (
|
||||
<div className="form__message form__message--error">
|
||||
{t(errors.v6.lease_duration.message)}
|
||||
</div>
|
||||
<div className="form__message form__message--error">{t(errors.v6.lease_duration.message)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,8 +129,13 @@ const FormDHCPv6 = ({
|
||||
<button
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
);
|
||||
|
||||
const Interfaces = ({ initialValues }: InterfacesProps) => {
|
||||
const Interfaces = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp);
|
||||
|
||||
const {
|
||||
register,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
} = useFormContext<DhcpFormValues>();
|
||||
|
||||
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,
|
||||
})}
|
||||
>
|
||||
})}>
|
||||
<option value="" disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</select>
|
||||
{errors.interface_name && (
|
||||
<div className="form__message form__message--error">
|
||||
{t(errors.interface_name.message)}
|
||||
</div>
|
||||
<div className="form__message form__message--error">{t(errors.interface_name.message)}</div>
|
||||
)}
|
||||
</div>
|
||||
{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,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<DhcpFormValues>({
|
||||
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 <Loading />;
|
||||
}
|
||||
@@ -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 = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Interfaces initialValues={{ interface_name: interfaceName }} />
|
||||
<FormProvider {...methods}>
|
||||
<Interfaces />
|
||||
|
||||
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
|
||||
<div>
|
||||
<FormDHCPv4
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={{ v4: initialV4 }}
|
||||
processingConfig={processingConfig}
|
||||
ipv4placeholders={ipv4placeholders}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
|
||||
<div>
|
||||
<FormDHCPv4
|
||||
onSubmit={handleSubmit}
|
||||
processingConfig={processingConfig}
|
||||
ipv4placeholders={ipv4placeholders}
|
||||
interfaces={interfaces}
|
||||
/>
|
||||
</div>
|
||||
</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 && (
|
||||
<Card title={t('dhcp_leases')} bodyType="card-body box-body--settings">
|
||||
<div className="row">
|
||||
@@ -283,7 +332,7 @@ const Dhcp = () => {
|
||||
processingDeleting={processingDeleting}
|
||||
processingUpdating={processingUpdating}
|
||||
cidr={cidr}
|
||||
gatewayIp={dhcp?.values?.v4?.gateway_ip}
|
||||
gatewayIp={ipv4Config.gateway_ip}
|
||||
/>
|
||||
|
||||
<div className="btn-list mt-2">
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ name, label, className, error, onClick, ...rest }, ref) => (
|
||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||
{label && (
|
||||
<label className="form__label" htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(({ name, label, className, error, ...rest }, ref) => (
|
||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||
{label && (
|
||||
<label className="form__label" htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<textarea
|
||||
className={clsx(
|
||||
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
||||
className,
|
||||
)}
|
||||
<textarea
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
/>
|
||||
{error && <div className="form__message form__message--error">{error}</div>}
|
||||
</div>
|
||||
),
|
||||
);
|
||||
ref={ref}
|
||||
{...rest}
|
||||
/>
|
||||
{error && <div className="form__message form__message--error">{error}</div>}
|
||||
</div>
|
||||
));
|
||||
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
@@ -757,12 +757,9 @@ type NestedObject = {
|
||||
order: number;
|
||||
};
|
||||
|
||||
export const getObjectKeysSorted = <
|
||||
T extends Record<string, NestedObject>,
|
||||
K extends keyof NestedObject
|
||||
>(
|
||||
export const getObjectKeysSorted = <T extends Record<string, NestedObject>, K extends keyof NestedObject>(
|
||||
object: T,
|
||||
sortKey: K
|
||||
sortKey: K,
|
||||
): string[] => {
|
||||
return Object.entries(object)
|
||||
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))
|
||||
|
||||
Reference in New Issue
Block a user