+ client: handle DNS config

This commit is contained in:
Artem Baskal 2019-12-04 21:52:38 +03:00 committed by Ildar Kamalov
parent 87bb773d3e
commit 197d07f32b
11 changed files with 294 additions and 23 deletions

View File

@ -187,6 +187,15 @@
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>", "query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
"query_log_strict_search": "Use double quotes for strict search", "query_log_strict_search": "Use double quotes for strict search",
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost", "query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
"dns_config": "DNS configuration",
"blocking_mode": "Blocking mode",
"nxdomain": "NXDOMAIN",
"null_ip": "Null IP",
"custom_ip": "Custom IP",
"blocking_ipv4": "Blocking IPv4",
"blocking_ipv6": "Blocking IPv6",
"form_enter_rate_limit": "Enter rate limit",
"rate_limit": "Rate limit",
"source_label": "Source", "source_label": "Source",
"found_in_known_domain_db": "Found in the known domains database.", "found_in_known_domain_db": "Found in the known domains database.",
"category_label": "Category", "category_label": "Category",

View File

@ -0,0 +1,35 @@
import { createAction } from 'redux-actions';
import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE');
export const getDnsConfigSuccess = createAction('GET_DNS_CONFIG_SUCCESS');
export const getDnsConfig = () => async (dispatch) => {
dispatch(getDnsConfigRequest());
try {
const data = await apiClient.getDnsConfig();
dispatch(getDnsConfigSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getDnsConfigFailure());
}
};
export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST');
export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE');
export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');
export const setDnsConfig = config => async (dispatch) => {
dispatch(setDnsConfigRequest());
try {
await apiClient.setDnsConfig(config);
dispatch(addSuccessToast('config_successfully_saved'));
dispatch(setDnsConfigSuccess(config));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setDnsConfigFailure());
}
};

View File

@ -91,17 +91,9 @@ export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS')
export const toggleProtection = status => async (dispatch) => { export const toggleProtection = status => async (dispatch) => {
dispatch(toggleProtectionRequest()); dispatch(toggleProtectionRequest());
let successMessage = '';
try { try {
if (status) { const successMessage = status ? 'disabled_protection' : 'enabled_protection';
successMessage = 'disabled_protection'; await apiClient.setDnsConfig({ protection_enabled: !status });
await apiClient.disableGlobalProtection();
} else {
successMessage = 'enabled_protection';
await apiClient.enableGlobalProtection();
}
dispatch(addSuccessToast(successMessage)); dispatch(addSuccessToast(successMessage));
dispatch(toggleProtectionSuccess()); dispatch(toggleProtectionSuccess());
} catch (error) { } catch (error) {

View File

@ -30,8 +30,6 @@ class Api {
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' }; GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' }; GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = { path: 'version.json', method: 'POST' }; GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' }; GLOBAL_UPDATE = { path: 'update', method: 'POST' };
startGlobalFiltering() { startGlobalFiltering() {
@ -76,16 +74,6 @@ class Api {
return this.makeRequest(path, method, config); return this.makeRequest(path, method, config);
} }
enableGlobalProtection() {
const { path, method } = this.GLOBAL_ENABLE_PROTECTION;
return this.makeRequest(path, method);
}
disableGlobalProtection() {
const { path, method } = this.GLOBAL_DISABLE_PROTECTION;
return this.makeRequest(path, method);
}
getUpdate() { getUpdate() {
const { path, method } = this.GLOBAL_UPDATE; const { path, method } = this.GLOBAL_UPDATE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
@ -546,6 +534,24 @@ class Api {
const { path, method } = this.GET_PROFILE; const { path, method } = this.GET_PROFILE;
return this.makeRequest(path, method); return this.makeRequest(path, method);
} }
// DNS config
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG;
return this.makeRequest(path, method);
}
setDnsConfig(data) {
const { path, method } = this.SET_DNS_CONFIG;
const config = {
data,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
} }
const apiClient = new Api(); const apiClient = new Api();

View File

@ -0,0 +1,120 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, renderRadioField, required, ipv4, ipv6, isPositive, toNumber } from '../../../helpers/form';
import { BLOCKING_MODES } from '../../../helpers/constants';
const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => (
<Field
key={mode}
name="blocking_mode"
type="radio"
component={renderRadioField}
value={mode}
placeholder={t(mode)}
disabled={processing}
/>
));
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">
<Trans>rate_limit</Trans>
</label>
<Field
name="ratelimit"
type="number"
component={renderField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[required, isPositive]}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings mb-3">
<label className="form__label">
<Trans>blocking_mode</Trans>
</label>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment>
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="blocking_ipv4">
<Trans>blocking_ipv4</Trans>
</label>
<Field
name="blocking_ipv4"
component={renderField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv4, required]}
/>
</div>
</div>
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ip_address">
<Trans>blocking_ipv6</Trans>
</label>
<Field
name="blocking_ipv6"
component={renderField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv6, required]}
/>
</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>
);
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('blockingModeForm');
Form = connect((state) => {
const blockingMode = selector(state, 'blocking_mode');
return {
blockingMode,
};
})(Form);
export default flow([
withNamespaces(),
reduxForm({
form: 'blockingModeForm',
}),
])(Form);

View File

