From 2a5b0b8da1f85727e36a4601c51bdfb79ef2583f Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Mon, 14 Sep 2020 20:16:46 +0300
Subject: [PATCH 1/2] - client: Allow change minimum TTL override in UI

Close #2091 #2094 #2056

Squashed commit of the following:

commit a84384bb409bfe60c4bd6477b2249c4431aa3b63
Merge: cdc5f27f a22db5f3
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 19:59:47 2020 +0300

    Merge branch 'master' into fix/2091

commit cdc5f27f279f33c7d988f2927adc172e77e0a6af
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 15:41:00 2020 +0300

    Fix uint32 fields validation

commit 0c6fcb90f9741ae8a33bf6c4d53bd954f2033a88
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 14:43:20 2020 +0300

    Validate DNS cache configuration DNS values unit32 range

commit 1f90a1fcbc04f6c7ffb75b453e5c67e117d1c5bf
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 12:11:39 2020 +0300

    Remove the limit on cache-min-ttl

commit 72e961034cc5752a50a4afc57c7be6a93d652f7d
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Sep 11 16:50:19 2020 +0300

    Fix translation

commit 6aebf4b87bb806ac0729b40418ba6056b3f71afa
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Sep 11 12:53:01 2020 +0300

    - client: Allow change minimum TTL override in UI
