diff --git a/client/.eslintrc b/client/.eslintrc
index aa104244..d5d5955b 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -48,6 +48,7 @@
"camelcase": "off",
"no-console": ["warn", { "allow": ["warn", "error"] }],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
- "import/prefer-default-export": "off"
+ "import/prefer-default-export": "off",
+ "no-alert": "off"
}
}
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 9e11009a..961b47c7 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -458,5 +458,9 @@
"check_reason": "Reason: {{reason}}",
"check_rule": "Rule: {{rule}}",
"check_service": "Service name: {{service}}",
- "check_not_found": "Doesn't exist in any filter"
+ "check_not_found": "Doesn't exist in any filter",
+ "client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",
+ "client_confirm_unblock": "Are you sure you want to unblock the client \"{{ip}}\"?",
+ "client_blocked": "Client \"{{ip}}\" successfully blocked",
+ "client_unblocked": "Client \"{{ip}}\" successfully unblocked"
}
diff --git a/client/src/actions/access.js b/client/src/actions/access.js
index 5b5272d7..1f51cea7 100644
--- a/client/src/actions/access.js
+++ b/client/src/actions/access.js
@@ -1,7 +1,11 @@
import { createAction } from 'redux-actions';
+import { t } from 'i18next';
+
import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
+import { getStats, getStatsConfig } from './stats';
import { normalizeTextarea } from '../helpers/helpers';
+import { ACTION } from '../helpers/constants';
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
@@ -28,9 +32,9 @@ export const setAccessList = config => async (dispatch) => {
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
const values = {
- allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [],
- disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [],
- blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [],
+ allowed_clients: normalizeTextarea(allowed_clients),
+ disallowed_clients: normalizeTextarea(disallowed_clients),
+ blocked_hosts: normalizeTextarea(blocked_hosts),
};
await apiClient.setAccessList(values);
@@ -41,3 +45,43 @@ export const setAccessList = config => async (dispatch) => {
dispatch(setAccessListFailure());
}
};
+
+export const toggleClientBlockRequest = createAction('TOGGLE_CLIENT_BLOCK_REQUEST');
+export const toggleClientBlockFailure = createAction('TOGGLE_CLIENT_BLOCK_FAILURE');
+export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCESS');
+
+export const toggleClientBlock = (type, ip) => async (dispatch, getState) => {
+ dispatch(toggleClientBlockRequest());
+ try {
+ const { allowed_clients, disallowed_clients, blocked_hosts } = getState().access;
+ let updatedDisallowedClients = normalizeTextarea(disallowed_clients);
+
+ if (type === ACTION.unblock && updatedDisallowedClients.includes(ip)) {
+ updatedDisallowedClients = updatedDisallowedClients.filter(client => client !== ip);
+ } else if (type === ACTION.block && !updatedDisallowedClients.includes(ip)) {
+ updatedDisallowedClients.push(ip);
+ }
+
+ const values = {
+ allowed_clients: normalizeTextarea(allowed_clients),
+ blocked_hosts: normalizeTextarea(blocked_hosts),
+ disallowed_clients: updatedDisallowedClients,
+ };
+
+ await apiClient.setAccessList(values);
+ dispatch(toggleClientBlockSuccess());
+
+ if (type === ACTION.unblock) {
+ dispatch(addSuccessToast(t('client_unblocked', { ip })));
+ } else if (type === ACTION.block) {
+ dispatch(addSuccessToast(t('client_blocked', { ip })));
+ }
+
+ dispatch(getStats());
+ dispatch(getStatsConfig());
+ dispatch(getAccessList());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(toggleClientBlockFailure());
+ }
+};
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index b3e4d2bb..0d212b1b 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -289,12 +289,8 @@ export const setUpstream = config => async (dispatch) => {
dispatch(setUpstreamRequest());
try {
const values = { ...config };
- values.bootstrap_dns = (
- values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
- ) || [];
- values.upstream_dns = (
- values.upstream_dns && normalizeTextarea(values.upstream_dns)
- ) || [];
+ values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
+ values.upstream_dns = normalizeTextarea(values.upstream_dns);
await apiClient.setUpstream(values);
dispatch(addSuccessToast('updated_upstream_dns_toast'));
@@ -313,12 +309,8 @@ export const testUpstream = config => async (dispatch) => {
dispatch(testUpstreamRequest());
try {
const values = { ...config };
- values.bootstrap_dns = (
- values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
- ) || [];
- values.upstream_dns = (
- values.upstream_dns && normalizeTextarea(values.upstream_dns)
- ) || [];
+ values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
+ values.upstream_dns = normalizeTextarea(values.upstream_dns);
const upstreamResponse = await apiClient.testUpstream(values);
const testMessages = Object.keys(upstreamResponse).map((key) => {
diff --git a/client/src/actions/stats.js b/client/src/actions/stats.js
index 25897aab..7a12b203 100644
--- a/client/src/actions/stats.js
+++ b/client/src/actions/stats.js
@@ -2,7 +2,7 @@ import { createAction } from 'redux-actions';
import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
-import { normalizeTopStats, secondsToMilliseconds, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
+import { normalizeTopStats, secondsToMilliseconds, getParamsForClientsSearch, addClientInfo, addClientStatus } from '../helpers/helpers';
export const getStatsConfigRequest = createAction('GET_STATS_CONFIG_REQUEST');
export const getStatsConfigFailure = createAction('GET_STATS_CONFIG_FAILURE');
@@ -46,12 +46,15 @@ export const getStats = () => async (dispatch) => {
const normalizedTopClients = normalizeTopStats(stats.top_clients);
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
const clients = await apiClient.findClients(clientsParams);
+ const accessData = await apiClient.getAccessList();
+ const { disallowed_clients } = accessData;
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
+ const topClientsWithStatus = addClientStatus(topClientsWithInfo, disallowed_clients, 'name');
const normalizedStats = {
...stats,
top_blocked_domains: normalizeTopStats(stats.top_blocked_domains),
- top_clients: topClientsWithInfo,
+ top_clients: topClientsWithStatus,
top_queried_domains: normalizeTopStats(stats.top_queried_domains),
avg_processing_time: secondsToMilliseconds(stats.avg_processing_time),
};
diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js
index 42288ca8..3823d52d 100644
--- a/client/src/components/Dashboard/BlockedDomains.js
+++ b/client/src/components/Dashboard/BlockedDomains.js
@@ -58,7 +58,7 @@ const BlockedDomains = ({
noDataText={t('no_domains_found')}
minRows={6}
defaultPageSize={100}
- className="-striped -highlight card-table-overflow stats__table"
+ className="-highlight card-table-overflow stats__table"
/>
);
diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js
index e83addcb..04c3270b 100644
--- a/client/src/components/Dashboard/Clients.js
+++ b/client/src/components/Dashboard/Clients.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
@@ -28,17 +28,58 @@ const countCell = dnsQueries =>
return