+ client: Refactor DHCP settings

This commit is contained in:
Artem Baskal
2020-08-19 18:23:05 +03:00
committed by Simon Zolin
parent c9f58ce4a7
commit 1d35d73fc5
49 changed files with 2953 additions and 1660 deletions

View File

@@ -48,7 +48,6 @@ export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
export const ADDRESS_IN_USE_TEXT = 'address already in use';
export const UBUNTU_SYSTEM_PORT = 53;
export const INSTALL_FIRST_STEP = 1;
export const INSTALL_TOTAL_STEPS = 5;
@@ -63,6 +62,8 @@ export const SETTINGS_NAMES = {
export const STANDARD_DNS_PORT = 53;
export const STANDARD_WEB_PORT = 80;
export const STANDARD_HTTPS_PORT = 443;
export const DNS_OVER_TLS_PORT = 853;
export const MAX_PORT = 65535;
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
@@ -144,7 +145,7 @@ export const UNSAFE_PORTS = [
export const ALL_INTERFACES_IP = '0.0.0.0';
export const DHCP_STATUS_RESPONSE = {
export const STATUS_RESPONSE = {
YES: 'yes',
NO: 'no',
ERROR: 'error',
@@ -458,6 +459,12 @@ export const IP_MATCH_LIST_STATUS = {
CIDR: 'CIDR', // the ip is in the specified CIDR range
};
export const DHCP_FORM_NAMES = {
DHCPv4: 'dhcpv4',
DHCPv6: 'dhcpv6',
DHCP_INTERFACES: 'dhcpInterfaces',
};
export const FORM_NAME = {
UPSTREAM: 'upstream',
DOMAIN_CHECK: 'domainCheck',
@@ -465,7 +472,6 @@ export const FORM_NAME = {
REWRITES: 'rewrites',
LOGS_FILTER: 'logsFilter',
CLIENT: 'client',
DHCP: 'dhcp',
LEASE: 'lease',
ACCESS: 'access',
BLOCKING_MODE: 'blockingMode',
@@ -477,9 +483,39 @@ export const FORM_NAME = {
INSTALL: 'install',
LOGIN: 'login',
CACHE: 'cache',
...DHCP_FORM_NAMES,
};
export const SMALL_SCREEN_SIZE = 767;
export const MEDIUM_SCREEN_SIZE = 1023;
export const SECONDS_IN_HOUR = 60 * 60;
export const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
export const DHCP_VALUES_PLACEHOLDERS = {
ipv4: {
subnet_mask: '255.255.255.0',
lease_duration: SECONDS_IN_DAY.toString(),
},
ipv6: {
range_start: '2001::1',
range_end: 'ff',
lease_duration: SECONDS_IN_DAY.toString(),
},
};
export const DHCP_DESCRIPTION_PLACEHOLDERS = {
ipv4: {
gateway_ip: 'dhcp_form_gateway_input',
subnet_mask: 'dhcp_form_subnet_input',
range_start: 'dhcp_form_range_start',
range_end: 'dhcp_form_range_end',
lease_duration: 'dhcp_form_lease_input',
},
ipv6: {
range_start: 'dhcp_form_range_start',
range_end: 'dhcp_form_range_end',
lease_duration: 'dhcp_form_lease_input',
},
};

View File

@@ -1,5 +1,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import classNames from 'classnames';
import { createOnBlurHandler } from './helpers';
import { R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
@@ -24,11 +26,12 @@ export const renderField = (props, elementType) => {
step,
onBlur,
});
return (
<>
{element}
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>
);
};
@@ -47,7 +50,7 @@ renderField.propTypes = {
step: PropTypes.number,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
@@ -71,7 +74,7 @@ export const renderGroupField = ({
const onBlur = (event) => createOnBlurHandler(event, input, normalizeOnBlur);
return (
<Fragment>
<>
<div className="input-group">
<input
{...input}
@@ -98,8 +101,8 @@ export const renderGroupField = ({
}
</div>
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
</Fragment>
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>
);
};
@@ -115,7 +118,7 @@ renderGroupField.propTypes = {
removeField: PropTypes.func,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
normalizeOnBlur: PropTypes.func,
};
@@ -137,7 +140,8 @@ export const renderRadioField = ({
</label>
{!disabled
&& touched
&& (error && <span className="form__message form__message--error">{error}</span>)}
&& error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
renderRadioField.propTypes = {
@@ -147,11 +151,11 @@ renderRadioField.propTypes = {
disabled: PropTypes.bool,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const renderSelectField = ({
export const renderCheckboxField = ({
input,
placeholder,
subtitle,
@@ -163,7 +167,8 @@ export const renderSelectField = ({
}) => <>
<label className={`checkbox ${modifier}`} onClick={onClick}>
<span className="checkbox__marker" />
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} checked={input.checked || checked}/>
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled}
checked={input.checked || checked} />
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{placeholder}</span>
@@ -178,10 +183,11 @@ export const renderSelectField = ({
</label>
{!disabled
&& touched
&& error && <span className="form__message form__message--error">{error}</span>}
&& error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>;
renderSelectField.propTypes = {
renderCheckboxField.propTypes = {
input: PropTypes.object.isRequired,
placeholder: PropTypes.string,
subtitle: PropTypes.string,
@@ -191,7 +197,37 @@ renderSelectField.propTypes = {
checked: PropTypes.bool,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const renderSelectField = ({
input,
meta: { touched, error },
children,
label,
}) => {
const showWarning = touched && error;
const selectClass = classNames('form-control custom-select', {
'select--no-warning': !showWarning,
});
return <>
{label && <label><Trans>{label}</Trans></label>}
<select {...input} className={selectClass}>{children}</select>
{showWarning
&& <span className="form__message form__message--error form__message--left-pad"><Trans>{error}</Trans></span>}
</>;
};
renderSelectField.propTypes = {
input: PropTypes.object.isRequired,
disabled: PropTypes.bool,
label: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.array, PropTypes.element]).isRequired,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.string,
}).isRequired,
};
@@ -218,7 +254,7 @@ export const renderServiceField = ({
</svg>
</label>
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
renderServiceField.propTypes = {
@@ -229,10 +265,12 @@ renderServiceField.propTypes = {
icon: PropTypes.string,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const getLastIpv4Octet = (ipv4) => parseInt(ipv4.slice(ipv4.lastIndexOf('.') + 1), 10);
/**
* @param value {string}
* @returns {*|number}

View File

@@ -19,6 +19,7 @@ import {
DEFAULT_LANGUAGE,
DEFAULT_TIME_FORMAT,
DETAILED_DATE_FORMAT_OPTIONS,
DHCP_VALUES_PLACEHOLDERS,
FILTERED,
FILTERED_STATUS,
IP_MATCH_LIST_STATUS,
@@ -190,7 +191,7 @@ export const captitalizeWords = (text) => text.split(/[ -_]/g)
export const getInterfaceIp = (option) => {
const onlyIPv6 = option.ip_addresses.every((ip) => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
let [interfaceIP] = option.ip_addresses;
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
@@ -203,16 +204,9 @@ export const getInterfaceIp = (option) => {
return interfaceIP;
};
export const getIpList = (interfaces) => {
let list = [];
Object.keys(interfaces)
.forEach((item) => {
list = [...list, ...interfaces[item].ip_addresses];
});
return list.sort();
};
export const getIpList = (interfaces) => Object.values(interfaces)
.reduce((acc, curr) => acc.concat(curr.ip_addresses), [])
.sort();
export const getDnsAddress = (ip, port = '') => {
const isStandardDnsPort = port === STANDARD_DNS_PORT;
@@ -668,11 +662,12 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
response_status,
})}`;
export const processContent = (content) => (Array.isArray(content)
? content.filter(([, value]) => value).reduce((acc, val) => acc.concat(val), [])
: content
);
export const processContent = (
content,
) => (Array.isArray(content)
? content.filter(([, value]) => value)
.reduce((acc, val) => acc.concat(val), [])
: content);
/**
* @param object {object}
* @param sortKey {string}
@@ -746,3 +741,65 @@ export const sortIp = (a, b) => {
return 0;
}
};
/**
* @param ip {string}
* @param gateway_ip {string}
* @returns {{range_end: string, subnet_mask: string, range_start: string,
* lease_duration: string, gateway_ip: string}}
*/
export const calculateDhcpPlaceholdersIpv4 = (ip, gateway_ip) => {
const LAST_OCTET_IDX = 3;
const LAST_OCTET_RANGE_START = 100;
const LAST_OCTET_RANGE_END = 200;
const addr = ipaddr.parse(ip);
addr.octets[LAST_OCTET_IDX] = LAST_OCTET_RANGE_START;
const range_start = addr.toString();
addr.octets[LAST_OCTET_IDX] = LAST_OCTET_RANGE_END;
const range_end = addr.toString();
const {
subnet_mask,
lease_duration,
} = DHCP_VALUES_PLACEHOLDERS.ipv4;
return {
gateway_ip: gateway_ip || ip,
subnet_mask,
range_start,
range_end,
lease_duration,
};
};
export const calculateDhcpPlaceholdersIpv6 = () => {
const {
range_start,
range_end,
lease_duration,
} = DHCP_VALUES_PLACEHOLDERS.ipv6;
return {
range_start,
range_end,
lease_duration,
};
};
/**
* Add ip_addresses property - concatenated ipv4_addresses and ipv6_addresses for every interface
* @param interfaces
* @param interfaces.ipv4_addresses {string[]}
* @param interfaces.ipv6_addresses {string[]}
* @returns interfaces Interfaces enriched with ip_addresses property
*/
export const enrichWithConcatenatedIpAddresses = (interfaces) => Object.entries(interfaces)
.reduce((acc, [k, v]) => {
const ipv4_addresses = v.ipv4_addresses ?? [];
const ipv6_addresses = v.ipv6_addresses ?? [];
acc[k].ip_addresses = ipv4_addresses.concat(ipv6_addresses);
return acc;
}, interfaces);

View File

@@ -1,7 +1,6 @@
import { Trans } from 'react-i18next';
import React from 'react';
import i18next from 'i18next';
import {
MAX_PORT,
R_CIDR,
R_CIDR_IPV6,
R_HOST,
@@ -9,10 +8,10 @@ import {
R_IPV6,
R_MAC,
R_URL_REQUIRES_PROTOCOL,
STANDARD_WEB_PORT,
UNSAFE_PORTS,
} from './constants';
import { isValidAbsolutePath } from './form';
import { getLastIpv4Octet, isValidAbsolutePath } from './form';
// Validation functions
// https://redux-form.com/8.3.0/examples/fieldlevelvalidation/
@@ -26,7 +25,7 @@ export const validateRequiredValue = (value) => {
if (formattedValue || formattedValue === 0 || (formattedValue && formattedValue.length !== 0)) {
return undefined;
}
return <Trans>form_error_required</Trans>;
return 'form_error_required';
};
/**
@@ -35,11 +34,28 @@ export const validateRequiredValue = (value) => {
*/
export const getMaxValueValidator = (maximum) => (value) => {
if (value && value > maximum) {
i18next.t('value_not_larger_than', { maximum });
return i18next.t('value_not_larger_than', { maximum });
}
return undefined;
};
/**
* @param value {string}
* @returns {undefined|string}
*/
export const validateIpv4RangeEnd = (_, allValues) => {
if (!allValues || !allValues.v4 || !allValues.v4.range_end || !allValues.v4.range_start) {
return undefined;
}
const { range_end, range_start } = allValues.v4;
if (getLastIpv4Octet(range_end) <= getLastIpv4Octet(range_start)) {
return 'range_end_error';
}
return undefined;
};
/**
* @param value {string}
@@ -47,7 +63,7 @@ export const getMaxValueValidator = (maximum) => (value) => {
*/
export const validateIpv4 = (value) => {
if (value && !R_IPV4.test(value)) {
return <Trans>form_error_ip4_format</Trans>;
return 'form_error_ip4_format';
}
return undefined;
};
@@ -63,12 +79,12 @@ export const validateClientId = (value) => {
const formattedValue = value ? value.trim() : value;
if (formattedValue && !(
R_IPV4.test(formattedValue)
|| R_IPV6.test(formattedValue)
|| R_MAC.test(formattedValue)
|| R_CIDR.test(formattedValue)
|| R_CIDR_IPV6.test(formattedValue)
|| R_IPV6.test(formattedValue)
|| R_MAC.test(formattedValue)
|| R_CIDR.test(formattedValue)
|| R_CIDR_IPV6.test(formattedValue)
)) {
return <Trans>form_error_client_id_format</Trans>;
return 'form_error_client_id_format';
}
return undefined;
};
@@ -79,7 +95,7 @@ export const validateClientId = (value) => {
*/
export const validateIpv6 = (value) => {
if (value && !R_IPV6.test(value)) {
return <Trans>form_error_ip6_format</Trans>;
return 'form_error_ip6_format';
}
return undefined;
};
@@ -90,7 +106,7 @@ export const validateIpv6 = (value) => {
*/
export const validateIp = (value) => {
if (value && !R_IPV4.test(value) && !R_IPV6.test(value)) {
return <Trans>form_error_ip_format</Trans>;
return 'form_error_ip_format';
}
return undefined;
};
@@ -101,76 +117,76 @@ export const validateIp = (value) => {
*/
export const validateMac = (value) => {
if (value && !R_MAC.test(value)) {
return <Trans>form_error_mac_format</Trans>;
return 'form_error_mac_format';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateIsPositiveValue = (value) => {
if ((value || value === 0) && value <= 0) {
return <Trans>form_error_positive</Trans>;
return 'form_error_positive';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {boolean|*}
*/
export const validateBiggerOrEqualZeroValue = (value) => {
if (value < 0) {
return <Trans>form_error_negative</Trans>;
return 'form_error_negative';
}
return false;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validatePort = (value) => {
if ((value || value === 0) && (value < 80 || value > 65535)) {
return <Trans>form_error_port_range</Trans>;
if ((value || value === 0) && (value < STANDARD_WEB_PORT || value > MAX_PORT)) {
return 'form_error_port_range';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateInstallPort = (value) => {
if (value < 1 || value > 65535) {
return <Trans>form_error_port</Trans>;
if (value < 1 || value > MAX_PORT) {
return 'form_error_port';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validatePortTLS = (value) => {
if (value === 0) {
return undefined;
}
if (value && (value < 80 || value > 65535)) {
return <Trans>form_error_port_range</Trans>;
if (value && (value < STANDARD_WEB_PORT || value > MAX_PORT)) {
return 'form_error_port_range';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateIsSafePort = (value) => {
if (UNSAFE_PORTS.includes(value)) {
return <Trans>form_error_port_unsafe</Trans>;
return 'form_error_port_unsafe';
}
return undefined;
};
@@ -181,7 +197,7 @@ export const validateIsSafePort = (value) => {
*/
export const validateDomain = (value) => {
if (value && !R_HOST.test(value)) {
return <Trans>form_error_domain_format</Trans>;
return 'form_error_domain_format';
}
return undefined;
};
@@ -192,7 +208,7 @@ export const validateDomain = (value) => {
*/
export const validateAnswer = (value) => {
if (value && (!R_IPV4.test(value) && !R_IPV6.test(value) && !R_HOST.test(value))) {
return <Trans>form_error_answer_format</Trans>;
return 'form_error_answer_format';
}
return undefined;
};
@@ -203,7 +219,7 @@ export const validateAnswer = (value) => {
*/
export const validatePath = (value) => {
if (value && !isValidAbsolutePath(value) && !R_URL_REQUIRES_PROTOCOL.test(value)) {
return <Trans>form_error_url_or_path_format</Trans>;
return 'form_error_url_or_path_format';
}
return undefined;
};