From d38b58cd85d663ecadaf785d53dc16666444fa9a Mon Sep 17 00:00:00 2001
From: Simon Zolin <s.zolin@adguard.com>
Date: Tue, 14 Jul 2020 17:08:53 +0300
Subject: [PATCH 1/7] * POST /control/version.json: change response when update
 is disabled

---
 AGHTechDoc.md          | 6 +++++-
 home/control_update.go | 4 ++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 3e3574e2..527a7eaa 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -344,10 +344,14 @@ Response:
 
 If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
 
-Response with empty body:
+Response when auto-update is disabled by command-line aragument:
 
 	200 OK
 
+	{
+		"disabled":true
+	}
+
 It means that update check is disabled by user.  UI should do nothing.
 
 
diff --git a/home/control_update.go b/home/control_update.go
index d734f4d1..566b8327 100644
--- a/home/control_update.go
+++ b/home/control_update.go
@@ -41,6 +41,10 @@ type getVersionJSONRequest struct {
 // Get the latest available version from the Internet
 func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
 	if Context.disableUpdate {
+		resp := make(map[string]interface{})
+		resp["disabled"] = true
+		d, _ := json.Marshal(resp)
+		_, _ = w.Write(d)
 		return
 	}
 

From 54693bb158cff978236adef1291966525da08965 Mon Sep 17 00:00:00 2001
From: Simon Zolin <s.zolin@adguard.com>
Date: Tue, 14 Jul 2020 17:10:34 +0300
Subject: [PATCH 2/7] minor

---
 AGHTechDoc.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 527a7eaa..e70d1609 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -344,7 +344,7 @@ Response:
 
 If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
 
-Response when auto-update is disabled by command-line aragument:
+Response when auto-update is disabled by command-line argument:
 
 	200 OK
 

From 61981a927babe1ec771c01c13e2d503e3dc957e5 Mon Sep 17 00:00:00 2001
From: ArtemBaskal <a.baskal@adguard.com>
Date: Tue, 14 Jul 2020 18:42:24 +0300
Subject: [PATCH 3/7] Change getVersionSuccess reducer

---
 client/src/reducers/index.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index 642d8503..cc25a4b9 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -82,7 +82,7 @@ const dashboard = handleActions(
         [actions.getVersionSuccess]: (state, { payload }) => {
             const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
 
-            if (payload && isVersionGreater(currentVersion, payload.new_version)) {
+            if (!payload.disabled && isVersionGreater(currentVersion, payload.new_version)) {
                 const {
                     announcement_url: announcementUrl,
                     new_version: newVersion,
@@ -96,7 +96,7 @@ const dashboard = handleActions(
                     canAutoUpdate,
                     isUpdateAvailable: true,
                     processingVersion: false,
-                    checkUpdateFlag: !!payload,
+                    checkUpdateFlag: !payload.disabled,
                 };
                 return newState;
             }
@@ -104,6 +104,7 @@ const dashboard = handleActions(
             return {
                 ...state,
                 processingVersion: false,
+                checkUpdateFlag: !payload.disabled,
             };
         },
 

From a32c1f2ee01450d778749edea392a14184c6f678 Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Wed, 15 Jul 2020 12:35:37 +0300
Subject: [PATCH 4/7] - client: Fix client Access settings normalization Close
 #1820

Squashed commit of the following:

commit 5aadec2e6e126588313ff006d6f95223ba19a526
Merge: a4db6b42 95f41285
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 11:15:31 2020 +0300

    Merge branch 'master' into fix/1820

commit a4db6b42ab9cbf43d96c783a72a99e0a2c594108
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 19:08:09 2020 +0300

    Remove textarea comma splitting

commit bb34797aac6602b405941dbd90fe6a81b663bb92
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 18:21:18 2020 +0300

    Fix client Access settings normalization

commit ac4fb536514f54c5722077d78dbbd981c4e906a8
Merge: 0c758ddc b9fca8d0
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 18:14:38 2020 +0300

    Merge branch 'master' into fix/1820

commit 0c758ddcd738136b92e6f947a8068ecc59f7ec25
Merge: 15650db3 f5a1f311
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 3 11:22:00 2020 +0300

    Merge branch 'master' into fix/1820

commit 15650db35323009001fd427a74a312705b54ac86
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jun 29 12:01:51 2020 +0300

    '- client: Don't normalise disallowed domains'
---
 client/src/actions/access.js                  |  8 ++---
 client/src/actions/dnsConfig.js               |  6 ++--
 client/src/actions/index.js                   |  6 ++--
 .../Settings/Clients/ClientsTable.js          |  4 +--
 .../components/Settings/Dns/Access/Form.js    | 13 ++++++--
 .../components/Settings/Dns/Upstream/Form.js  | 13 +++++---
 client/src/components/Settings/Dns/index.js   |  5 ++-
 client/src/helpers/helpers.js                 | 31 ++++++++++++-------
 8 files changed, 54 insertions(+), 32 deletions(-)

diff --git a/client/src/actions/access.js b/client/src/actions/access.js
index 8e1efab0..e04daac0 100644
--- a/client/src/actions/access.js
+++ b/client/src/actions/access.js
@@ -2,9 +2,9 @@ import { createAction } from 'redux-actions';
 import i18next from 'i18next';
 
 import apiClient from '../api/Api';
-import { normalizeTextarea } from '../helpers/helpers';
 import { addErrorToast, addSuccessToast } from './toasts';
 import { BLOCK_ACTIONS } from '../helpers/constants';
+import { splitByNewLine } from '../helpers/helpers';
 
 export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
 export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
@@ -31,9 +31,9 @@ export const setAccessList = (config) => async (dispatch) => {
         const { allowed_clients, disallowed_clients, blocked_hosts } = config;
 
         const values = {
-            allowed_clients: normalizeTextarea(allowed_clients),
-            disallowed_clients: normalizeTextarea(disallowed_clients),
-            blocked_hosts: normalizeTextarea(blocked_hosts),
+            allowed_clients: splitByNewLine(allowed_clients),
+            disallowed_clients: splitByNewLine(disallowed_clients),
+            blocked_hosts: splitByNewLine(blocked_hosts),
         };
 
         await apiClient.setAccessList(values);
diff --git a/client/src/actions/dnsConfig.js b/client/src/actions/dnsConfig.js
index 947638c3..c599ca8d 100644
--- a/client/src/actions/dnsConfig.js
+++ b/client/src/actions/dnsConfig.js
@@ -1,7 +1,7 @@
 import { createAction } from 'redux-actions';
 
 import apiClient from '../api/Api';
-import { normalizeTextarea } from '../helpers/helpers';
+import { splitByNewLine } from '../helpers/helpers';
 import { addErrorToast, addSuccessToast } from './toasts';
 
 export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
@@ -30,11 +30,11 @@ export const setDnsConfig = (config) => async (dispatch) => {
 
         let hasDnsSettings = false;
         if (Object.prototype.hasOwnProperty.call(data, 'bootstrap_dns')) {
-            data.bootstrap_dns = normalizeTextarea(config.bootstrap_dns);
+            data.bootstrap_dns = splitByNewLine(config.bootstrap_dns);
             hasDnsSettings = true;
         }
         if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
-            data.upstream_dns = normalizeTextarea(config.upstream_dns);
+            data.upstream_dns = splitByNewLine(config.upstream_dns);
             hasDnsSettings = true;
         }
 
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 2cef57e6..3d3f1b63 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -2,7 +2,7 @@ import { createAction } from 'redux-actions';
 import i18next from 'i18next';
 import axios from 'axios';
 
-import { isVersionGreater, normalizeTextarea, sortClients } from '../helpers/helpers';
+import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
 import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
 import { getTlsStatus } from './encryption';
 import apiClient from '../api/Api';
@@ -279,8 +279,8 @@ export const testUpstream = (config) => async (dispatch) => {
     dispatch(testUpstreamRequest());
     try {
         const values = { ...config };
-        values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
-        values.upstream_dns = normalizeTextarea(values.upstream_dns);
+        values.bootstrap_dns = splitByNewLine(values.bootstrap_dns);
+        values.upstream_dns = splitByNewLine(values.upstream_dns);
 
         const upstreamResponse = await apiClient.testUpstream(values);
         const testMessages = Object.keys(upstreamResponse)
diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js
index e767cfb9..a179ac30 100644
--- a/client/src/components/Settings/Clients/ClientsTable.js
+++ b/client/src/components/Settings/Clients/ClientsTable.js
@@ -4,7 +4,7 @@ import { Trans, withTranslation } from 'react-i18next';
 import ReactTable from 'react-table';
 
 import { MODAL_TYPE } from '../../../helpers/constants';
-import { normalizeTextarea } from '../../../helpers/helpers';
+import { splitByNewLine } from '../../../helpers/helpers';
 import Card from '../../ui/Card';
 import Modal from './Modal';
 import CellWrap from '../../ui/CellWrap';
@@ -30,7 +30,7 @@ class ClientsTable extends Component {
             }
 
             if (values.upstreams && typeof values.upstreams === 'string') {
-                config.upstreams = normalizeTextarea(values.upstreams);
+                config.upstreams = splitByNewLine(values.upstreams);
             } else {
                 config.upstreams = [];
             }
diff --git a/client/src/components/Settings/Dns/Access/Form.js b/client/src/components/Settings/Dns/Access/Form.js
index 8edc3189..4f8342f0 100644
--- a/client/src/components/Settings/Dns/Access/Form.js
+++ b/client/src/components/Settings/Dns/Access/Form.js
@@ -4,7 +4,10 @@ import { Field, reduxForm } from 'redux-form';
 import { Trans, withTranslation } from 'react-i18next';
 import flow from 'lodash/flow';
 import { renderTextareaField } from '../../../../helpers/form';
-import { normalizeMultiline } from '../../../../helpers/helpers';
+import {
+    trimMultilineString,
+    removeEmptyLines,
+} from '../../../../helpers/helpers';
 import { FORM_NAME } from '../../../../helpers/constants';
 
 const fields = [
@@ -12,16 +15,19 @@ const fields = [
         id: 'allowed_clients',
         title: 'access_allowed_title',
         subtitle: 'access_allowed_desc',
+        normalizeOnBlur: removeEmptyLines,
     },
     {
         id: 'disallowed_clients',
         title: 'access_disallowed_title',
         subtitle: 'access_disallowed_desc',
+        normalizeOnBlur: trimMultilineString,
     },
     {
         id: 'blocked_hosts',
         title: 'access_blocked_title',
         subtitle: 'access_blocked_desc',
+        normalizeOnBlur: removeEmptyLines,
     },
 ];
 
@@ -31,7 +37,7 @@ const Form = (props) => {
     } = props;
 
     const renderField = ({
-        id, title, subtitle, disabled = processingSet,
+        id, title, subtitle, disabled = processingSet, normalizeOnBlur,
     }) => <div key={id} className="form__group mb-5">
         <label className="form__label form__label--with-desc" htmlFor={id}>
             <Trans>{title}</Trans>
@@ -46,7 +52,7 @@ const Form = (props) => {
             type="text"
             className="form-control form-control--textarea font-monospace"
             disabled={disabled}
-            normalizeOnBlur={id === 'disallowed_clients' ? normalizeMultiline : undefined}
+            normalizeOnBlur={normalizeOnBlur}
         />
     </div>;
 
@@ -55,6 +61,7 @@ const Form = (props) => {
         title: PropTypes.string,
         subtitle: PropTypes.string,
         disabled: PropTypes.bool,
+        normalizeOnBlur: PropTypes.func,
     };
 
     return (
diff --git a/client/src/components/Settings/Dns/Upstream/Form.js b/client/src/components/Settings/Dns/Upstream/Form.js
index 99ae9a95..8174b54e 100644
--- a/client/src/components/Settings/Dns/Upstream/Form.js
+++ b/client/src/components/Settings/Dns/Upstream/Form.js
@@ -6,9 +6,10 @@ import { Trans, useTranslation } from 'react-i18next';
 import classnames from 'classnames';
 
 import Examples from './Examples';
-import { renderRadioField } from '../../../../helpers/form';
+import { renderRadioField, renderTextareaField } from '../../../../helpers/form';
 import { DNS_REQUEST_OPTIONS, FORM_NAME } from '../../../../helpers/constants';
 import { testUpstream } from '../../../../actions';
+import { removeEmptyLines } from '../../../../helpers/helpers';
 
 const getInputFields = () => [{
     // eslint-disable-next-line react/display-name
@@ -17,9 +18,10 @@ const getInputFields = () => [{
     </label>,
     name: 'upstream_dns',
     type: 'text',
-    component: 'textarea',
+    component: renderTextareaField,
     className: 'form-control form-control--textarea font-monospace',
     placeholder: 'upstream_dns',
+    normalizeOnBlur: removeEmptyLines,
 },
 {
     name: 'upstream_mode',
@@ -69,7 +71,8 @@ const Form = ({
     return <form onSubmit={handleSubmit}>
         <div className="row">
             {INPUT_FIELDS.map(({
-                name, component, type, className, placeholder, getTitle, subtitle, disabled, value,
+                name, component, type, className, placeholder,
+                getTitle, subtitle, disabled, value, normalizeOnBlur,
             }) => <div className="col-12 mb-4" key={placeholder}>
                 {typeof getTitle === 'function' && getTitle()}
                 <Field
@@ -82,6 +85,7 @@ const Form = ({
                     placeholder={t(placeholder)}
                     subtitle={t(subtitle)}
                     disabled={processingSetConfig || processingTestUpstream || disabled}
+                    normalizeOnBlur={normalizeOnBlur}
                 />
             </div>)}
             <div className="col-12">
@@ -101,11 +105,12 @@ const Form = ({
                 <Field
                     id="bootstrap_dns"
                     name="bootstrap_dns"
-                    component="textarea"
+                    component={renderTextareaField}
                     type="text"
                     className="form-control form-control--textarea form-control--textarea-small font-monospace"
                     placeholder={t('bootstrap_dns')}
                     disabled={processingSetConfig}
+                    normalizeOnBlur={removeEmptyLines}
                 />
             </div>
         </div>
diff --git a/client/src/components/Settings/Dns/index.js b/client/src/components/Settings/Dns/index.js
index 41993f89..03a9e25b 100644
--- a/client/src/components/Settings/Dns/index.js
+++ b/client/src/components/Settings/Dns/index.js
@@ -45,7 +45,10 @@ const Dns = (props) => {
                         dnsConfig={dnsConfig}
                         setDnsConfig={setDnsConfig}
                     />
-                    <Access access={access} setAccessList={setAccessList} />
+                    <Access
+                        access={access}
+                        setAccessList={setAccessList}
+                    />
                 </>}
         </>
     );
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index e075a3c5..a6426ba5 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -306,15 +306,26 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
     }
 };
 
-export const normalizeTextarea = (text) => {
-    if (!text) {
-        return [];
-    }
+/**
+ * @param {string} text
+ * @returns []string
+ */
+export const splitByNewLine = (text) => text.split('\n')
+    .filter((n) => n.trim());
 
-    return text.replace(/[;, ]/g, '\n')
-        .split('\n')
-        .filter((n) => n);
-};
+/**
+ * @param {string} text
+ * @returns {string}
+ */
+export const trimMultilineString = (text) => splitByNewLine(text)
+    .map((line) => line.trim()).join('\n');
+
+/**
+ * @param {string} text
+ * @returns {string}
+ */
+export const removeEmptyLines = (text) => splitByNewLine(text)
+    .join('\n');
 
 /**
  * Normalizes the topClients array
@@ -533,10 +544,6 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => {
     return acc;
 }, {});
 
-export const normalizeMultiline = (multiline) => `${normalizeTextarea(multiline)
-    .map((line) => line.trim())
-    .join('\n')}\n`;
-
 /**
  * @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
  * @param cidr {array} ipaddr.js CIDR array

From 38366ba801063650742ccda186231b19821003c9 Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Wed, 15 Jul 2020 12:49:08 +0300
Subject: [PATCH 5/7] Setup pre-commit lint hook

Squashed commit of the following:

commit 02591b74c184faf7f7156e95cf05a78fb0ea22a7
Merge: 4057c8ae a32c1f2e
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 12:36:20 2020 +0300

    Merge branch 'master' into feature/git-hooks

commit 4057c8ae117dfb5de493769dbf1577c8d59035a4
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 20:04:38 2020 +0300

    Review changes

commit 2400ab77d9e0e3f7b62b0ffd64aeccf369ff84cd
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 16:27:14 2020 +0300

    Add lint-js and lint-go to .PHONY

commit 8a4efc2cb4f2d53ebea4b88b8182e4c1eb7812eb
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jul 14 15:47:14 2020 +0300

    Run linter only if corresponding file extension is changed

commit 8e2e110e9c9c3f865503cf3c0cd3e31dd4579b71
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 19:07:42 2020 +0300

    Setup pre-commit lint hooks
---
 .githooks/pre-commit | 12 ++++++++++++
 Makefile             | 18 +++++++++++++-----
 README.md            |  5 ++++-
 3 files changed, 29 insertions(+), 6 deletions(-)
 create mode 100755 .githooks/pre-commit

diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 00000000..d933e462
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e;
+git diff --cached --name-only | grep -q '.js$' && make lint-js;
+
+found=0
+git diff --cached --name-only | grep -q '.go$' && found=1
+if [ $found == 1 ]; then
+	make lint-go || exit 1
+	go test ./... || exit 1
+fi
+
+exit 0;
diff --git a/Makefile b/Makefile
index 7c3faeaf..73567b91 100644
--- a/Makefile
+++ b/Makefile
@@ -88,9 +88,12 @@ ifndef DOCKER_IMAGE_NAME
 $(error DOCKER_IMAGE_NAME value is not set)
 endif
 
-.PHONY: all build client client-watch docker lint test dependencies clean release docker-multi-arch
+.PHONY: all build client client-watch docker lint lint-js lint-go test dependencies clean release docker-multi-arch
 all: build
 
+init:
+	git config core.hooksPath .githooks
+
 build: dependencies client
 	PATH=$(GOPATH)/bin:$(PATH) go generate ./...
 	CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
@@ -116,11 +119,16 @@ docker:
 	@echo Now you can run the docker image:
 	@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
 
-lint:
-	@echo Running linters
-	golangci-lint run ./...
+lint: lint-js lint-go
+
+lint-js:
+	@echo Running js linter
 	npm --prefix client run lint
 
+lint-go:
+	@echo Running go linter
+	golangci-lint run
+
 test:
 	@echo Running unit-tests
 	go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
@@ -223,4 +231,4 @@ define write_version_file
 
 	# Finish
 	echo "}" >> $(DIST_DIR)/version.json
-endef
\ No newline at end of file
+endef
diff --git a/README.md b/README.md
index 17421a5d..ae6d0730 100644
--- a/README.md
+++ b/README.md
@@ -150,11 +150,14 @@ Is there a chance to handle this in the future? DNS will never be enough to do t
 
 ### Prerequisites
 
+Run `make init` to prepare the development environment.
+
 You will need this to build AdGuard Home:
 
  * [go](https://golang.org/dl/) v1.14 or later.
  * [node.js](https://nodejs.org/en/download/) v10 or later.
-
+ * [golangci-lint](https://github.com/golangci/golangci-lint)
+ 
 ### Building
 
 Open Terminal and execute these commands:

From e46db985e83b5637131d6aded0594a7b30cddf69 Mon Sep 17 00:00:00 2001
From: Artem Baskal <a.baskal@adguard.com>
Date: Wed, 15 Jul 2020 20:55:13 +0300
Subject: [PATCH 6/7] - client: Fix query logs UI issues Close #1828

Squashed commit of the following:

commit bf96b9f2cc99a94a1289c47b04cde136cf0c9f37
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 20:44:22 2020 +0300

    Remove field domain from response tooltip

commit bba35fdbed6d1e2e532c8effaf2da69de3f2c078
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 20:29:24 2020 +0300

    Unify mobile modal

commit 5ee2da41594497fd64eadf0fd64c24afdad94e44
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 19:02:47 2020 +0300

    Delete unnecessary comment

commit ac3a3f13009ad508ddd7eb31aadf7e590a5c2829
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:59:44 2020 +0300

    minor

commit 4b1969a53ce2fcfc859c228b27816459bd8bd1d0
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:56:51 2020 +0300

    Fix safari mediaQuery change listener issue

commit d85de5c4e90d2460632e593cffe3ceea3137e92c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:10:30 2020 +0300

    Fix logs input search markup (for different locales)

commit 6d704399c5379dfda663503b3a5b1d12a92732b2
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 16:05:35 2020 +0300

    Fix whois_info markup, fix domain name overflow

commit 4c900f60a9c6b71b427d968177252eb168c424c0
Merge: a3955c98 38366ba8
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 13:42:43 2020 +0300

    Merge branch 'master' into fix/1828

commit a3955c989a939866c6772b147547344b3f8769c4
Merge: c91c41cb 2759d81a
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 15:14:47 2020 +0300

    Merge branch 'master' into fix/1828

commit c91c41cbc5f616e0af1092424e42b909d2f43f7c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 13:48:54 2020 +0300

    Fix cell overflow

commit 19e1d31a40f2e1bb1189a85b72507bcc364d4e0c
Merge: af31f48c a33164bf
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 12:36:44 2020 +0300

    Merge branch 'master' into fix/1828

commit af31f48c4d2699ebfbd2034711c51499b42e40f5
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 10:45:57 2020 +0300

    minor

commit d9507c5f3f5758e587766ae0fa45f1b9ad703ccf
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 10 18:34:22 2020 +0300

    - client: Fix query logs UI issues
---
 client/src/components/Dashboard/Clients.js    |  2 +-
 client/src/components/Dashboard/DomainCell.js |  2 +-
 .../components/Logs/Cells/getClientCell.js    |  6 +-
 .../components/Logs/Cells/getResponseCell.js  | 14 ++--
 client/src/components/Logs/Filters/Form.js    | 26 ++++----
 client/src/components/Logs/Logs.css           | 64 ++++++-------------
 client/src/components/Logs/Table.js           | 58 ++++++-----------
 client/src/components/Logs/index.js           | 29 +++++++--
 client/src/helpers/formatClientCell.js        | 12 ++--
 client/src/helpers/helpers.js                 |  4 +-
 10 files changed, 98 insertions(+), 119 deletions(-)

diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js
index fc129b93..ac8771e5 100644
--- a/client/src/components/Dashboard/Clients.js
+++ b/client/src/components/Dashboard/Clients.js
@@ -62,7 +62,7 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
     return (
         <>
             <div className="logs__row logs__row--overflow logs__row--column">
-                {formatClientCell(row, t)}
+                {formatClientCell(row, true)}
             </div>
             {ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
             && renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
diff --git a/client/src/components/Dashboard/DomainCell.js b/client/src/components/Dashboard/DomainCell.js
index 76bb0877..c613c268 100644
--- a/client/src/components/Dashboard/DomainCell.js
+++ b/client/src/components/Dashboard/DomainCell.js
@@ -9,7 +9,7 @@ const DomainCell = ({ value }) => {
 
     return (
         <div className="logs__row">
-            <div className="logs__text logs__text--domain" title={value}>
+            <div className="logs__text" title={value}>
                 {value}
             </div>
             {trackerData && <Popover data={trackerData} />}
diff --git a/client/src/components/Logs/Cells/getClientCell.js b/client/src/components/Logs/Cells/getClientCell.js
index 2ebca672..00d5536c 100644
--- a/client/src/components/Logs/Cells/getClientCell.js
+++ b/client/src/components/Logs/Cells/getClientCell.js
@@ -33,7 +33,7 @@ const getClientCell = ({
     const isFiltered = checkFiltered(reason);
 
     const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
-        'mt-2': isDetailed && !name,
+        'mt-2': isDetailed && !name && !whois_info,
         'white-space--nowrap': isDetailed,
     });
 
@@ -80,9 +80,9 @@ const getClientCell = ({
             })}
             <div
                 className={nameClass}>
-                <div data-tip={true} data-for={id}>{formatClientCell(row, t, isDetailed)}</div>
+                <div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
                 {isDetailed && name
-                && <div className="detailed-info d-none d-sm-block logs__text"
+                && !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
                         title={name}>{name}</div>}
             </div>
             {renderBlockingButton(isFiltered, domain)}
diff --git a/client/src/components/Logs/Cells/getResponseCell.js b/client/src/components/Logs/Cells/getResponseCell.js
index fcc2e5a0..6690c0f6 100644
--- a/client/src/components/Logs/Cells/getResponseCell.js
+++ b/client/src/components/Logs/Cells/getResponseCell.js
@@ -9,8 +9,7 @@ import getHintElement from './getHintElement';
 
 const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
     const {
-        reason, filterId, rule, status, upstream, elapsedMs,
-        domain, response, originalResponse,
+        reason, filterId, rule, status, upstream, elapsedMs, response, originalResponse,
     } = row.original;
 
     const { filters, whitelistFilters } = filtering;
@@ -41,7 +40,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
 
     const FILTERED_STATUS_TO_FIELDS_MAP = {
         [FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
-            domain,
             encryption_status: boldStatusLabel,
             install_settings_dns: upstream,
             elapsed: formattedElapsedMs,
@@ -49,7 +47,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
             response_table_header: renderResponses(response),
         },
         [FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
-            domain,
             encryption_status: boldStatusLabel,
             install_settings_dns: upstream,
             elapsed: formattedElapsedMs,
@@ -59,7 +56,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
             original_response: renderResponses(originalResponse),
         },
         [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
-            domain,
             encryption_status: boldStatusLabel,
             install_settings_dns: upstream,
             elapsed: formattedElapsedMs,
@@ -68,21 +64,19 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
             response_code: status,
         },
         [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
-            domain,
             encryption_status: boldStatusLabel,
             filter,
             rule_label: rule,
             response_code: status,
         },
         [FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
-            domain,
             encryption_status: boldStatusLabel,
             install_settings_dns: upstream,
             elapsed: formattedElapsedMs,
             response_code: status,
+            response_table_header: renderResponses(response),
         },
         [FILTERED_STATUS.FILTERED_BLACK_LIST]: {
-            domain,
             encryption_status: boldStatusLabel,
             filter,
             rule_label: rule,
@@ -93,7 +87,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
         },
     };
 
-    const fields = FILTERED_STATUS_TO_FIELDS_MAP[reason]
+    const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
         ? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
         : Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
 
@@ -108,7 +102,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
                 contentItemClass: 'text-truncate key-colon o-hidden',
                 xlinkHref: 'question',
                 title: 'response_details',
-                content: fields,
+                content,
                 placement: 'bottom',
             })}
             <div className="text-truncate">
diff --git a/client/src/components/Logs/Filters/Form.js b/client/src/components/Logs/Filters/Form.js
index 42145c64..d70153c1 100644
--- a/client/src/components/Logs/Filters/Form.js
+++ b/client/src/components/Logs/Filters/Form.js
@@ -144,18 +144,20 @@ const Form = (props) => {
                   e.preventDefault();
               }}
         >
-            <Field
-                id={FORM_NAMES.search}
-                name={FORM_NAMES.search}
-                component={renderFilterField}
-                type="text"
-                className={classNames('form-control--search form-control--transparent', className)}
-                placeholder={t('domain_or_client')}
-                tooltip={t('query_log_strict_search')}
-                onClearInputClick={onInputClear}
-                onKeyDown={onEnterPress}
-                normalizeOnBlur={normalizeOnBlur}
-            />
+            <div className="field__search">
+                <Field
+                    id={FORM_NAMES.search}
+                    name={FORM_NAMES.search}
+                    component={renderFilterField}
+                    type="text"
+                    className={classNames('form-control--search form-control--transparent', className)}
+                    placeholder={t('domain_or_client')}
+                    tooltip={t('query_log_strict_search')}
+                    onClearInputClick={onInputClear}
+                    onKeyDown={onEnterPress}
+                    normalizeOnBlur={normalizeOnBlur}
+                />
+            </div>
             <div className="field__select">
                 <Field
                     name={FORM_NAMES.response_status}
diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css
index 2f12c08d..4d734b53 100644
--- a/client/src/components/Logs/Logs.css
+++ b/client/src/components/Logs/Logs.css
@@ -13,7 +13,8 @@
 }
 
 .card-table .logs__row {
-    overflow: visible;
+    overflow: hidden;
+    text-overflow: ellipsis;
 }
 
 .logs__row--center {
@@ -57,10 +58,6 @@
     width: 100%;
 }
 
-.logs__text--domain {
-    max-width: 285px;
-}
-
 .logs__text--wrap,
 .logs__text--whois {
     line-height: 1.4;
@@ -202,6 +199,7 @@
 .logs__whois {
     display: inline;
     font-size: 12px;
+    white-space: nowrap;
 }
 
 .logs__whois::after {
@@ -455,15 +453,11 @@
     color: var(--danger);
 }
 
-.ml-small {
-    margin-left: 3.3125rem;
-}
-
 .form-control--search {
-    width: 39.125rem;
     box-shadow: 0 1px 0 #ddd;
     padding: 0 2.5rem;
     height: 2.25rem;
+    flex-grow: 1;
 }
 
 .form-control--transparent {
@@ -493,31 +487,12 @@
 }
 
 .form-control--container {
-    max-width: 100%;
+    flex: auto;
 }
 
-@media (max-width: 1279.98px) {
-    .form-control--search {
-        max-width: 30.125rem;
-    }
-
-    .form-control--container {
-        max-width: 70%;
-    }
-
-    .form-control--search {
-        max-width: 50%;
-    }
-}
-
-@media (max-width: 991.98px) {
-    .form-control--search {
-        max-width: 40%;
-    }
-
-    .form-control--container {
-        max-width: 100%;
-    }
+.field__search {
+    display: flex;
+    flex-grow: 1;
 }
 
 @media (max-width: 767.98px) {
@@ -528,6 +503,19 @@
     .ml-small {
         margin-left: 1.5rem;
     }
+
+    .form-control--container {
+        width: 100%;
+        flex-direction: column;
+    }
+
+    .form-control--search {
+        width: 100%;
+    }
+
+    .field__select {
+        margin-top: 1.5rem;
+    }
 }
 
 @media (max-width: 575px) {
@@ -544,16 +532,6 @@
     }
 }
 
-@media (max-width: 500px) {
-    .form-control--search {
-        max-width: 85%;
-    }
-
-    .field__select {
-        margin-top: 1.5rem;
-    }
-}
-
 .loading__container > .-loading-inner {
     top: 10rem !important;
     bottom: initial !important;
diff --git a/client/src/components/Logs/Table.js b/client/src/components/Logs/Table.js
index 131db854..fcf8daa4 100644
--- a/client/src/components/Logs/Table.js
+++ b/client/src/components/Logs/Table.js
@@ -25,7 +25,7 @@ import {
     formatDateTime,
     formatElapsedMs,
     formatTime,
-
+    processContent,
 } from '../../helpers/helpers';
 import Loading from '../ui/Loading';
 import { getSourceData } from '../../helpers/trackers/trackers';
@@ -302,6 +302,7 @@ const Table = (props) => {
                             filterId,
                             rule,
                             originalResponse,
+                            status,
                         } = rowInfo.original;
 
                         const hasTracker = !!tracker;
@@ -328,17 +329,20 @@ const Table = (props) => {
                         };
 
                         const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
-                        const status = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
-                        const statusBlocked = <div className="bg--danger">{status}</div>;
+                        const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
 
                         const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
 
                         const sourceData = getSourceData(tracker);
 
+                        const { filters, whitelistFilters } = filtering;
+                        const filter = getFilterName(filters, whitelistFilters, filterId, t);
+
                         const detailedData = {
                             time_table_header: formatTime(time, LONG_TIME_FORMAT),
                             date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
-                            encryption_status: status,
+                            encryption_status: isBlocked
+                                ? <div className="bg--danger">{requestStatus}</div> : requestStatus,
                             domain,
                             type_table_header: type,
                             protocol,
@@ -346,12 +350,19 @@ const Table = (props) => {
                             table_name: tracker?.name,
                             category_label: hasTracker && captitalizeWords(tracker.category),
                             tracker_source: hasTracker && sourceData
-                                && <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
-                                   className="link--green">{sourceData.name}</a>,
+                                && <a
+                                    href={sourceData.url}
+                                    target="_blank"
+                                    rel="noopener noreferrer"
+                                    className="link--green">{sourceData.name}
+                                </a>,
                             response_details: 'title',
                             install_settings_dns: upstream,
                             elapsed: formattedElapsedMs,
+                            filter: isBlocked ? filter : null,
+                            rule_label: rule,
                             response_table_header: response?.join('\n'),
+                            response_code: status,
                             client_details: 'title',
                             ip_address: client,
                             name: info?.name,
@@ -360,41 +371,14 @@ const Table = (props) => {
                             network,
                             source_label: source,
                             validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
-                            [buttonType]: <div onClick={onToggleBlock}
-                                               className="title--border bg--danger text-center">{t(buttonType)}</div>,
-                        };
-
-                        const { filters, whitelistFilters } = filtering;
-
-                        const filter = getFilterName(filters, whitelistFilters, filterId, t);
-
-                        const detailedDataBlocked = {
-                            time_table_header: formatTime(time, LONG_TIME_FORMAT),
-                            date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
-                            encryption_status: statusBlocked,
-                            domain,
-                            type_table_header: type,
-                            protocol,
-                            known_tracker: 'title',
-                            table_name: tracker?.name,
-                            category_label: hasTracker && captitalizeWords(tracker.category),
-                            source_label: hasTracker && sourceData
-                                && <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
-                                   className="link--green">{sourceData.name}</a>,
-                            response_details: 'title',
-                            install_settings_dns: upstream,
-                            elapsed: formattedElapsedMs,
-                            filter,
-                            rule_label: rule,
-                            response_table_header: response?.join('\n'),
                             original_response: originalResponse?.join('\n'),
                             [buttonType]: <div onClick={onToggleBlock}
-                                               className="title--border text-center">{t(buttonType)}</div>,
+                                               className={classNames('title--border text-center', {
+                                                   'bg--danger': isBlocked,
+                                               })}>{t(buttonType)}</div>,
                         };
 
-                        const detailedDataCurrent = isBlocked ? detailedDataBlocked : detailedData;
-
-                        setDetailedDataCurrent(detailedDataCurrent);
+                        setDetailedDataCurrent(processContent(detailedData));
                         setButtonType(buttonType);
                         setModalOpened(true);
                     }
diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js
index b328e34e..f889fead 100644
--- a/client/src/components/Logs/index.js
+++ b/client/src/components/Logs/index.js
@@ -5,6 +5,7 @@ import Modal from 'react-modal';
 import { shallowEqual, useDispatch, useSelector } from 'react-redux';
 import { useHistory } from 'react-router-dom';
 import queryString from 'query-string';
+import classNames from 'classnames';
 import {
     BLOCK_ACTIONS,
     TABLE_DEFAULT_PAGE_SIZE,
@@ -27,7 +28,7 @@ import {
 import { addSuccessToast } from '../../actions/toasts';
 import './Logs.css';
 
-export const processContent = (data, buttonType) => Object.entries(data)
+const processContent = (data, buttonType) => Object.entries(data)
     .map(([key, value]) => {
         if (!value) {
             return null;
@@ -49,7 +50,9 @@ export const processContent = (data, buttonType) => Object.entries(data)
 
         return isHidden ? null : <Fragment key={key}>
             <div
-                className={`key__${key} ${keyClass} ${(isBoolean && value === true) ? 'font-weight-bold' : ''}`}>
+                className={classNames(`key__${key}`, keyClass, {
+                    'font-weight-bold': isBoolean && value === true,
+                })}>
                 <Trans>{isButton ? value : key}</Trans>
             </div>
             <div className={`value__${key} text-pre text-truncate`}>
@@ -133,7 +136,16 @@ const Logs = (props) => {
     };
 
     useEffect(() => {
-        mediaQuery.addEventListener('change', mediaQueryHandler);
+        try {
+            mediaQuery.addEventListener('change', mediaQueryHandler);
+        } catch (e1) {
+            try {
+                // Safari 13.1 do not support mediaQuery.addEventListener('change', handler)
+                mediaQuery.addListener(mediaQueryHandler);
+            } catch (e2) {
+                console.error(e2);
+            }
+        }
 
         (async () => {
             setIsLoading(true);
@@ -153,7 +165,16 @@ const Logs = (props) => {
         })();
 
         return () => {
-            mediaQuery.removeEventListener('change', mediaQueryHandler);
+            try {
+                mediaQuery.removeEventListener('change', mediaQueryHandler);
+            } catch (e1) {
+                try {
+                    mediaQuery.removeListener(mediaQueryHandler);
+                } catch (e2) {
+                    console.error(e2);
+                }
+            }
+
             dispatch(resetFilteredLogs());
         };
     }, []);
diff --git a/client/src/helpers/formatClientCell.js b/client/src/helpers/formatClientCell.js
index 05baff38..3fc8e55d 100644
--- a/client/src/helpers/formatClientCell.js
+++ b/client/src/helpers/formatClientCell.js
@@ -2,14 +2,14 @@ import React from 'react';
 import { normalizeWhois } from './helpers';
 import { WHOIS_ICONS } from './constants';
 
-const getFormattedWhois = (whois, t) => {
+const getFormattedWhois = (whois) => {
     const whoisInfo = normalizeWhois(whois);
     return (
         Object.keys(whoisInfo)
             .map((key) => {
                 const icon = WHOIS_ICONS[key];
                 return (
-                    <span className="logs__whois text-muted" key={key} title={t(key)}>
+                    <span className="logs__whois text-muted " key={key} title={whoisInfo[key]}>
                     {icon && (
                         <>
                             <svg className="logs__whois-icon icons">
@@ -24,7 +24,7 @@ const getFormattedWhois = (whois, t) => {
     );
 };
 
-export const formatClientCell = (row, t, isDetailed = false) => {
+export const formatClientCell = (row, isDetailed = false) => {
     const { value, original: { info } } = row;
     let whoisContainer = '';
     let nameContainer = value;
@@ -33,7 +33,7 @@ export const formatClientCell = (row, t, isDetailed = false) => {
         const { name, whois_info } = info;
 
         if (name) {
-            nameContainer = isDetailed
+            nameContainer = !whois_info && isDetailed
                 ? <small title={value}>{value}</small>
                 : <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
                     {name}
@@ -42,10 +42,10 @@ export const formatClientCell = (row, t, isDetailed = false) => {
                 </div>;
         }
 
-        if (whois_info) {
+        if (whois_info && isDetailed) {
             whoisContainer = (
                 <div className="logs__text logs__text--wrap logs__text--whois">
-                    {getFormattedWhois(whois_info, t)}
+                    {getFormattedWhois(whois_info)}
                 </div>
             );
         }
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index a6426ba5..bd246609 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -318,7 +318,8 @@ export const splitByNewLine = (text) => text.split('\n')
  * @returns {string}
  */
 export const trimMultilineString = (text) => splitByNewLine(text)
-    .map((line) => line.trim()).join('\n');
+    .map((line) => line.trim())
+    .join('\n');
 
 /**
  * @param {string} text
@@ -636,7 +637,6 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
     response_status,
 })}`;
 
-
 export const processContent = (content) => (Array.isArray(content)
     ? content.filter(([, value]) => value)
         .flat() : content);

From 177404d15720bc275f356977f991a188c9ba933a Mon Sep 17 00:00:00 2001
From: Simon Zolin <s.zolin@adguard.com>
Date: Thu, 16 Jul 2020 10:15:26 +0300
Subject: [PATCH 7/7] - auth: fix logic with --glinet argument

---
 home/auth.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/home/auth.go b/home/auth.go
index 3166052b..36e56d05 100644
--- a/home/auth.go
+++ b/home/auth.go
@@ -395,6 +395,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
 
 			if glProcessCookie(r) {
 				log.Debug("Auth: authentification was handled by GL-Inet submodule")
+				ok = true
 
 			} else if err == nil {
 				r := Context.auth.CheckSession(cookie.Value)