@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import Card from '../../ui/Card';
import Form from './Form';
const DnsConfig = ({ t, dnsConfig, setDnsConfig }) => {
const handleFormSubmit = (values) => {
setDnsConfig(values);
};
const {
blocking_mode,
ratelimit,
blocking_ipv4,
blocking_ipv6,
processingSetConfig,
} = dnsConfig;
return (
<Card
title={t('dns_config')}
bodyType="card-body box-body--settings"
id="dns-config"
>
<div className="form">
<Form
initialValues={{
ratelimit,
blocking_mode,
blocking_ipv4,
blocking_ipv6,
}}
onSubmit={handleFormSubmit}
processing={processingSetConfig}
/>
</div>
</Card>
);
};
DnsConfig.propTypes = {
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(DnsConfig);

View File

@ -6,6 +6,8 @@ import Services from './Services';
import StatsConfig from './StatsConfig'; import StatsConfig from './StatsConfig';
import LogsConfig from './LogsConfig'; import LogsConfig from './LogsConfig';
import FiltersConfig from './FiltersConfig'; import FiltersConfig from './FiltersConfig';
import DnsConfig from './DnsConfig';
import Checkbox from '../ui/Checkbox'; import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle'; import PageTitle from '../ui/PageTitle';
@ -38,6 +40,7 @@ class Settings extends Component {
this.props.getStatsConfig(); this.props.getStatsConfig();
this.props.getLogsConfig(); this.props.getLogsConfig();
this.props.getFilteringStatus(); this.props.getFilteringStatus();
this.props.getDnsConfig();
} }
renderSettings = (settings) => { renderSettings = (settings) => {
@ -68,10 +71,12 @@ class Settings extends Component {
resetStats, resetStats,
stats, stats,
queryLogs, queryLogs,
dnsConfig,
setLogsConfig, setLogsConfig,
clearLogs, clearLogs,
filtering, filtering,
setFiltersConfig, setFiltersConfig,
setDnsConfig,
t, t,
} = this.props; } = this.props;
@ -101,6 +106,12 @@ class Settings extends Component {
</div> </div>
</Card> </Card>
</div> </div>
<div className="col-md-12">
<DnsConfig
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
</div>
<div className="col-md-12"> <div className="col-md-12">
<LogsConfig <LogsConfig
enabled={queryLogs.enabled} enabled={queryLogs.enabled}
@ -143,6 +154,7 @@ Settings.propTypes = {
resetStats: PropTypes.func.isRequired, resetStats: PropTypes.func.isRequired,
setFiltersConfig: PropTypes.func.isRequired, setFiltersConfig: PropTypes.func.isRequired,
getFilteringStatus: PropTypes.func.isRequired, getFilteringStatus: PropTypes.func.isRequired,
getDnsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
}; };

View File

@ -4,11 +4,12 @@ import { getBlockedServices, setBlockedServices } from '../actions/services';
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats'; import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs'; import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
import { getFilteringStatus, setFiltersConfig } from '../actions/filtering'; import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
import { getDnsConfig, setDnsConfig } from '../actions/dnsConfig';
import Settings from '../components/Settings'; import Settings from '../components/Settings';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { const {
settings, services, stats, queryLogs, filtering, settings, services, stats, queryLogs, filtering, dnsConfig,
} = state; } = state;
const props = { const props = {
settings, settings,
@ -16,6 +17,7 @@ const mapStateToProps = (state) => {
stats, stats,
queryLogs, queryLogs,
filtering, filtering,
dnsConfig,
}; };
return props; return props;
}; };
@ -33,6 +35,8 @@ const mapDispatchToProps = {
setLogsConfig, setLogsConfig,
getFilteringStatus, getFilteringStatus,
setFiltersConfig, setFiltersConfig,
getDnsConfig,
setDnsConfig,
}; };
export default connect( export default connect(

View File

@ -341,6 +341,12 @@ export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168]; export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
export const BLOCKING_MODES = {
nxdomain: 'nxdomain',
null_ip: 'null_ip',
custom_ip: 'custom_ip',
};
export const WHOIS_ICONS = { export const WHOIS_ICONS = {
location: 'location', location: 'location',
orgname: 'network', orgname: 'network',

View File

@ -0,0 +1,36 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/dnsConfig';
import { BLOCKING_MODES } from '../helpers/constants';
const dnsConfig = handleActions(
{
[actions.getDnsConfigRequest]: state => ({ ...state, processingGetConfig: true }),
[actions.getDnsConfigFailure]: state =>
({ ...state, processingGetConfig: false }),
[actions.getDnsConfigSuccess]: (state, { payload }) => ({
...state,
...payload,
processingGetConfig: false,
}),
[actions.setDnsConfigRequest]: state => ({ ...state, processingSetConfig: true }),
[actions.setDnsConfigFailure]: state =>
({ ...state, processingSetConfig: false }),
[actions.setDnsConfigSuccess]: (state, { payload }) => ({
...state,
...payload,
processingSetConfig: false,
}),
},
{
processingGetConfig: false,
processingSetConfig: false,
blocking_mode: BLOCKING_MODES.nxdomain,
ratelimit: 20,
blocking_ipv4: '',
blocking_ipv6: '',
},
);
export default dnsConfig;

View File

@ -13,6 +13,7 @@ import rewrites from './rewrites';
import services from './services'; import services from './services';
import stats from './stats'; import stats from './stats';
import queryLogs from './queryLogs'; import queryLogs from './queryLogs';
import dnsConfig from './dnsConfig';
import filtering from './filtering'; import filtering from './filtering';
const settings = handleActions( const settings = handleActions(
@ -369,6 +370,7 @@ export default combineReducers({
rewrites, rewrites,
services, services,
stats, stats,
dnsConfig,
loadingBar: loadingBarReducer, loadingBar: loadingBarReducer,
form: formReducer, form: formReducer,
}); });