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 ( +
+ ); +}; + +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 ( +