diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index d933e462..4fd4e4f5 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -1,6 +1,10 @@
#!/bin/bash
set -e;
-git diff --cached --name-only | grep -q '.js$' && make lint-js;
+git diff --cached --name-only | grep -q '.js$' && found=1
+if [ $found == 1 ]; then
+ make lint-js || exit 1
+ npm run test --prefix client || exit 1
+fi
found=0
git diff --cached --name-only | grep -q '.go$' && found=1
diff --git a/client/package-lock.json b/client/package-lock.json
index 84c8e186..42cac10f 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -12377,9 +12377,9 @@
}
},
"react-i18next": {
- "version": "11.4.0",
- "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
- "integrity": "sha512-lyOZSSQkif4H9HnHN3iEKVkryLI+WkdZSEw3VAZzinZLopfYRMHVY5YxCopdkXPLEHs6S5GjKYPh3+j0j336Fg==",
+ "version": "11.7.2",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.7.2.tgz",
+ "integrity": "sha512-Djj3K3hh5Tecla2CI9rLO3TZBYGMFrGilm0JY4cLofAQONCi5TK6nVmUPKoB59n1ZffgjfgJt6zlbE9aGF6Q0Q==",
"requires": {
"@babel/runtime": "^7.3.1",
"html-parse-stringify2": "2.0.1"
diff --git a/client/package.json b/client/package.json
index fdc19c9d..4ad8d5eb 100644
--- a/client/package.json
+++ b/client/package.json
@@ -28,7 +28,7 @@
"react": "^16.13.1",
"react-click-outside": "^3.0.1",
"react-dom": "^16.13.1",
- "react-i18next": "^11.4.0",
+ "react-i18next": "^11.7.2",
"react-modal": "^3.11.2",
"react-popper-tooltip": "^2.11.1",
"react-redux": "^7.2.0",
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 6abd1d4c..a3514327 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -366,7 +366,7 @@
"fix": "Fix",
"dns_providers": "Here is a <0>list of known DNS providers0> to choose from.",
"update_now": "Update now",
- "update_failed": "Auto-update failed. Please follow the steps to update manually.",
+ "update_failed": "Auto-update failed. Please follow these steps to update manually.",
"processing_update": "Please wait, AdGuard Home is being updated",
"clients_title": "Clients",
"clients_desc": "Configure devices connected to AdGuard Home",
diff --git a/client/src/__tests__/helpers.test.js b/client/src/__tests__/helpers.test.js
index f974cca6..bb371be4 100644
--- a/client/src/__tests__/helpers.test.js
+++ b/client/src/__tests__/helpers.test.js
@@ -1,5 +1,7 @@
-import { getIpMatchListStatus, sortIp } from '../helpers/helpers';
-import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
+import {
+ countClientsStatistics, findAddressType, getIpMatchListStatus, sortIp,
+} from '../helpers/helpers';
+import { ADDRESS_TYPES, IP_MATCH_LIST_STATUS } from '../helpers/constants';
describe('getIpMatchListStatus', () => {
describe('IPv4', () => {
@@ -482,3 +484,56 @@ describe('sortIp', () => {
});
});
});
+
+describe('findAddressType', () => {
+ describe('ip', () => {
+ expect(findAddressType('127.0.0.1')).toStrictEqual(ADDRESS_TYPES.IP);
+ });
+ describe('cidr', () => {
+ expect(findAddressType('127.0.0.1/8')).toStrictEqual(ADDRESS_TYPES.CIDR);
+ });
+ describe('mac', () => {
+ expect(findAddressType('00:1B:44:11:3A:B7')).toStrictEqual(ADDRESS_TYPES.UNKNOWN);
+ });
+});
+
+describe('countClientsStatistics', () => {
+ test('single ip', () => {
+ expect(countClientsStatistics(['127.0.0.1'], {
+ '127.0.0.1': 1,
+ })).toStrictEqual(1);
+ });
+ test('multiple ip', () => {
+ expect(countClientsStatistics(['127.0.0.1', '127.0.0.2'], {
+ '127.0.0.1': 1,
+ '127.0.0.2': 2,
+ })).toStrictEqual(1 + 2);
+ });
+ test('cidr', () => {
+ expect(countClientsStatistics(['127.0.0.0/8'], {
+ '127.0.0.1': 1,
+ '127.0.0.2': 2,
+ })).toStrictEqual(1 + 2);
+ });
+ test('cidr and multiple ip', () => {
+ expect(countClientsStatistics(['1.1.1.1', '2.2.2.2', '3.3.3.0/24'], {
+ '1.1.1.1': 1,
+ '2.2.2.2': 2,
+ '3.3.3.3': 3,
+ })).toStrictEqual(1 + 2 + 3);
+ });
+ test('mac', () => {
+ expect(countClientsStatistics(['00:1B:44:11:3A:B7', '2.2.2.2', '3.3.3.0/24'], {
+ '1.1.1.1': 1,
+ '2.2.2.2': 2,
+ '3.3.3.3': 3,
+ })).toStrictEqual(2 + 3);
+ });
+ test('not found', () => {
+ expect(countClientsStatistics(['4.4.4.4', '5.5.5.5', '6.6.6.6'], {
+ '1.1.1.1': 1,
+ '2.2.2.2': 2,
+ '3.3.3.3': 3,
+ })).toStrictEqual(0);
+ });
+});
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index d4018bb0..ff039af1 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -4,9 +4,10 @@ import axios from 'axios';
import endsWith from 'lodash/endsWith';
import escapeRegExp from 'lodash/escapeRegExp';
+import React from 'react';
import { splitByNewLine, sortClients } from '../helpers/helpers';
import {
- BLOCK_ACTIONS, CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, FORM_NAME,
+ BLOCK_ACTIONS, CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, FORM_NAME, GETTING_STARTED_LINK,
} from '../helpers/constants';
import { areEqualVersions } from '../helpers/version';
import { getTlsStatus } from './encryption';
@@ -184,7 +185,14 @@ export const getUpdate = () => async (dispatch, getState) => {
dispatch(getUpdateRequest());
const handleRequestError = () => {
- dispatch(addNoticeToast({ error: 'update_failed' }));
+ const options = {
+ components: {
+ a: ,
+ },
+ };
+
+ dispatch(addNoticeToast({ error: 'update_failed', options }));
dispatch(getUpdateFailure());
};
diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css
index fd088b79..b230ab8b 100644
--- a/client/src/components/Logs/Logs.css
+++ b/client/src/components/Logs/Logs.css
@@ -388,3 +388,28 @@
.logs__table .loading:before {
min-height: 100%;
}
+
+.logs__whois {
+ display: inline;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.logs__whois::after {
+ content: "|";
+ padding: 0 5px;
+ opacity: 0.3;
+}
+
+.logs__whois:last-child::after {
+ content: "";
+}
+
+.logs__whois-icon.icons {
+ position: relative;
+ top: -2px;
+ width: 12px;
+ height: 12px;
+ margin-right: 1px;
+ opacity: 0.5;
+}
diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js
index cb7f2914..ac613164 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 { splitByNewLine } from '../../../helpers/helpers';
+import { splitByNewLine, countClientsStatistics } from '../../../helpers/helpers';
import Card from '../../ui/Card';
import Modal from './Modal';
import CellWrap from '../../ui/CellWrap';
@@ -204,7 +204,10 @@ class ClientsTable extends Component {
{
Header: this.props.t('requests_count'),
id: 'statistics',
- accessor: (row) => this.props.normalizedTopClients.configured[row.name] || 0,
+ accessor: (row) => countClientsStatistics(
+ row.ids,
+ this.props.normalizedTopClients.auto,
+ ),
sortMethod: (a, b) => b - a,
minWidth: 120,
Cell: (row) => {
diff --git a/client/src/components/Settings/Clients/whoisCell.js b/client/src/components/Settings/Clients/whoisCell.js
index 94afd6cc..1a8b0484 100644
--- a/client/src/components/Settings/Clients/whoisCell.js
+++ b/client/src/components/Settings/Clients/whoisCell.js
@@ -14,7 +14,7 @@ const getFormattedWhois = (value, t) => {
{icon && (
-