From a4dedacf43d6aa6baa284c08e22b179323a87f5e Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <i.kamalov@adguard.com>
Date: Tue, 15 Oct 2019 17:14:24 +0300
Subject: [PATCH] + client: handle DHCP reset

---
 client/src/__locales/en.json                 |  3 +-
 client/src/actions/index.js                  | 16 +++++
 client/src/api/Api.js                        |  6 ++
 client/src/components/Settings/Dhcp/Form.js  | 64 ++++++++++++++------
 client/src/components/Settings/Dhcp/index.js | 44 ++++++++------
 client/src/containers/Dhcp.js                |  2 +
 client/src/reducers/index.js                 | 10 +++
 7 files changed, 109 insertions(+), 36 deletions(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 9ff11424..6b0bc715 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -17,7 +17,7 @@
     "dhcp_leases": "DHCP leases",
     "dhcp_static_leases": "DHCP static leases",
     "dhcp_leases_not_found": "No DHCP leases found",
-    "dhcp_config_saved": "Saved DHCP server config",
+    "dhcp_config_saved": "DHCP config successfully saved",
     "form_error_required": "Required field",
     "form_error_ip4_format": "Invalid IPv4 format",
     "form_error_ip6_format": "Invalid IPv6 format",
@@ -45,6 +45,7 @@
     "dhcp_new_static_lease": "New static lease",
     "dhcp_static_leases_not_found": "No DHCP static leases found",
     "dhcp_add_static_lease": "Add static lease",
+    "dhcp_reset": "Are you sure you want to reset DHCP config?",
     "delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
     "form_enter_hostname": "Enter hostname",
     "error_details": "Error details",
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 28c2a713..d01130f6 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -470,6 +470,22 @@ export const toggleDhcp = values => async (dispatch) => {
     }
 };
 