---
 client/src/__locales/en.json                  |  7 +-
 .../components/Settings/Dhcp/FormDHCPv4.js    |  8 +--
 .../components/Settings/Dhcp/FormDHCPv6.js    | 13 ++--
 .../src/components/Settings/Dns/Cache/Form.js | 65 +++++++++----------
 .../components/Settings/Dns/Cache/index.js    | 11 ++++
 .../components/Settings/Dns/Config/Form.js    |  7 +-
 client/src/helpers/constants.js               | 13 +++-
 client/src/helpers/validators.js              | 23 -------
 8 files changed, 69 insertions(+), 78 deletions(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index ece13f64..a5e0208b 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -253,7 +253,7 @@
     "rate_limit": "Rate limit",
     "edns_enable": "Enable EDNS Client Subnet",
     "edns_cs_desc": "If enabled, AdGuard Home will be sending clients' subnets to the DNS servers.",
-    "rate_limit_desc": "The number of requests per second that a single client is allowed to make (0: unlimited)",
+    "rate_limit_desc": "The number of requests per second that a single client is allowed to make (setting it to 0 means unlimited)",
     "blocking_ipv4_desc": "IP address to be returned for a blocked A request",
     "blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
     "blocking_mode_default": "Default: Respond with REFUSED when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule",
@@ -564,10 +564,9 @@
     "enter_cache_size": "Enter cache size",
     "enter_cache_ttl_min_override": "Enter minimum TTL",
     "enter_cache_ttl_max_override": "Enter maximum TTL",
-    "cache_ttl_min_override_desc": "Override TTL value (minimum) received from upstream server. This value can't larger than 3600 (1 hour)",
+    "cache_ttl_min_override_desc": "Override TTL value (minimum) received from upstream server",
     "cache_ttl_max_override_desc": "Override TTL value (maximum) received from upstream server",
-    "min_exceeds_max_value": "Minimum value exceeds maximum value",
-    "value_not_larger_than": "Value can't be larger than {{maximum}}",
+    "ttl_cache_validation": "Minimum cache TTL value must be less than or equal to the maximum value",
     "filter_category_general": "General",
     "filter_category_security": "Security",
     "filter_category_regional": "Regional",
diff --git a/client/src/components/Settings/Dhcp/FormDHCPv4.js b/client/src/components/Settings/Dhcp/FormDHCPv4.js
index b938b6f0..873e7696 100644
--- a/client/src/components/Settings/Dhcp/FormDHCPv4.js
+++ b/client/src/components/Settings/Dhcp/FormDHCPv4.js
@@ -8,10 +8,9 @@ import {
     renderInputField,
     toNumber,
 } from '../../../helpers/form';
-import { FORM_NAME } from '../../../helpers/constants';
+import { FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
 import {
     validateIpv4,
-    validateIsPositiveValue,
     validateRequiredValue,
     validateIpv4RangeEnd,
 } from '../../../helpers/validators';
@@ -110,9 +109,10 @@ const FormDHCPv4 = ({
                         type="number"
                         className="form-control"
                         placeholder={t(ipv4placeholders.lease_duration)}
-                        validate={[validateIsPositiveValue, validateRequired]}
+                        validate={validateRequired}
                         normalize={toNumber}
-                        min={0}
+                        min={1}
+                        max={UINT32_RANGE.MAX}
                         disabled={!isInterfaceIncludesIpv4}
                     />
                 </div>
diff --git a/client/src/components/Settings/Dhcp/FormDHCPv6.js b/client/src/components/Settings/Dhcp/FormDHCPv6.js
index 80219fd6..e37a6213 100644
--- a/client/src/components/Settings/Dhcp/FormDHCPv6.js
+++ b/client/src/components/Settings/Dhcp/FormDHCPv6.js
@@ -8,12 +8,8 @@ import {
     renderInputField,
     toNumber,
 } from '../../../helpers/form';
-import { FORM_NAME } from '../../../helpers/constants';
-import {
-    validateIpv6,
-    validateIsPositiveValue,
-    validateRequiredValue,
-} from '../../../helpers/validators';
+import { FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
+import { validateIpv6, validateRequiredValue } from '../../../helpers/validators';
 
 const FormDHCPv6 = ({
     handleSubmit,
@@ -86,9 +82,10 @@ const FormDHCPv6 = ({
                     type="number"
                     className="form-control"
                     placeholder={t(ipv6placeholders.lease_duration)}
-                    validate={[validateIsPositiveValue, validateRequired]}
+                    validate={validateRequired}
                     normalizeOnBlur={toNumber}
-                    min={0}
+                    min={1}
+                    max={UINT32_RANGE.MAX}
                     disabled={!isInterfaceIncludesIpv6}
                 />
             </div>
diff --git a/client/src/components/Settings/Dns/Cache/Form.js b/client/src/components/Settings/Dns/Cache/Form.js
index c7b2d6ed..f17310c9 100644
--- a/client/src/components/Settings/Dns/Cache/Form.js
+++ b/client/src/components/Settings/Dns/Cache/Form.js
@@ -4,32 +4,30 @@ import { Field, reduxForm } from 'redux-form';
 import { Trans, useTranslation } from 'react-i18next';
 import { shallowEqual, useSelector } from 'react-redux';
 import { renderInputField, toNumber } from '../../../../helpers/form';
-import { validateBiggerOrEqualZeroValue, getMaxValueValidator, validateRequiredValue } from '../../../../helpers/validators';
-import { FORM_NAME, SECONDS_IN_HOUR } from '../../../../helpers/constants';
+import { validateRequiredValue } from '../../../../helpers/validators';
+import { CACHE_CONFIG_FIELDS, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
 
-const validateMaxValue3600 = getMaxValueValidator(SECONDS_IN_HOUR);
-
-const getInputFields = ({ validateRequiredValue, validateMaxValue3600 }) => [{
-    name: 'cache_size',
-    title: 'cache_size',
-    description: 'cache_size_desc',
-    placeholder: 'enter_cache_size',
-    validate: validateRequiredValue,
-},
-{
-    name: 'cache_ttl_min',
-    title: 'cache_ttl_min_override',
-    description: 'cache_ttl_min_override_desc',
-    placeholder: 'enter_cache_ttl_min_override',
-    max: SECONDS_IN_HOUR,
-    validate: validateMaxValue3600,
-},
-{
-    name: 'cache_ttl_max',
-    title: 'cache_ttl_max_override',
-    description: 'cache_ttl_max_override_desc',
-    placeholder: 'enter_cache_ttl_max_override',
-}];
+const getInputFields = (validateRequiredValue) => [
+    {
+        name: CACHE_CONFIG_FIELDS.cache_size,
+        title: 'cache_size',
+        description: 'cache_size_desc',
+        placeholder: 'enter_cache_size',
+        validate: validateRequiredValue,
+    },
+    {
+        name: CACHE_CONFIG_FIELDS.cache_ttl_min,
+        title: 'cache_ttl_min_override',
+        description: 'cache_ttl_min_override_desc',
+        placeholder: 'enter_cache_ttl_min_override',
+    },
+    {
+        name: CACHE_CONFIG_FIELDS.cache_ttl_max,
+        title: 'cache_ttl_max_override',
+        description: 'cache_ttl_max_override_desc',
+        placeholder: 'enter_cache_ttl_max_override',
+    },
+];
 
 const Form = ({
     handleSubmit, submitting, invalid,
@@ -41,17 +39,16 @@ const Form = ({
         cache_ttl_max, cache_ttl_min,
     } = useSelector((state) => state.form[FORM_NAME.CACHE].values, shallowEqual);
 
-    const minExceedsMax = cache_ttl_min > cache_ttl_max;
+    const minExceedsMax = typeof cache_ttl_min === 'number'
+            && typeof cache_ttl_max === 'number'
+            && cache_ttl_min > cache_ttl_max;
 
-    const INPUTS_FIELDS = getInputFields({
-        validateRequiredValue,
-        validateMaxValue3600,
-    });
+    const INPUTS_FIELDS = getInputFields(validateRequiredValue);
 
     return <form onSubmit={handleSubmit}>
         <div className="row">
             {INPUTS_FIELDS.map(({
-                name, title, description, placeholder, validate, max,
+                name, title, description, placeholder, validate, min = 0, max = UINT32_RANGE.MAX,
             }) => <div className="col-12" key={name}>
                 <div className="col-12 col-md-7 p-0">
                     <div className="form__group form__group--settings">
@@ -66,15 +63,15 @@ const Form = ({
                             disabled={processingSetConfig}
                             normalize={toNumber}
                             className="form-control"
-                            validate={[validateBiggerOrEqualZeroValue].concat(validate || [])}
-                            min={0}
+                            validate={validate}
+                            min={min}
                             max={max}
                         />
                     </div>
                 </div>
             </div>)}
             {minExceedsMax
-            && <span className="text-danger pl-3 pb-3">{t('min_exceeds_max_value')}</span>}
+            && <span className="text-danger pl-3 pb-3">{t('ttl_cache_validation')}</span>}
         </div>
         <button
             type="submit"
diff --git a/client/src/components/Settings/Dns/Cache/index.js b/client/src/components/Settings/Dns/Cache/index.js
index 22b8b7b6..391c17e9 100644
--- a/client/src/components/Settings/Dns/Cache/index.js
+++ b/client/src/components/Settings/Dns/Cache/index.js
@@ -1,10 +1,12 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 import { shallowEqual, useDispatch, useSelector } from 'react-redux';
+import { change } from 'redux-form';
 import Card from '../../../ui/Card';
 import Form from './Form';
 import { setDnsConfig } from '../../../../actions/dnsConfig';
 import { selectCompletedFields } from '../../../../helpers/helpers';
+import { CACHE_CONFIG_FIELDS, FORM_NAME } from '../../../../helpers/constants';
 
 const CacheConfig = () => {
     const { t } = useTranslation();
@@ -15,6 +17,15 @@ const CacheConfig = () => {
 
     const handleFormSubmit = (values) => {
         const completedFields = selectCompletedFields(values);
+
+        Object.entries(completedFields).forEach(([k, v]) => {
+            if ((k === CACHE_CONFIG_FIELDS.cache_ttl_min
+                    || k === CACHE_CONFIG_FIELDS.cache_ttl_max)
+                    && v === 0) {
+                dispatch(change(FORM_NAME.CACHE, k, ''));
+            }
+        });
+
         dispatch(setDnsConfig(completedFields));
     };
 
diff --git a/client/src/components/Settings/Dns/Config/Form.js b/client/src/components/Settings/Dns/Config/Form.js
index 0d4a23ee..ecdd2439 100644
--- a/client/src/components/Settings/Dns/Config/Form.js
+++ b/client/src/components/Settings/Dns/Config/Form.js
@@ -10,12 +10,11 @@ import {
     toNumber,
 } from '../../../../helpers/form';
 import {
-    validateBiggerOrEqualZeroValue,
     validateIpv4,
     validateIpv6,
     validateRequiredValue,
 } from '../../../../helpers/validators';
-import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
+import { BLOCKING_MODES, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
 
 const checkboxes = [
     {
@@ -87,7 +86,9 @@ const Form = ({
                         className="form-control"
                         placeholder={t('form_enter_rate_limit')}
                         normalize={toNumber}
-                        validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
+                        validate={validateRequiredValue}
+                        min={UINT32_RANGE.MIN}
+                        max={UINT32_RANGE.MAX}
                     />
                 </div>
             </div>
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 86ed844d..2ab176ea 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -506,9 +506,12 @@ export const FORM_NAME = {
 export const SMALL_SCREEN_SIZE = 767;
 export const MEDIUM_SCREEN_SIZE = 1023;
 
-export const SECONDS_IN_HOUR = 60 * 60;
+export const SECONDS_IN_DAY = 60 * 60 * 24;
 
-export const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
+export const UINT32_RANGE = {
+    MIN: 0,
+    MAX: 4294967295,
+};
 
 export const DHCP_VALUES_PLACEHOLDERS = {
     ipv4: {
@@ -559,3 +562,9 @@ export const ADDRESS_TYPES = {
     CIDR: 'CIDR',
     UNKNOWN: 'UNKNOWN',
 };
+
+export const CACHE_CONFIG_FIELDS = {
+    cache_size: 'cache_size',
+    cache_ttl_min: 'cache_ttl_min',
+    cache_ttl_max: 'cache_ttl_max',
+};
diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js
index 64c5fa49..9b853120 100644
--- a/client/src/helpers/validators.js
+++ b/client/src/helpers/validators.js
@@ -1,4 +1,3 @@
-import i18next from 'i18next';
 import {
     MAX_PORT,
     R_CIDR,
@@ -28,17 +27,6 @@ export const validateRequiredValue = (value) => {
     return 'form_error_required';
 };
 
-/**
- * @param maximum {number}
- * @returns {(value:number) => undefined|string}
- */
-export const getMaxValueValidator = (maximum) => (value) => {
-    if (value && value > maximum) {
-        return i18next.t('value_not_larger_than', { maximum });
-    }
-    return undefined;
-};
-
 /**
  * @param value {string}
  * @returns {undefined|string}
@@ -122,17 +110,6 @@ export const validateMac = (value) => {
     return undefined;
 };
 
-/**
- * @param value {number}
- * @returns {undefined|string}
- */
-export const validateIsPositiveValue = (value) => {
-    if ((value || value === 0) && value <= 0) {
-        return 'form_error_positive';
-    }
-    return undefined;
-};
-
 /**
  * @param value {number}
  * @returns {boolean|*}

From 96512433477a6b32ac4e51e73899f82dc1c2a6e1 Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Tue, 15 Sep 2020 10:30:34 +0300
Subject: [PATCH 2/2] - client: Make dashboard card height constant

Close #2039

Squashed commit of the following:

commit 1d6dd461558403ea0ba813d5f665ecbaab9cd8ed
Merge: 6ca80c91 2a5b0b8d
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Sep 15 10:18:19 2020 +0300

    Merge branch 'master' into fix/2039

commit 6ca80c9171da61a4bc02cec409299d0cfb96da49
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 19:58:43 2020 +0300

    Fix mobile media query

commit 239c9ab29aaaef200bd2289fa116507ea5c2bf4c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Sep 14 13:59:39 2020 +0300

    - client: Make dashboard card height constant
---
 client/src/components/Dashboard/index.js | 3 +--
 client/src/components/ui/Card.css        | 8 +++++++-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js
index 07d8f94f..4d229ffb 100644
--- a/client/src/components/Dashboard/index.js
+++ b/client/src/components/Dashboard/index.js
@@ -82,7 +82,7 @@ const Dashboard = ({
             </div>
         </PageTitle>
         {statsProcessing && <Loading />}
-        {!statsProcessing && <div className="row row-cards">
+        {!statsProcessing && <div className="row row-cards dashboard">
             <div className="col-lg-12">
                 <Statistics
                         interval={stats.interval}
@@ -100,7 +100,6 @@ const Dashboard = ({
             <div className="col-lg-6">
                 <Counters
                         subtitle={subtitle}
-
                         refreshButton={refreshButton}
                 />
             </div>
diff --git a/client/src/components/ui/Card.css b/client/src/components/ui/Card.css
index 5930d881..eb8a7963 100644
--- a/client/src/components/ui/Card.css
+++ b/client/src/components/ui/Card.css
@@ -19,7 +19,7 @@
     max-height: 17.5rem;
 }
 
-.card-table-overflow--limited.clients__table {
+.dashboard .card-table-overflow--limited {
     max-height: 18rem;
 }
 
@@ -122,6 +122,12 @@
     }
 }
 
+@media (min-width: 992px) {
+    .dashboard .card:not(.card--full) {
+        height: 22rem;
+    }
+}
+
 .card .logs__cell--red {
     background-color: #fff4f2;
 }