diff --git a/client/package-lock.json b/client/package-lock.json
index fc6ea4f1..585d2b4c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -4126,6 +4126,11 @@
"next-tick": "1"
}
},
+ "es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+ },
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
@@ -6588,7 +6593,7 @@
},
"html-webpack-plugin": {
"version": "3.2.0",
- "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
"dev": true,
"requires": {
@@ -6638,7 +6643,7 @@
},
"readable-stream": {
"version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@@ -7387,8 +7392,7 @@
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
- "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
- "dev": true
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
},
"is-regex": {
"version": "1.0.4",
@@ -13202,6 +13206,21 @@
"reduce-reducers": "^0.1.0"
}
},
+ "redux-form": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/redux-form/-/redux-form-7.4.2.tgz",
+ "integrity": "sha512-QxC36s4Lelx5Cr8dbpxqvl23dwYOydeAX8c6YPmgkz/Dhj053C16S2qoyZN6LO6HJ2oUF00rKsAyE94GwOUhFA==",
+ "requires": {
+ "es6-error": "^4.1.1",
+ "hoist-non-react-statics": "^2.5.4",
+ "invariant": "^2.2.4",
+ "is-promise": "^2.1.0",
+ "lodash": "^4.17.10",
+ "lodash-es": "^4.17.10",
+ "prop-types": "^15.6.1",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
@@ -15003,7 +15022,7 @@
},
"through": {
"version": "2.3.8",
- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
diff --git a/client/package.json b/client/package.json
index 7d200e80..6b518d60 100644
--- a/client/package.json
+++ b/client/package.json
@@ -31,6 +31,7 @@
"react-transition-group": "^2.4.0",
"redux": "^4.0.0",
"redux-actions": "^2.4.0",
+ "redux-form": "^7.4.2",
"redux-thunk": "^2.3.0",
"svg-url-loader": "^2.3.2",
"whatwg-fetch": "2.0.3"
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index f6045521..d20555fb 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -127,5 +127,16 @@
"category_label": "Category",
"rule_label": "Rule",
"filter_label": "Filter",
- "unknown_filter": "Unknown filter {{filterId}}"
-}
\ No newline at end of file
+ "unknown_filter": "Unknown filter {{filterId}}",
+ "refresh_status": "Refresh status",
+ "save_config": "Save config",
+ "enabled_dhcp": "DHCP server enabled",
+ "disabled_dhcp": "DHCP server disabled",
+ "dhcp_title": "DHCP server",
+ "dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
+ "dhcp_enable": "Enable DHCP server",
+ "dhcp_disable": "Disable DHCP server",
+ "dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
+ "dhcp_leases": "DHCP leases",
+ "dhcp_leases_not_found": "No DHCP leases found"
+}
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index a4da8c9a..cd56647e 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -522,3 +522,73 @@ export const getLanguage = () => async (dispatch) => {
dispatch(getLanguageFailure());
}
};
+
+export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST');
+export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS');
+export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
+
+export const getDhcpStatus = () => async (dispatch) => {
+ dispatch(getDhcpStatusRequest());
+ try {
+ const status = await apiClient.getDhcpStatus();
+ dispatch(getDhcpStatusSuccess(status));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(getDhcpStatusFailure());
+ }
+};
+
+export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
+export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
+export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
+
+export const setDhcpConfig = config => async (dispatch) => {
+ dispatch(setDhcpConfigRequest());
+ try {
+ await apiClient.setDhcpConfig(config);
+ dispatch(setDhcpConfigSuccess());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(setDhcpConfigFailure());
+ }
+};
+
+export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
+export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
+export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
+
+export const findActiveDhcp = () => async (dispatch) => {
+ dispatch(findActiveDhcpRequest());
+ try {
+ const result = await apiClient.findActiveDhcp();
+ dispatch(findActiveDhcpSuccess(result));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(findActiveDhcpFailure());
+ }
+};
+
+export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
+export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
+export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
+
+export const toggleDhcp = status => async (dispatch) => {
+ dispatch(toggleDhcpRequest());
+ let successMessage = '';
+
+ try {
+ if (status) {
+ successMessage = 'disabled_dhcp';
+ await apiClient.disableGlobalProtection();
+ } else {
+ successMessage = 'enabled_dhcp';
+ await apiClient.enableGlobalProtection();
+ }
+
+ dispatch(addSuccessToast(successMessage));
+ dispatch(toggleDhcpSuccess());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(toggleDhcpFailure());
+ }
+};
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index f0d90941..966b5199 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -302,4 +302,71 @@ export default class Api {
};
return this.makeRequest(path, method, parameters);
}
+
+ // DHCP
+ DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
+ DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
+ DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'GET' };
+
+ getDhcpStatus() {
+ // const { path, method } = this.DHCP_STATUS;
+ // return this.makeRequest(path, method);
+
+ const example = {
+ config: {
+ enabled: false,
+ gateway_ip: '192.168.1.1',
+ subnet_mask: '255.255.255.0',
+ range_start: '192.168.1.2',
+ range_end: '192.168.10.50',
+ lease_duration: '43200',
+ },
+ leases: [
+ {
+ mac: '001109b3b3b8',
+ ip: '192.168.1.22',
+ hostname: 'dell',
+ expires: '2017-07-21T17:32:28Z',
+ },
+ {
+ mac: '001109b3b3b9',
+ ip: '192.168.1.23',
+ hostname: 'dell',
+ expires: '2017-07-21T17:32:28Z',
+ },
+ ],
+ };
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(example);
+ }, 1000);
+ });
+ }
+
+ setDhcpConfig(config) {
+ // const { path, method } = this.DHCP_SET_CONFIG;
+ // const parameters = config;
+ // return this.makeRequest(path, method, parameters);
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(window.alert(`Set config:\n\n${JSON.stringify(config, null, 2)}`));
+ }, 1000);
+ });
+ }
+
+ findActiveDhcp() {
+ // const { path, method } = this.DHCP_FIND_ACTIVE;
+ // return this.makeRequest(path, method);
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ gateway_ip: '127.0.0.1',
+ found: true,
+ });
+ }, 10000);
+ });
+ }
}
diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js
new file mode 100644
index 00000000..a4caea0b
--- /dev/null
+++ b/client/src/components/Settings/Dhcp/Form.js
@@ -0,0 +1,134 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { Field, reduxForm } from 'redux-form';
+import { R_IPV4 } from '../../../helpers/constants';
+
+const required = (value) => {
+ if (value) {
+ return false;
+ }
+ return 'Required field';
+};
+
+const ipv4 = (value) => {
+ if (value && !new RegExp(R_IPV4).test(value)) {
+ return 'Invalid IPv4 format';
+ }
+ return false;
+};
+
+const renderField = ({
+ input, className, placeholder, type, disabled, meta: { touched, error },
+}) => (
+