diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 697dbbcd..d53593c5 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -298,5 +298,14 @@ "clients_not_found": "No clients found", "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "auto_clients_title": "Clients (runtime)", - "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration" -} \ No newline at end of file + "auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration", + "access_title": "Access settings", + "access_desc": "Here you can configure access rules for the AdGuard Home DNS server.", + "access_allowed_title": "Allowed clients", + "access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.", + "access_disallowed_title": "Disallowed clients", + "access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.", + "access_blocked_title": "Blocked domains", + "access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.", + "access_settings_saved": "Access settings successfully saved" +} diff --git a/client/src/actions/access.js b/client/src/actions/access.js new file mode 100644 index 00000000..b10062cb --- /dev/null +++ b/client/src/actions/access.js @@ -0,0 +1,45 @@ +import { createAction } from 'redux-actions'; +import Api from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; +import { normalizeTextarea } from '../helpers/helpers'; + +const apiClient = new Api(); + +export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST'); +export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE'); +export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS'); + +export const getAccessList = () => async (dispatch) => { + dispatch(getAccessListRequest()); + try { + const data = await apiClient.getAccessList(); + dispatch(getAccessListSuccess(data)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getAccessListFailure()); + } +}; + +export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST'); +export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE'); +export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS'); + +export const setAccessList = config => async (dispatch) => { + dispatch(setAccessListRequest()); + try { + const { allowed_clients, disallowed_clients, blocked_hosts } = config; + + const values = { + allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [], + disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [], + blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [], + }; + + await apiClient.setAccessList(values); + dispatch(setAccessListSuccess()); + dispatch(addSuccessToast('access_settings_saved')); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setAccessListFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 81bce7cf..1fa852f2 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -460,4 +460,22 @@ export default class Api { }; return this.makeRequest(path, method, parameters); } + + // DNS access settings + ACCESS_LIST = { path: 'access/list', method: 'GET' }; + ACCESS_SET = { path: 'access/set', method: 'POST' }; + + getAccessList() { + const { path, method } = this.ACCESS_LIST; + return this.makeRequest(path, method); + } + + setAccessList(config) { + const { path, method } = this.ACCESS_SET; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); + } } diff --git a/client/src/components/Settings/Access/Form.js b/client/src/components/Settings/Access/Form.js new file mode 100644 index 00000000..9096102d --- /dev/null +++ b/client/src/components/Settings/Access/Form.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Field, reduxForm } from 'redux-form'; +import { Trans, withNamespaces } from 'react-i18next'; +import flow from 'lodash/flow'; + +const Form = (props) => { + const { handleSubmit, submitting, invalid } = props; + + return ( +
+
+ +
+ access_allowed_desc +
+ +
+
+ +
+ access_disallowed_desc +
+ +
+
+ +
+ access_blocked_desc +
+ +
+
+
+ +
+
+
+ ); +}; + +Form.propTypes = { + handleSubmit: PropTypes.func, + submitting: PropTypes.bool, + invalid: PropTypes.bool, + initialValues: PropTypes.object, + t: PropTypes.func, +}; + +export default flow([withNamespaces(), reduxForm({ form: 'accessForm' })])(Form); diff --git a/client/src/components/Settings/Access/index.js b/client/src/components/Settings/Access/index.js new file mode 100644 index 00000000..77ccc265 --- /dev/null +++ b/client/src/components/Settings/Access/index.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces } from 'react-i18next'; + +import Form from './Form'; +import Card from '../../ui/Card'; + +class Access extends Component { + handleFormSubmit = (values) => { + this.props.setAccessList(values); + }; + + render() { + const { t, access } = this.props; + + const { + processing, + processingSet, + ...values + } = access; + + return ( + +
+ + ); + } +} + +Access.propTypes = { + access: PropTypes.object.isRequired, + setAccessList: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withNamespaces()(Access); diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 281330df..7e410a0c 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -63,6 +63,10 @@ font-weight: 700; } +.form__label--with-desc { + margin-bottom: 0; +} + .form__status { margin-top: 10px; font-size: 14px; diff --git a/client/src/components/Settings/Upstream/Form.js b/client/src/components/Settings/Upstream/Form.js index 8ef916f5..37990e42 100644 --- a/client/src/components/Settings/Upstream/Form.js +++ b/client/src/components/Settings/Upstream/Form.js @@ -62,7 +62,7 @@ let Form = (props) => {
-