Pull request #730: + client: Add Hot Module Replacement

Merge in DNS/adguard-home from feature/hmr to master

Squashed commit of the following:

commit 952ed1955c2a7a32446d99489f137f02eb47c99e
Merge: 83484931 de92c852
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 11:02:10 2020 +0300

    Merge branch 'master' into feature/hmr

commit 8348493105d7d63d8b0836a5c272df2b17a6b142
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 15:07:31 2020 +0300

    Remove empty prop types, remove Services empty container

commit b2fe4a30b79d91e482318ee5deea8e49c7038f7e
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:56:35 2020 +0300

    Move constants

commit f8be4c18c35193ad77bf5e25f311ad834c1d6870
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:19:02 2020 +0300

    Fix Setup bug, update webpack.dev

commit 1d9cc4ddf8af2c979eb707a7f0fc06744eec186c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 12:10:38 2020 +0300

    Review changes

commit a1edb21358def21ed1808b081ffc2f0b6755e3da
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 11:46:58 2020 +0300

    Remove lazy loading, fix updated components

commit 0aa2cf55f8d4206ac9e2f99fc1b990ed8a9c7825
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 20:32:19 2020 +0300

    Refactor App component, add lazy loading

commit 3c2ba4772a91ff7b06641dba6c6bf3fdcd2fdf7f
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 17:12:41 2020 +0300

    Simplify App hot loading boilerplate, setup lazy loading, update Header

commit 8df3221f315372b066f2ac0c9a1687f1677b8415
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 15:16:06 2020 +0300

    + client: Add Hot Module Replacement
This commit is contained in:
Artem Baskal
2020-08-13 11:15:45 +03:00
parent de92c85256
commit 97df19898f
38 changed files with 1098 additions and 822 deletions

View File

@@ -1,40 +1,36 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../../ui/Card';
import { setAccessList } from '../../../../actions/access';
class Access extends Component {
handleFormSubmit = (values) => {
this.props.setAccessList(values);
const Access = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
processing,
processingSet,
...values
} = useSelector((state) => state.access, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setAccessList(values));
};
render() {
const { t, access } = this.props;
const { processing, processingSet, ...values } = access;
return (
<Card
title={t('access_title')}
subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={values}
onSubmit={this.handleFormSubmit}
processingSet={processingSet}
/>
</Card>
);
}
}
Access.propTypes = {
access: PropTypes.object.isRequired,
setAccessList: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
return (
<Card
title={t('access_title')}
subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={values}
onSubmit={handleFormSubmit}
processingSet={processingSet}
/>
</Card>
);
};
export default withTranslation()(Access);
export default Access;

View File

@@ -1,9 +1,8 @@
import React, { Fragment } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import {
renderInputField,
renderRadioField,
@@ -18,32 +17,36 @@ import {
} from '../../../../helpers/validators';
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
const checkboxes = [{
name: 'edns_cs_enabled',
placeholder: 'edns_enable',
subtitle: 'edns_cs_desc',
},
{
name: 'dnssec_enabled',
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
}];
const checkboxes = [
{
name: 'edns_cs_enabled',
placeholder: 'edns_enable',
subtitle: 'edns_cs_desc',
},
{
name: 'dnssec_enabled',
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
},
];
const customIps = [{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
validateIp: validateIpv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
validateIp: validateIpv6,
}];
const customIps = [
{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
validateIp: validateIpv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
validateIp: validateIpv6,
},
];
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
.map((mode) => (
@@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
/>
));
let Form = ({
handleSubmit, submitting, invalid, processing, blockingMode, t,
}) => <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit"
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
<Field
name="ratelimit"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
/>
</div>
</div>
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
const Form = ({
handleSubmit, submitting, invalid, processing,
}) => {
const { t } = useTranslation();
const {
blocking_mode,
} = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
return <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit"
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
</div>)}
</Fragment>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>;
<Field
name="ratelimit"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
/>
</div>
</div>
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blocking_mode === BLOCKING_MODES.custom_ip && (
<>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
</div>
</div>)}
</>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>;
};
Form.propTypes = {
blockingMode: PropTypes.string.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE);
Form = connect((state) => {
const blockingMode = selector(state, 'blocking_mode');
return {
blockingMode,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
])(Form);
export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);

View File

@@ -1,15 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Card from '../../../ui/Card';
import Form from './Form';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const Config = ({ t, dnsConfig, setDnsConfig }) => {
const handleFormSubmit = (values) => {
setDnsConfig(values);
};
const Config = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
blocking_mode,
ratelimit,
@@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
dnssec_enabled,
disable_ipv6,
processingSetConfig,
} = dnsConfig;
} = useSelector((state) => state.dnsConfig, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setDnsConfig(values));
};
return (
<Card
@@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
);
};
Config.propTypes = {
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Config);
export default Config;

View File

@@ -1,29 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { shallowEqual, useDispatch } from 'react-redux';
import Form from './Form';
import Card from '../../../ui/Card';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const Upstream = (props) => {
const [t] = useTranslation();
const Upstream = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
upstream_dns,
bootstrap_dns,
upstream_mode,
processingSetConfig,
} = ((state) => state.dnsConfig, shallowEqual);
const { processingTestUpstream } = ((state) => state.settings, shallowEqual);
const handleSubmit = (values) => {
dispatch(setDnsConfig(values));
};
const {
processingTestUpstream,
dnsConfig: {
upstream_dns,
bootstrap_dns,
processingSetConfig,
upstream_mode,
},
} = props;
return (
<Card
title={t('upstream_dns')}
@@ -48,9 +45,4 @@ const Upstream = (props) => {
);
};
Upstream.propTypes = {
processingTestUpstream: PropTypes.bool.isRequired,
dnsConfig: PropTypes.object.isRequired,
};
export default Upstream;

View File

@@ -1,67 +1,40 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Upstream from './Upstream';
import Access from './Access';
import Config from './Config';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import CacheConfig from './Cache';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';
const Dns = (props) => {
const Dns = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const processing = useSelector((state) => state.access.processing);
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
const isDataLoading = processing || processingGetConfig;
useEffect(() => {
props.getAccessList();
props.getDnsConfig();
dispatch(getAccessList());
dispatch(getDnsConfig());
}, []);
const {
settings,
access,
setAccessList,
dnsConfig,
setDnsConfig,
} = props;
const isDataLoading = access.processing || dnsConfig.processingGetConfig;
return (
<>
<PageTitle title={t('dns_settings')} />
{isDataLoading
? <Loading />
: <>
<Upstream
processingTestUpstream={settings.processingTestUpstream}
dnsConfig={dnsConfig}
/>
<Config
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<CacheConfig
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<Access
access={access}
setAccessList={setAccessList}
/>
</>}
</>
);
};
Dns.propTypes = {
settings: PropTypes.object.isRequired,
getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired,
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
getDnsConfig: PropTypes.func.isRequired,
return <>
<PageTitle title={t('dns_settings')} />
{isDataLoading
? <Loading />
: <>
<Upstream />
<Config />
<CacheConfig />
<Access />
</>}
</>;
};
export default Dns;