+export const resetDhcpRequest = createAction('RESET_DHCP_REQUEST');
+export const resetDhcpSuccess = createAction('RESET_DHCP_SUCCESS');
+export const resetDhcpFailure = createAction('RESET_DHCP_FAILURE');
+
+export const resetDhcp = () => async (dispatch) => {
+    dispatch(resetDhcpRequest());
+    try {
+        const status = await apiClient.resetDhcp();
+        dispatch(resetDhcpSuccess(status));
+        dispatch(addSuccessToast('dhcp_config_saved'));
+    } catch (error) {
+        dispatch(addErrorToast({ error }));
+        dispatch(resetDhcpFailure());
+    }
+};
+
 export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL');
 
 export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index 72d6d527..2393a89d 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -248,6 +248,7 @@ class Api {
     DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
     DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
     DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
+    DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
 
     getDhcpStatus() {
         const { path, method } = this.DHCP_STATUS;
@@ -295,6 +296,11 @@ class Api {
         return this.makeRequest(path, method, parameters);
     }
 
+    resetDhcp() {
+        const { path, method } = this.DHCP_RESET;
+        return this.makeRequest(path, method);
+    }
+
     // Installation
     INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
     INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js
index 86de6340..8bbef865 100644
--- a/client/src/components/Settings/Dhcp/Form.js
+++ b/client/src/components/Settings/Dhcp/Form.js
@@ -50,6 +50,23 @@ const renderInterfaceValues = (interfaceValues => (
     </ul>
 ));
 
+const clearFields = (change, resetDhcp, t) => {
+    const fields = {
+        interface_name: '',
+        gateway_ip: '',
+        subnet_mask: '',
+        range_start: '',
+        range_end: '',
+        lease_duration: 86400,
+    };
+
+    // eslint-disable-next-line no-alert
+    if (window.confirm(t('dhcp_reset'))) {
+        Object.keys(fields).forEach(field => change(field, fields[field]));
+        resetDhcp();
+    }
+};
+
 let Form = (props) => {
     const {
         t,
@@ -61,6 +78,8 @@ let Form = (props) => {
         interfaceValue,
         processingConfig,
         processingInterfaces,
+        resetDhcp,
+        change,
     } = props;
 
     return (
@@ -160,31 +179,42 @@ let Form = (props) => {
                 </div>
             </div>
 
-            <button
-                type="submit"
-                className="btn btn-success btn-standard"
-                disabled={submitting || invalid || processingConfig}
-            >
-                {t('save_config')}
-            </button>
+            <div className="btn-list">
+                <button
+                    type="submit"
+                    className="btn btn-success btn-standard"
+                    disabled={submitting || invalid || processingConfig}
+                >
+                    {t('save_config')}
+                </button>
+                <button
+                    type="button"
+                    className="btn btn-secondary btn-standart"
+                    disabled={submitting || processingConfig}
+                    onClick={() => clearFields(change, resetDhcp, t)}
+                >
+                    <Trans>reset_settings</Trans>
+                </button>
+            </div>
         </form>
     );
 };
 
 Form.propTypes = {
-    handleSubmit: PropTypes.func,
-    submitting: PropTypes.bool,
-    invalid: PropTypes.bool,
-    interfaces: PropTypes.object,
+    handleSubmit: PropTypes.func.isRequired,
+    submitting: PropTypes.bool.isRequired,
+    invalid: PropTypes.bool.isRequired,
+    interfaces: PropTypes.object.isRequired,
     interfaceValue: PropTypes.string,
-    initialValues: PropTypes.object,
-    processingConfig: PropTypes.bool,
-    processingInterfaces: PropTypes.bool,
-    enabled: PropTypes.bool,
-    t: PropTypes.func,
+    initialValues: PropTypes.object.isRequired,
+    processingConfig: PropTypes.bool.isRequired,
+    processingInterfaces: PropTypes.bool.isRequired,
+    enabled: PropTypes.bool.isRequired,
+    t: PropTypes.func.isRequired,
+    resetDhcp: PropTypes.func.isRequired,
+    change: PropTypes.func.isRequired,
 };
 
-
 const selector = formValueSelector('dhcpForm');
 
 Form = connect((state) => {
diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js
index 626c18d4..959922ce 100644
--- a/client/src/components/Settings/Dhcp/index.js
+++ b/client/src/components/Settings/Dhcp/index.js
@@ -154,7 +154,15 @@ class Dhcp extends Component {
     };
 
     render() {
-        const { t, dhcp } = this.props;
+        const {
+            t,
+            dhcp,
+            resetDhcp,
+            findActiveDhcp,
+            addStaticLease,
+            removeStaticLease,
+            toggleLeaseModal,
+        } = this.props;
         const statusButtonClass = classnames({
             'btn btn-primary btn-standard': true,
             'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
@@ -184,6 +192,7 @@ class Dhcp extends Component {
                                         processingConfig={dhcp.processingConfig}
                                         processingInterfaces={dhcp.processingInterfaces}
                                         enabled={enabled}
+                                        resetDhcp={resetDhcp}
                                     />
                                     <hr />
                                     <div className="card-actions mb-3">
@@ -191,9 +200,7 @@ class Dhcp extends Component {
                                         <button
                                             type="button"
                                             className={statusButtonClass}
-                                            onClick={() =>
-                                                this.props.findActiveDhcp(interface_name)
-                                            }
+                                            onClick={() => findActiveDhcp(interface_name)}
                                             disabled={
                                                 enabled || !interface_name || dhcp.processingConfig
                                             }
@@ -232,9 +239,9 @@ class Dhcp extends Component {
                                             <StaticLeases
                                                 staticLeases={dhcp.staticLeases}
                                                 isModalOpen={dhcp.isModalOpen}
-                                                addStaticLease={this.props.addStaticLease}
-                                                removeStaticLease={this.props.removeStaticLease}
-                                                toggleLeaseModal={this.props.toggleLeaseModal}
+                                                addStaticLease={addStaticLease}
+                                                removeStaticLease={removeStaticLease}
+                                                toggleLeaseModal={toggleLeaseModal}
                                                 processingAdding={dhcp.processingAdding}
                                                 processingDeleting={dhcp.processingDeleting}
                                             />
@@ -243,7 +250,7 @@ class Dhcp extends Component {
                                             <button
                                                 type="button"
                                                 className="btn btn-success btn-standard mt-3"
-                                                onClick={() => this.props.toggleLeaseModal()}
+                                                onClick={() => toggleLeaseModal()}
                                             >
                                                 <Trans>dhcp_add_static_lease</Trans>
                                             </button>
@@ -260,16 +267,17 @@ class Dhcp extends Component {
 }
 
 Dhcp.propTypes = {
-    dhcp: PropTypes.object,
-    toggleDhcp: PropTypes.func,
-    getDhcpStatus: PropTypes.func,
-    setDhcpConfig: PropTypes.func,
-    findActiveDhcp: PropTypes.func,
-    addStaticLease: PropTypes.func,
-    removeStaticLease: PropTypes.func,
-    toggleLeaseModal: PropTypes.func,
-    getDhcpInterfaces: PropTypes.func,
-    t: PropTypes.func,
+    dhcp: PropTypes.object.isRequired,
+    toggleDhcp: PropTypes.func.isRequired,
+    getDhcpStatus: PropTypes.func.isRequired,
+    setDhcpConfig: PropTypes.func.isRequired,
+    findActiveDhcp: PropTypes.func.isRequired,
+    addStaticLease: PropTypes.func.isRequired,
+    removeStaticLease: PropTypes.func.isRequired,
+    toggleLeaseModal: PropTypes.func.isRequired,
+    getDhcpInterfaces: PropTypes.func.isRequired,
+    t: PropTypes.func.isRequired,
+    resetDhcp: PropTypes.func.isRequired,
 };
 
 export default withNamespaces()(Dhcp);
diff --git a/client/src/containers/Dhcp.js b/client/src/containers/Dhcp.js
index 6f2b8f47..1f9d6b62 100644
--- a/client/src/containers/Dhcp.js
+++ b/client/src/containers/Dhcp.js
@@ -8,6 +8,7 @@ import {
     toggleLeaseModal,
     addStaticLease,
     removeStaticLease,
+    resetDhcp,
 } from '../actions';
 import Dhcp from '../components/Settings/Dhcp';
 
@@ -28,6 +29,7 @@ const mapDispatchToProps = {
     toggleLeaseModal,
     addStaticLease,
     removeStaticLease,
+    resetDhcp,
 };
 
 export default connect(
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index 0e8ff407..3eca36a6 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -289,6 +289,16 @@ const dhcp = handleActions(
             return newState;
         },
 
+        [actions.resetDhcpRequest]: state => ({ ...state, processingReset: true }),
+        [actions.resetDhcpFailure]: state => ({ ...state, processingReset: false }),
+        [actions.resetDhcpSuccess]: state => ({
+            ...state,
+            processingReset: false,
+            config: {
+                enabled: false,
+            },
+        }),
+
         [actions.toggleLeaseModal]: (state) => {
             const newState = {
                 ...state,