diff --git a/Makefile b/Makefile
index f577ea7c..074da525 100644
--- a/Makefile
+++ b/Makefile
@@ -92,7 +92,7 @@ endif
all: build
build: dependencies client
- go generate ./...
+ 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)"
PATH=$(GOPATH)/bin:$(PATH) packr clean
diff --git a/client/package-lock.json b/client/package-lock.json
index faaa5598..86fdb272 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1909,6 +1909,11 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
+ },
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
@@ -3097,6 +3102,11 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
@@ -4818,8 +4828,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"deep-equal": {
"version": "1.1.1",
@@ -11044,6 +11053,24 @@
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
+ },
+ "dependencies": {
+ "query-string": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+ "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+ "dev": true
+ }
}
},
"npm-run-path": {
@@ -12138,13 +12165,13 @@
"dev": true
},
"query-string": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
- "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
- "dev": true,
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.1.tgz",
+ "integrity": "sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==",
"requires": {
- "object-assign": "^4.1.0",
- "strict-uri-encode": "^1.0.0"
+ "decode-uri-component": "^0.2.0",
+ "split-on-first": "^1.0.0",
+ "strict-uri-encode": "^2.0.0"
}
},
"querystring": {
@@ -13455,6 +13482,18 @@
"is-data-descriptor": "^1.0.0",
"kind-of": "^6.0.2"
}
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
}
}
},
@@ -13696,6 +13735,11 @@
"integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==",
"dev": true
},
+ "split-on-first": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+ },
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -13833,10 +13877,9 @@
"dev": true
},
"strict-uri-encode": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
- "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
- "dev": true
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-length": {
"version": "4.0.1",
diff --git a/client/package.json b/client/package.json
index edf5e424..d462519c 100644
--- a/client/package.json
+++ b/client/package.json
@@ -22,6 +22,7 @@
"lodash": "^4.17.15",
"nanoid": "^3.1.9",
"prop-types": "^15.7.2",
+ "query-string": "^6.13.1",
"react": "^16.13.1",
"react-click-outside": "^3.0.1",
"react-dom": "^16.13.1",
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index ed1148fe..c739e4a3 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -562,5 +562,6 @@
"filter_category_security_desc": "Lists that specialize on blocking malware, phishing or scam domains",
"filter_category_regional_desc": "Lists that focus on regional ads and tracking servers",
"filter_category_other_desc": "Other blocklists",
- "original_response": "Original response"
+ "original_response": "Original response",
+ "click_to_view_queries": "Click to view queries"
}
diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js
index 2a230622..bf3bee9f 100644
--- a/client/src/actions/queryLogs.js
+++ b/client/src/actions/queryLogs.js
@@ -2,12 +2,19 @@ import { createAction } from 'redux-actions';
import apiClient from '../api/Api';
import { normalizeLogs, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
-import { TABLE_DEFAULT_PAGE_SIZE, TABLE_FIRST_PAGE } from '../helpers/constants';
+import {
+ DEFAULT_LOGS_FILTER,
+ TABLE_DEFAULT_PAGE_SIZE,
+ TABLE_FIRST_PAGE,
+} from '../helpers/constants';
import { addErrorToast, addSuccessToast } from './toasts';
const getLogsWithParams = async (config) => {
const { older_than, filter, ...values } = config;
- const rawLogs = await apiClient.getQueryLog({ ...filter, older_than });
+ const rawLogs = await apiClient.getQueryLog({
+ ...filter,
+ older_than,
+ });
const { data, oldest } = rawLogs;
let logs = normalizeLogs(data);
const clientsParams = getParamsForClientsSearch(logs, 'client');
@@ -18,7 +25,11 @@ const getLogsWithParams = async (config) => {
}
return {
- logs, oldest, older_than, filter, ...values,
+ logs,
+ oldest,
+ older_than,
+ filter,
+ ...values,
};
};
@@ -38,7 +49,10 @@ const checkFilteredLogs = async (data, filter, dispatch, total) => {
dispatch(getAdditionalLogsRequest());
try {
- const additionalLogs = await getLogsWithParams({ older_than: oldest, filter });
+ const additionalLogs = await getLogsWithParams({
+ older_than: oldest,
+ filter,
+ });
if (additionalLogs.oldest.length > 0) {
return await checkFilteredLogs(additionalLogs, filter, dispatch, {
logs: [...totalData.logs, ...additionalLogs.logs],
@@ -69,13 +83,19 @@ export const getLogs = (config) => async (dispatch, getState) => {
dispatch(getLogsRequest());
try {
const { isFiltered, filter, page } = getState().queryLogs;
- const data = await getLogsWithParams({ ...config, filter });
+ const data = await getLogsWithParams({
+ ...config,
+ filter,
+ });
if (isFiltered) {
const additionalData = await checkFilteredLogs(data, filter, dispatch);
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
dispatch(getLogsSuccess(updatedData));
- dispatch(setLogsPagination({ page, pageSize: TABLE_DEFAULT_PAGE_SIZE }));
+ dispatch(setLogsPagination({
+ page,
+ pageSize: TABLE_DEFAULT_PAGE_SIZE,
+ }));
} else {
dispatch(getLogsSuccess(data));
}
@@ -86,24 +106,48 @@ export const getLogs = (config) => async (dispatch, getState) => {
};
export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
-export const setLogsFilterFailure = createAction('SET_LOGS_FILTER_FAILURE');
-export const setLogsFilterSuccess = createAction('SET_LOGS_FILTER_SUCCESS');
-export const setLogsFilter = (filter) => async (dispatch) => {
- dispatch(setLogsFilterRequest());
+/**
+ *
+ * @param filter
+ * @param {string} filter.search
+ * @param {string} filter.response_status query field of RESPONSE_FILTER object
+ * @returns function
+ */
+export const setLogsFilter = (filter) => setLogsFilterRequest(filter);
+
+export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST');
+export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
+export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
+
+export const setFilteredLogs = (filter) => async (dispatch) => {
+ dispatch(setFilteredLogsRequest());
try {
- const data = await getLogsWithParams({ older_than: '', filter });
+ const data = await getLogsWithParams({
+ older_than: '',
+ filter,
+ });
const additionalData = await checkFilteredLogs(data, filter, dispatch);
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
- dispatch(setLogsFilterSuccess({ ...updatedData, filter }));
+ dispatch(setFilteredLogsSuccess({
+ ...updatedData,
+ filter,
+ }));
dispatch(setLogsPage(TABLE_FIRST_PAGE));
} catch (error) {
dispatch(addErrorToast({ error }));
- dispatch(setLogsFilterFailure(error));
+ dispatch(setFilteredLogsFailure(error));
}
};
+export const resetFilteredLogs = () => setFilteredLogs(DEFAULT_LOGS_FILTER);
+
+export const refreshFilteredLogs = () => async (dispatch, getState) => {
+ const { filter } = getState().queryLogs;
+ await dispatch(setFilteredLogs(filter));
+};
+
export const clearLogsRequest = createAction('CLEAR_LOGS_REQUEST');
export const clearLogsFailure = createAction('CLEAR_LOGS_FAILURE');
export const clearLogsSuccess = createAction('CLEAR_LOGS_SUCCESS');
diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js
index afbd3888..71b33039 100644
--- a/client/src/components/App/index.js
+++ b/client/src/components/App/index.js
@@ -36,7 +36,7 @@ import i18n from '../../i18n';
import Loading from '../ui/Loading';
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
import Services from '../Filters/Services';
-import { setHtmlLangAttr } from '../../helpers/helpers';
+import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
class App extends Component {
componentDidMount() {
@@ -111,7 +111,9 @@ class App extends Component {
{!dashboard.processing && dashboard.isCoreRunning && (
<>
-
+
diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js
index 1df5ab81..144f5bee 100644
--- a/client/src/components/Dashboard/BlockedDomains.js
+++ b/client/src/components/Dashboard/BlockedDomains.js
@@ -14,7 +14,11 @@ const CountCell = (totalBlocked) => function cell(row) {
const { value } = row;
const percent = getPercent(totalBlocked, value);
- return | ;
+ return | ;
};
const BlockedDomains = ({
diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js
index 8cd30234..fc129b93 100644
--- a/client/src/components/Dashboard/Clients.js
+++ b/client/src/components/Dashboard/Clients.js
@@ -25,7 +25,7 @@ const countCell = (dnsQueries) => function cell(row) {
const percent = getPercent(dnsQueries, value);
const percentColor = getClientsPercentColor(percent);
- return | ;
+ return | ;
};
const renderBlockingButton = (ipMatchListStatus, ip, handleClick, processing) => {
diff --git a/client/src/components/Dashboard/Counters.js b/client/src/components/Dashboard/Counters.js
index e55f168e..9f780c2e 100644
--- a/client/src/components/Dashboard/Counters.js
+++ b/client/src/components/Dashboard/Counters.js
@@ -1,31 +1,80 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { Trans, withTranslation } from 'react-i18next';
+import propTypes from 'prop-types';
+import { Trans, useTranslation } from 'react-i18next';
import round from 'lodash/round';
-
+import { shallowEqual, useSelector } from 'react-redux';
import Card from '../ui/Card';
-import Tooltip from '../ui/Tooltip';
+import IconTooltip from '../ui/IconTooltip';
import { formatNumber } from '../../helpers/helpers';
+import LogsSearchLink from '../ui/LogsSearchLink';
+import { RESPONSE_FILTER } from '../../helpers/constants';
-const tooltipType = 'tooltip-custom--narrow';
+const Row = ({
+ label, count, response_status, tooltipTitle, translationComponents,
+}) => {
+ const content = response_status
+ ? {formatNumber(count)}
+ : count;
-const Counters = (props) => {
+ return
+
+ {label}
+
+ |
+ {content} |
+
;
+};
+
+const Counters = ({ refreshButton, subtitle }) => {
const {
- t,
interval,
- refreshButton,
- subtitle,
- dnsQueries,
- blockedFiltering,
- replacedSafebrowsing,
- replacedParental,
- replacedSafesearch,
+ numDnsQueries,
+ numBlockedFiltering,
+ numReplacedSafebrowsing,
+ numReplacedParental,
+ numReplacedSafesearch,
avgProcessingTime,
- } = props;
+ } = useSelector((state) => state.stats, shallowEqual);
+ const { t } = useTranslation();
- const tooltipTitle = interval === 1
- ? t('number_of_dns_query_24_hours')
- : t('number_of_dns_query_days', { count: interval });
+ const rows = [
+ {
+ label: 'dns_query',
+ count: numDnsQueries,
+ tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
+ response_status: RESPONSE_FILTER.ALL.query,
+ },
+ {
+ label: 'blocked_by',
+ count: numBlockedFiltering,
+ tooltipTitle: 'number_of_dns_query_blocked_24_hours',
+ response_status: RESPONSE_FILTER.BLOCKED.query,
+ translationComponents: [link],
+ },
+ {
+ label: 'stats_malware_phishing',
+ count: numReplacedSafebrowsing,
+ tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
+ response_status: RESPONSE_FILTER.BLOCKED_THREATS.query,
+ },
+ {
+ label: 'stats_adult',
+ count: numReplacedParental,
+ tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
+ response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.query,
+ },
+ {
+ label: 'enforced_save_search',
+ count: numReplacedSafesearch,
+ tooltipTitle: 'number_of_dns_query_to_safe_search',
+ response_status: RESPONSE_FILTER.SAFE_SEARCH.query,
+ },
+ {
+ label: 'average_processing_time',
+ count: avgProcessingTime ? `${round(avgProcessingTime)} ms` : 0,
+ tooltipTitle: 'average_processing_time_hint',
+ },
+ ];
return (
{
refresh={refreshButton}
>
-
-
-
- dns_query
-
- |
-
-
- {formatNumber(dnsQueries)}
-
- |
-
-
-
- link]}>
- blocked_by
-
-
- |
-
-
- {formatNumber(blockedFiltering)}
-
- |
-
-
-
- stats_malware_phishing
-
- |
-
-
- {formatNumber(replacedSafebrowsing)}
-
- |
-
-
-
- stats_adult
-
- |
-
-
- {formatNumber(replacedParental)}
-
- |
-
-
-
- enforced_save_search
-
- |
-
-
- {formatNumber(replacedSafesearch)}
-
- |
-
-
-
- average_processing_time
-
- |
-
-
- {avgProcessingTime ? `${round(avgProcessingTime)} ms` : 0}
-
- |
-
-
+ {rows.map(Row)}
);
};
-Counters.propTypes = {
- dnsQueries: PropTypes.number.isRequired,
- blockedFiltering: PropTypes.number.isRequired,
- replacedSafebrowsing: PropTypes.number.isRequired,
- replacedParental: PropTypes.number.isRequired,
- replacedSafesearch: PropTypes.number.isRequired,
- avgProcessingTime: PropTypes.number.isRequired,
- refreshButton: PropTypes.node.isRequired,
- subtitle: PropTypes.string.isRequired,
- interval: PropTypes.number.isRequired,
- t: PropTypes.func.isRequired,
+Row.propTypes = {
+ label: propTypes.string.isRequired,
+ count: propTypes.string.isRequired,
+ response_status: propTypes.string,
+ tooltipTitle: propTypes.string.isRequired,
+ translationComponents: propTypes.arrayOf(propTypes.element),
};
-export default withTranslation()(Counters);
+Counters.propTypes = {
+ refreshButton: propTypes.node.isRequired,
+ subtitle: propTypes.string.isRequired,
+};
+
+export default Counters;
diff --git a/client/src/components/Dashboard/QueriedDomains.js b/client/src/components/Dashboard/QueriedDomains.js
index c9a2fb4e..debc4fe7 100644
--- a/client/src/components/Dashboard/QueriedDomains.js
+++ b/client/src/components/Dashboard/QueriedDomains.js
@@ -13,7 +13,8 @@ import { getPercent } from '../../helpers/helpers';
const getQueriedPercentColor = (percent) => {
if (percent > 10) {
return STATUS_COLORS.red;
- } if (percent > 5) {
+ }
+ if (percent > 5) {
return STATUS_COLORS.yellow;
}
return STATUS_COLORS.green;
@@ -24,7 +25,8 @@ const countCell = (dnsQueries) => function cell(row) {
const percent = getPercent(dnsQueries, value);
const percentColor = getQueriedPercentColor(percent);
- return | ;
+ return | ;
};
const QueriedDomains = ({
diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js
index 89bd5e92..71a1366b 100644
--- a/client/src/components/Dashboard/index.js
+++ b/client/src/components/Dashboard/index.js
@@ -111,13 +111,6 @@ class Dashboard extends Component {
diff --git a/client/src/components/Header/Menu.js b/client/src/components/Header/Menu.js
index 5e5e449d..fef0dd50 100644
--- a/client/src/components/Header/Menu.js
+++ b/client/src/components/Header/Menu.js
@@ -1,16 +1,19 @@
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import enhanceWithClickOutside from 'react-click-outside';
import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next';
-
import { SETTINGS_URLS, FILTERS_URLS, MENU_URLS } from '../../helpers/constants';
import Dropdown from '../ui/Dropdown';
const MENU_ITEMS = [
{
- route: MENU_URLS.root, exact: true, icon: 'dashboard', text: 'dashboard', order: 0,
+ route: MENU_URLS.root,
+ exact: true,
+ icon: 'dashboard',
+ text: 'dashboard',
+ order: 0,
},
// Settings dropdown should have visual order 1
@@ -18,27 +21,63 @@ const MENU_ITEMS = [
// Filters dropdown should have visual order 2
{
- route: MENU_URLS.logs, icon: 'log', text: 'query_log', order: 3,
+ route: MENU_URLS.logs,
+ icon: 'log',
+ text: 'query_log',
+ order: 3,
},
{
- route: MENU_URLS.guide, icon: 'setup', text: 'setup_guide', order: 4,
+ route: MENU_URLS.guide,
+ icon: 'setup',
+ text: 'setup_guide',
+ order: 4,
},
];
const SETTINGS_ITEMS = [
- { route: SETTINGS_URLS.settings, text: 'general_settings' },
- { route: SETTINGS_URLS.dns, text: 'dns_settings' },
- { route: SETTINGS_URLS.encryption, text: 'encryption_settings' },
- { route: SETTINGS_URLS.clients, text: 'client_settings' },
- { route: SETTINGS_URLS.dhcp, text: 'dhcp_settings' },
+ {
+ route: SETTINGS_URLS.settings,
+ text: 'general_settings',
+ },
+ {
+ route: SETTINGS_URLS.dns,
+ text: 'dns_settings',
+ },
+ {
+ route: SETTINGS_URLS.encryption,
+ text: 'encryption_settings',
+ },
+ {
+ route: SETTINGS_URLS.clients,
+ text: 'client_settings',
+ },
+ {
+ route: SETTINGS_URLS.dhcp,
+ text: 'dhcp_settings',
+ },
];
const FILTERS_ITEMS = [
- { route: FILTERS_URLS.dns_blocklists, text: 'dns_blocklists' },
- { route: FILTERS_URLS.dns_allowlists, text: 'dns_allowlists' },
- { route: FILTERS_URLS.dns_rewrites, text: 'dns_rewrites' },
- { route: FILTERS_URLS.blocked_services, text: 'blocked_services' },
- { route: FILTERS_URLS.custom_rules, text: 'custom_filtering_rules' },
+ {
+ route: FILTERS_URLS.dns_blocklists,
+ text: 'dns_blocklists',
+ },
+ {
+ route: FILTERS_URLS.dns_allowlists,
+ text: 'dns_allowlists',
+ },
+ {
+ route: FILTERS_URLS.dns_rewrites,
+ text: 'dns_rewrites',
+ },
+ {
+ route: FILTERS_URLS.blocked_services,
+ text: 'blocked_services',
+ },
+ {
+ route: FILTERS_URLS.custom_rules,
+ text: 'custom_filtering_rules',
+ },
];
class Menu extends Component {
@@ -52,7 +91,8 @@ class Menu extends Component {
getActiveClassForDropdown = (URLS) => {
const { pathname } = this.props.location;
- const isActivePage = Object.values(URLS).some((item) => item === pathname);
+ const isActivePage = Object.values(URLS)
+ .some((item) => item === pathname);
return isActivePage ? 'active' : '';
};
@@ -79,18 +119,18 @@ class Menu extends Component {
getDropdown = ({
label, order, URLS, icon, ITEMS,
}) => (
-
- {ITEMS.map((item) => (
- this.getNavLink({
- ...item,
- order,
- className: 'dropdown-item',
- })))}
-
+
+ {ITEMS.map((item) => (
+ this.getNavLink({
+ ...item,
+ order,
+ className: 'dropdown-item',
+ })))}
+
);
render() {
@@ -99,7 +139,7 @@ class Menu extends Component {
'mobile-menu--active': this.props.isMenuOpen,
});
return (
-
+ <>
{MENU_ITEMS.map((item) => (
@@ -108,26 +148,33 @@ class Menu extends Component {
key={item.text}
onClick={this.closeMenu}
>
- {this.getNavLink({ ...item, className: 'nav-link' })}
+ {this.getNavLink({
+ ...item,
+ className: 'nav-link',
+ })}
))}
- {this.getDropdown({
- order: 1,
- label: 'settings',
- icon: 'settings',
- URLS: SETTINGS_URLS,
- ITEMS: SETTINGS_ITEMS,
- })}
- {this.getDropdown({
- order: 2,
- label: 'filters',
- icon: 'filters',
- URLS: FILTERS_URLS,
- ITEMS: FILTERS_ITEMS,
- })}
+ -
+ {this.getDropdown({
+ order: 1,
+ label: 'settings',
+ icon: 'settings',
+ URLS: SETTINGS_URLS,
+ ITEMS: SETTINGS_ITEMS,
+ })}
+
+ -
+ {this.getDropdown({
+ order: 2,
+ label: 'filters',
+ icon: 'filters',
+ URLS: FILTERS_URLS,
+ ITEMS: FILTERS_ITEMS,
+ })}
+
-
+ >
);
}
}
diff --git a/client/src/components/Logs/Cells/getDomainCell.js b/client/src/components/Logs/Cells/getDomainCell.js
index c4156920..de142977 100644
--- a/client/src/components/Logs/Cells/getDomainCell.js
+++ b/client/src/components/Logs/Cells/getDomainCell.js
@@ -21,13 +21,13 @@ const getDomainCell = (props) => {
const hasTracker = !!tracker;
- const lockIconClass = classNames('icons', 'icon--small', 'd-none', 'd-sm-block', 'cursor--pointer', {
+ const lockIconClass = classNames('icons icon--small d-none d-sm-block cursor--pointer', {
'icon--active': answer_dnssec,
'icon--disabled': !answer_dnssec,
'my-3': isDetailed,
});
- const privacyIconClass = classNames('icons', 'mx-2', 'icon--small', 'd-none', 'd-sm-block', 'cursor--pointer', {
+ const privacyIconClass = classNames('icons mx-2 icon--small d-none d-sm-block cursor--pointer', {
'icon--active': hasTracker,
'icon--disabled': !hasTracker,
'my-3': isDetailed,
@@ -56,7 +56,7 @@ const getDomainCell = (props) => {
const renderGrid = (content, idx) => {
const preparedContent = typeof content === 'string' ? t(content) : content;
- const className = classNames('text-truncate key-colon o-hidden', {
+ const className = classNames('text-truncate o-hidden', {
'overflow-break': preparedContent.length > 100,
});
return {preparedContent}
;
diff --git a/client/src/components/Logs/Filters/Form.js b/client/src/components/Logs/Filters/Form.js
index 21e64322..42145c64 100644
--- a/client/src/components/Logs/Filters/Form.js
+++ b/client/src/components/Logs/Filters/Form.js
@@ -2,17 +2,20 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
-import debounce from 'lodash/debounce';
-import { useDispatch } from 'react-redux';
+import { shallowEqual, useDispatch, useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
import classNames from 'classnames';
import {
DEBOUNCE_FILTER_TIMEOUT,
DEFAULT_LOGS_FILTER,
FORM_NAME,
RESPONSE_FILTER,
+ RESPONSE_FILTER_QUERIES,
} from '../../../helpers/constants';
-import Tooltip from '../../ui/Tooltip';
+import IconTooltip from '../../ui/IconTooltip';
import { setLogsFilter } from '../../../actions/queryLogs';
+import useDebounce from '../../../helpers/useDebounce';
+import { createOnBlurHandler, getLogsUrlParams } from '../../../helpers/helpers';
const renderFilterField = ({
input,
@@ -25,34 +28,43 @@ const renderFilterField = ({
tooltip,
meta: { touched, error },
onClearInputClick,
-}) => <>
-
-
-
-
-
-
-
-
-
+ onKeyDown,
+ normalizeOnBlur,
+}) => {
+ const onBlur = (event) => createOnBlurHandler(event, input, normalizeOnBlur);
+
+ return <>
+
+
+
+
+
+
+
+
+
- {!disabled
- && touched
- && (error && {error})}
->;
+ {!disabled
+ && touched
+ && (error && {error})}
+ >;
+};
renderFilterField.propTypes = {
input: PropTypes.object.isRequired,
@@ -64,65 +76,91 @@ renderFilterField.propTypes = {
disabled: PropTypes.string,
autoComplete: PropTypes.string,
tooltip: PropTypes.string,
+ onKeyDown: PropTypes.func,
+ normalizeOnBlur: PropTypes.func,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
}).isRequired,
};
+const FORM_NAMES = {
+ search: 'search',
+ response_status: 'response_status',
+};
+
const Form = (props) => {
const {
className = '',
responseStatusClass,
- submit,
- reset,
setIsLoading,
+ change,
} = props;
const { t } = useTranslation();
const dispatch = useDispatch();
+ const history = useHistory();
- const debouncedSubmit = debounce(submit, DEBOUNCE_FILTER_TIMEOUT);
- const zeroDelaySubmit = () => setTimeout(submit, 0);
+ const {
+ response_status, search,
+ } = useSelector((state) => state.form[FORM_NAME.LOGS_FILTER].values, shallowEqual);
- const clearInput = async () => {
- await dispatch(setLogsFilter(DEFAULT_LOGS_FILTER));
- await reset();
- };
+ const [
+ debouncedSearch,
+ setDebouncedSearch,
+ ] = useDebounce(search.trim(), DEBOUNCE_FILTER_TIMEOUT);
+
+ useEffect(() => {
+ dispatch(setLogsFilter({
+ response_status,
+ search: debouncedSearch,
+ }));
+
+ history.replace(`${getLogsUrlParams(debouncedSearch, response_status)}`);
+ }, [response_status, debouncedSearch]);
+
+ if (response_status && !(response_status in RESPONSE_FILTER_QUERIES)) {
+ change(FORM_NAMES.response_status, DEFAULT_LOGS_FILTER[FORM_NAMES.response_status]);
+ }
const onInputClear = async () => {
setIsLoading(true);
- await clearInput();
+ setDebouncedSearch(DEFAULT_LOGS_FILTER[FORM_NAMES.search]);
+ change(FORM_NAMES.search, DEFAULT_LOGS_FILTER[FORM_NAMES.search]);
setIsLoading(false);
};
- useEffect(() => clearInput, []);
+ const onEnterPress = (e) => {
+ if (e.key === 'Enter') {
+ setDebouncedSearch(search);
+ }
+ };
+
+ const normalizeOnBlur = (data) => data.trim();
return (