From c72cd58f69b71e4981d8b182b2f7d53ea5e30868 Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Thu, 24 Sep 2020 15:48:37 +0300 Subject: [PATCH] + client: Move the client access check to the server-side Squashed commit of the following: commit 1aab0f62e94ce665a1b996552fac41dc4e769b4d Merge: cdf5eb6e c1f5fdae Author: ArtemBaskal Date: Thu Sep 24 15:36:05 2020 +0300 Merge branch '1920-client-find' into feature/1925 commit cdf5eb6ea67a665d21a3155d8cf89bba9a5a9948 Merge: b6c20b1c 10f67bd3 Author: ArtemBaskal Date: Wed Sep 23 20:28:51 2020 +0300 Merge branch 'master' into feature/1925 commit b6c20b1c7359a0e5902405b0551712f936848a80 Merge: 97d388ef 96512433 Author: ArtemBaskal Date: Tue Sep 15 10:44:25 2020 +0300 Merge branch 'master' into feature/1925 commit 97d388ef6571d590f21da00f86d889e881ca0c3d Author: ArtemBaskal Date: Tue Sep 15 10:30:50 2020 +0300 Extract buttons commit ca45fde11fc2b2812ff2b84dbd67aff0b5341be1 Author: ArtemBaskal Date: Thu Sep 10 12:46:09 2020 +0300 Handle errors in updateLogs commit f15e03c2e5a7115db984f70f72b0ddd870ece73d Author: ArtemBaskal Date: Thu Sep 10 12:39:34 2020 +0300 Update mobile block status on click commit 033b28db3b324f6d529ac1a0ef657886cdbe02bd Author: ArtemBaskal Date: Wed Sep 9 20:53:42 2020 +0300 Fix mobile block buttons, auto open page on web serve start commit 2730937b23309167a066b9154728ac53ffe81a49 Author: ArtemBaskal Date: Wed Sep 9 13:58:37 2020 +0300 Disable allow this client button when isNotInAllowedList is true commit 818cf869d63654c184762ad2701c4429a3e3011e Author: ArtemBaskal Date: Wed Sep 9 13:06:01 2020 +0300 Update client block state on option click commit a072b8983757f419645c0207ea78e6e867c440cb Author: ArtemBaskal Date: Tue Sep 8 20:17:16 2020 +0300 Adapt to api changes commit 28ab2bd8b3f14f60bc822b5a69fa1801db67d816 Author: ArtemBaskal Date: Tue Sep 8 14:12:20 2020 +0300 Change query log block confirm messages commit 9b0b6f6f9b1ec168fa71dbedd036152da59006e3 Author: ArtemBaskal Date: Tue Sep 8 12:00:46 2020 +0300 Refactor inner work with disallowed commit 05f76154b8f489738d032fdaa835edb371ce70c7 Author: ArtemBaskal Date: Mon Sep 7 16:11:37 2020 +0300 + client: Move the client access check to the server-side --- client/src/__locales/en.json | 1 + client/src/__tests__/helpers.test.js | 135 +----------------- client/src/actions/access.js | 20 ++- client/src/actions/queryLogs.js | 37 ++++- client/src/components/Dashboard/Clients.js | 66 ++++----- .../src/components/Logs/Cells/ClientCell.js | 54 ++++--- .../components/Logs/Cells/helpers/index.js | 17 ++- client/src/components/Logs/Cells/index.js | 66 +++++---- client/src/components/Logs/Logs.css | 19 ++- client/src/helpers/constants.js | 6 - client/src/helpers/helpers.js | 132 ++--------------- client/src/reducers/dhcp.js | 1 - client/webpack.dev.js | 1 + 13 files changed, 175 insertions(+), 380 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index b6235a66..26e7df08 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -582,5 +582,6 @@ "click_to_view_queries": "Click to view queries", "port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction on how to resolve this.", "adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.", + "client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.", "experimental": "Experimental" } diff --git a/client/src/__tests__/helpers.test.js b/client/src/__tests__/helpers.test.js index bb371be4..309b40e6 100644 --- a/client/src/__tests__/helpers.test.js +++ b/client/src/__tests__/helpers.test.js @@ -1,136 +1,5 @@ -import { - countClientsStatistics, findAddressType, getIpMatchListStatus, sortIp, -} from '../helpers/helpers'; -import { ADDRESS_TYPES, IP_MATCH_LIST_STATUS } from '../helpers/constants'; - -describe('getIpMatchListStatus', () => { - describe('IPv4', () => { - test('should return EXACT on find the exact ip match', () => { - const list = `127.0.0.2 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.1 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return CIDR on find the cidr match', () => { - const list = `127.0.0.2 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.0/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - - test('should return NOT_FOUND if the ip is not in the list', () => { - const list = `127.0.0.1 -2001:db8:11a3:9d7:0:0:0:0 -192.168.0.1/8 -127.0.0.2 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.4', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return the first EXACT or CIDR match in the list', () => { - const list1 = `2001:db8:11a3:9d7:0:0:0:0 -127.0.0.1 -127.0.0.8/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list1)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - - const list2 = `2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.0/24 -127.0.0.1 -127.0.0.8/24 -127.0.0.3`; - expect(getIpMatchListStatus('127.0.0.1', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - }); - - describe('IPv6', () => { - test('should return EXACT on find the exact ip match', () => { - const list = `127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return EXACT on find the exact ip match of short and long notation', () => { - const list = `127.0.0.0 -192.168.0.1/8 -2001:db8:: -127.0.0.2`; - expect(getIpMatchListStatus('2001:db8:0:0:0:0:0:0', list)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - }); - - test('should return CIDR on find the cidr match', () => { - const list1 = `2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.1 -127.0.0.2`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list1)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - - const list2 = `2001:0db8::/16 -127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:: -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('2001:db1::', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - - test('should return NOT_FOUND if the ip is not in the list', () => { - const list = `2001:db8:11a3:9d7:0:0:0:0 -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.1 -127.0.0.2`; - expect(getIpMatchListStatus('::', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return the first EXACT or CIDR match in the list', () => { - const list1 = `2001:db8:11a3:9d7:0:0:0:0 -2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -127.0.0.3`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list1)) - .toEqual(IP_MATCH_LIST_STATUS.EXACT); - - const list2 = `2001:0db8:11a3:09d7:0000:0000:0000:0000/64 -2001:db8:11a3:9d7:0:0:0:0 -127.0.0.3`; - expect(getIpMatchListStatus('2001:db8:11a3:9d7:0:0:0:0', list2)) - .toEqual(IP_MATCH_LIST_STATUS.CIDR); - }); - }); - - describe('Empty list or IP', () => { - test('should return NOT_FOUND on empty ip', () => { - const list = `127.0.0.0 -2001:db8:11a3:9d7:0:0:0:0 -2001:db8:11a3:9d7:ffff:ffff:ffff:ffff -127.0.0.1`; - expect(getIpMatchListStatus('', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - - test('should return NOT_FOUND on empty list', () => { - const list = ''; - expect(getIpMatchListStatus('127.0.0.1', list)) - .toEqual(IP_MATCH_LIST_STATUS.NOT_FOUND); - }); - }); -}); +import { sortIp, countClientsStatistics, findAddressType } from '../helpers/helpers'; +import { ADDRESS_TYPES } from '../helpers/constants'; describe('sortIp', () => { describe('ipv4', () => { diff --git a/client/src/actions/access.js b/client/src/actions/access.js index e04daac0..17acb5e8 100644 --- a/client/src/actions/access.js +++ b/client/src/actions/access.js @@ -3,7 +3,6 @@ import i18next from 'i18next'; import apiClient from '../api/Api'; import { addErrorToast, addSuccessToast } from './toasts'; -import { BLOCK_ACTIONS } from '../helpers/constants'; import { splitByNewLine } from '../helpers/helpers'; export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST'); @@ -49,19 +48,16 @@ export const toggleClientBlockRequest = createAction('TOGGLE_CLIENT_BLOCK_REQUES export const toggleClientBlockFailure = createAction('TOGGLE_CLIENT_BLOCK_FAILURE'); export const toggleClientBlockSuccess = createAction('TOGGLE_CLIENT_BLOCK_SUCCESS'); -export const toggleClientBlock = (type, ip) => async (dispatch) => { +export const toggleClientBlock = (ip, disallowed, disallowed_rule) => async (dispatch) => { dispatch(toggleClientBlockRequest()); try { const { - allowed_clients, disallowed_clients, blocked_hosts, + allowed_clients, blocked_hosts, disallowed_clients = [], } = await apiClient.getAccessList(); - let updatedDisallowedClients = disallowed_clients || []; - if (type === BLOCK_ACTIONS.UNBLOCK && updatedDisallowedClients.includes(ip)) { - updatedDisallowedClients = updatedDisallowedClients.filter((client) => client !== ip); - } else if (type === BLOCK_ACTIONS.BLOCK && !updatedDisallowedClients.includes(ip)) { - updatedDisallowedClients.push(ip); - } + const updatedDisallowedClients = disallowed + ? disallowed_clients.filter((client) => client !== disallowed_rule) + : disallowed_clients.concat(ip); const values = { allowed_clients, @@ -72,9 +68,9 @@ export const toggleClientBlock = (type, ip) => async (dispatch) => { await apiClient.setAccessList(values); dispatch(toggleClientBlockSuccess(values)); - if (type === BLOCK_ACTIONS.UNBLOCK) { - dispatch(addSuccessToast(i18next.t('client_unblocked', { ip }))); - } else if (type === BLOCK_ACTIONS.BLOCK) { + if (disallowed) { + dispatch(addSuccessToast(i18next.t('client_unblocked', { ip: disallowed_rule }))); + } else { dispatch(addSuccessToast(i18next.t('client_blocked', { ip }))); } } catch (error) { diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js index bfb9a609..1c653fb3 100644 --- a/client/src/actions/queryLogs.js +++ b/client/src/actions/queryLogs.js @@ -7,6 +7,17 @@ import { } from '../helpers/constants'; import { addErrorToast, addSuccessToast } from './toasts'; +const enrichWithClientInfo = async (logs) => { + const clientsParams = getParamsForClientsSearch(logs, 'client'); + + if (Object.keys(clientsParams).length > 0) { + const clients = await apiClient.findClients(clientsParams); + return addClientInfo(logs, clients, 'client'); + } + + return logs; +}; + const getLogsWithParams = async (config) => { const { older_than, filter, ...values } = config; const rawLogs = await apiClient.getQueryLog({ @@ -14,13 +25,8 @@ const getLogsWithParams = async (config) => { older_than, }); const { data, oldest } = rawLogs; - let logs = normalizeLogs(data); - const clientsParams = getParamsForClientsSearch(logs, 'client'); - - if (Object.keys(clientsParams).length > 0) { - const clients = await apiClient.findClients(clientsParams); - logs = addClientInfo(logs, clients, 'client'); - } + const normalizedLogs = normalizeLogs(data); + const logs = await enrichWithClientInfo(normalizedLogs); return { logs, @@ -82,6 +88,23 @@ export const getLogsRequest = createAction('GET_LOGS_REQUEST'); export const getLogsFailure = createAction('GET_LOGS_FAILURE'); export const getLogsSuccess = createAction('GET_LOGS_SUCCESS'); +export const updateLogs = () => async (dispatch, getState) => { + try { + const { logs, oldest, older_than } = getState().queryLogs; + + const enrichedLogs = await enrichWithClientInfo(logs); + + dispatch(getLogsSuccess({ + logs: enrichedLogs, + oldest, + older_than, + })); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getLogsFailure(error)); + } +}; + export const getLogs = () => async (dispatch, getState) => { dispatch(getLogsRequest()); try { diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index 2f25bda3..ccac7baf 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -8,10 +8,11 @@ import classNames from 'classnames'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; -import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers'; -import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants'; +import { getPercent, sortIp } from '../../helpers/helpers'; +import { BLOCK_ACTIONS, STATUS_COLORS } from '../../helpers/constants'; import { toggleClientBlock } from '../../actions/access'; import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell'; +import { getStats } from '../../actions/stats'; const getClientsPercentColor = (percent) => { if (percent > 50) { @@ -33,59 +34,51 @@ const CountCell = (row) => { return ; }; -const renderBlockingButton = (ip) => { +const renderBlockingButton = (ip, disallowed, disallowed_rule) => { const dispatch = useDispatch(); const { t } = useTranslation(); const processingSet = useSelector((state) => state.access.processingSet); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, shallowEqual, - ); - - const ipMatchListStatus = getIpMatchListStatus(ip, disallowed_clients); - - if (ipMatchListStatus === IP_MATCH_LIST_STATUS.CIDR) { - return null; - } - - const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND; - const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK; - const text = type; const buttonClass = classNames('button-action button-action--main', { - 'button-action--unblock': !isNotFound, + 'button-action--unblock': disallowed, }); - const toggleClientStatus = (type, ip) => { - const confirmMessage = type === BLOCK_ACTIONS.BLOCK - ? `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}` - : t('client_confirm_unblock', { ip }); + const toggleClientStatus = async (ip, disallowed, disallowed_rule) => { + const confirmMessage = disallowed + ? t('client_confirm_unblock', { ip: disallowed_rule }) + : `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`; if (window.confirm(confirmMessage)) { - dispatch(toggleClientBlock(type, ip)); + await dispatch(toggleClientBlock(ip, disallowed, disallowed_rule)); + await dispatch(getStats()); } }; - const onClick = () => toggleClientStatus(type, ip); + const onClick = () => toggleClientStatus(ip, disallowed, disallowed_rule); + const text = disallowed ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; + + const isNotInAllowedList = disallowed && disallowed_rule === ''; return
- -
; + + ; }; const ClientCell = (row) => { - const { value, original: { info } } = row; + const { value, original: { info, info: { disallowed, disallowed_rule } } } = row; return <>
{renderFormattedClientCell(value, info, true)} - {renderBlockingButton(value)} + {renderBlockingButton(value, disallowed, disallowed_rule)}
; }; @@ -96,7 +89,6 @@ const Clients = ({ }) => { const { t } = useTranslation(); const topClients = useSelector((state) => state.stats.topClients, shallowEqual); - const disallowedClients = useSelector((state) => state.access.disallowed_clients, shallowEqual); return ; diff --git a/client/src/components/Logs/Cells/ClientCell.js b/client/src/components/Logs/Cells/ClientCell.js index a468bfcb..56a3440c 100644 --- a/client/src/components/Logs/Cells/ClientCell.js +++ b/client/src/components/Logs/Cells/ClientCell.js @@ -11,12 +11,16 @@ import IconTooltip from './IconTooltip'; import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell'; import { toggleClientBlock } from '../../../actions/access'; import { getBlockClientInfo } from './helpers'; +import { getStats } from '../../../actions/stats'; +import { updateLogs } from '../../../actions/queryLogs'; const ClientCell = ({ client, domain, info, - info: { name, whois_info }, + info: { + name, whois_info, disallowed, disallowed_rule, + }, reason, }) => { const { t } = useTranslation(); @@ -26,11 +30,6 @@ const ClientCell = ({ const isDetailed = useSelector((state) => state.queryLogs.isDetailed); const [isOptionsOpened, setOptionsOpened] = useState(false); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, - shallowEqual, - ); - const autoClient = autoClients.find((autoClient) => autoClient.name === client); const source = autoClient?.source; const whoisAvailable = whois_info && Object.keys(whois_info).length > 0; @@ -66,43 +65,50 @@ const ClientCell = ({ const { confirmMessage, buttonKey: blockingClientKey, - type, - } = getBlockClientInfo(client, disallowed_clients); + isNotInAllowedList, + } = getBlockClientInfo(client, disallowed, disallowed_rule); const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; const clientNameBlockingFor = getBlockingClientName(clients, client); - const BUTTON_OPTIONS_TO_ACTION_MAP = { - [blockingForClientKey]: () => { - dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); + const BUTTON_OPTIONS = [ + { + name: blockingForClientKey, + onClick: () => { + dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); + }, }, - [blockingClientKey]: () => { - const message = `${type === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`; - if (window.confirm(message)) { - dispatch(toggleClientBlock(type, client)); - } + { + name: blockingClientKey, + onClick: async () => { + if (window.confirm(confirmMessage)) { + await dispatch(toggleClientBlock(client, disallowed, disallowed_rule)); + await dispatch(updateLogs()); + } + }, + disabled: isNotInAllowedList, }, - }; + ]; const onClick = async () => { await dispatch(toggleBlocking(buttonType, domain)); + await dispatch(getStats()); }; - const getOptions = (optionToActionMap) => { - const options = Object.entries(optionToActionMap); + const getOptions = (options) => { if (options.length === 0) { return null; } - return <>{options - .map(([name, onClick]) =>
{options.map(({ name, onClick, disabled }) =>
)}; + )}; }; - const content = getOptions(BUTTON_OPTIONS_TO_ACTION_MAP); + const content = getOptions(BUTTON_OPTIONS); const buttonClass = classNames('button-action button-action--main', { 'button-action--unblock': isFiltered, @@ -168,6 +174,8 @@ ClientCell.propTypes = { city: propTypes.string, orgname: propTypes.string, }), + disallowed: propTypes.bool.isRequired, + disallowed_rule: propTypes.string.isRequired, }), ]), reason: propTypes.string.isRequired, diff --git a/client/src/components/Logs/Cells/helpers/index.js b/client/src/components/Logs/Cells/helpers/index.js index 61e7ff5c..b843ed99 100644 --- a/client/src/components/Logs/Cells/helpers/index.js +++ b/client/src/components/Logs/Cells/helpers/index.js @@ -1,19 +1,18 @@ -import { getIpMatchListStatus } from '../../../../helpers/helpers'; -import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS } from '../../../../helpers/constants'; +import i18next from 'i18next'; export const BUTTON_PREFIX = 'btn_'; -export const getBlockClientInfo = (client, disallowed_clients) => { - const ipMatchListStatus = getIpMatchListStatus(client, disallowed_clients); +export const getBlockClientInfo = (ip, disallowed, disallowed_rule) => { + const confirmMessage = disallowed + ? i18next.t('client_confirm_unblock', { ip: disallowed_rule }) + : `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`; - const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND; - const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK; + const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client'); + const isNotInAllowedList = disallowed && disallowed_rule === ''; - const confirmMessage = isNotFound ? 'client_confirm_block' : 'client_confirm_unblock'; - const buttonKey = isNotFound ? 'disallow_this_client' : 'allow_this_client'; return { confirmMessage, buttonKey, - type, + isNotInAllowedList, }; }; diff --git a/client/src/components/Logs/Cells/index.js b/client/src/components/Logs/Cells/index.js index 4f96a5e9..2e2635d9 100644 --- a/client/src/components/Logs/Cells/index.js +++ b/client/src/components/Logs/Cells/index.js @@ -32,6 +32,7 @@ import ClientCell from './ClientCell'; import '../Logs.css'; import { toggleClientBlock } from '../../../actions/access'; import { getBlockClientInfo, BUTTON_PREFIX } from './helpers'; +import { updateLogs } from '../../../actions/queryLogs'; const Row = memo(({ style, @@ -49,21 +50,19 @@ const Row = memo(({ const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual); const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual); - const disallowed_clients = useSelector( - (state) => state.access.disallowed_clients, - shallowEqual, - ); - const clients = useSelector((state) => state.dashboard.clients); const onClick = () => { - if (!isSmallScreen) { return; } + if (!isSmallScreen) { + return; + } const { answer_dnssec, client, domain, elapsedMs, info, + info: { disallowed, disallowed_rule }, reason, response, time, @@ -94,7 +93,7 @@ const Row = memo(({ const isFiltered = checkFiltered(reason); const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST - || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; + || reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK; const onToggleBlock = () => { @@ -113,8 +112,8 @@ const Row = memo(({ const { confirmMessage, buttonKey: blockingClientKey, - type: blockType, - } = getBlockClientInfo(client, disallowed_clients); + isNotInAllowedList, + } = getBlockClientInfo(client, disallowed, disallowed_rule); const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only'; const clientNameBlockingFor = getBlockingClientName(clients, client); @@ -123,13 +122,33 @@ const Row = memo(({ dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor)); }; - const onBlockingClientClick = () => { - const message = `${blockType === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`; - if (window.confirm(message)) { - dispatch(toggleClientBlock(blockType, client)); + const onBlockingClientClick = async () => { + if (window.confirm(confirmMessage)) { + await dispatch(toggleClientBlock(client, disallowed, disallowed_rule)); + await dispatch(updateLogs()); + setModalOpened(false); } }; + const blockButton = ; + + const blockForClientButton = ; + + const blockClientButton = ; + const detailedData = { time_table_header: formatTime(time, LONG_TIME_FORMAT), date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS), @@ -144,12 +163,12 @@ const Row = memo(({ table_name: tracker?.name, category_label: hasTracker && captitalizeWords(tracker.category), tracker_source: hasTracker && sourceData - && {sourceData.name} - , + && {sourceData.name} + , response_details: 'title', install_settings_dns: upstream, elapsed: formattedElapsedMs, @@ -166,12 +185,9 @@ const Row = memo(({ source_label: source, validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false, original_response: originalResponse?.join('\n'), - [BUTTON_PREFIX + buttonType]:
{t(buttonType)}
, - [BUTTON_PREFIX + blockingForClientKey]:
{t(blockingForClientKey)}
, - [BUTTON_PREFIX + blockingClientKey]:
{t(blockingClientKey)}
, + [BUTTON_PREFIX + buttonType]: blockButton, + [BUTTON_PREFIX + blockingForClientKey]: blockForClientButton, + [BUTTON_PREFIX + blockingClientKey]: blockClientButton, }; setDetailedDataCurrent(processContent(detailedData)); diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index b230ab8b..901c3ab0 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -96,7 +96,7 @@ } .bg--danger { - color: var(--danger); + color: var(--danger) !important; } .form-control--search { @@ -293,7 +293,20 @@ background: var(--btn-unblock-disabled); } -.button-action--arrow-option:hover { +.button-action--arrow-option { + background: transparent; + border: 0; + display: block; + width: 100%; + text-align: left; + color: inherit; +} + +.button-action--arrow-option:disabled { + display: none; +} + +.tooltip-custom__container .button-action--arrow-option:not(:disabled):hover { cursor: pointer; background: var(--gray-f3); overflow: hidden; @@ -327,7 +340,7 @@ } .logs__row--red { - background-color: var(--red); + background-color: var(--red) !important; } .logs__row--white { diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 08fd1529..70f9e317 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -479,12 +479,6 @@ export const DNS_REQUEST_OPTIONS = { LOAD_BALANCING: '', }; -export const IP_MATCH_LIST_STATUS = { - NOT_FOUND: 'NOT_FOUND', // not found in the list - EXACT: 'EXACT', // found exact match (including the match of short and long forms) - CIDR: 'CIDR', // the ip is in the specified CIDR range -}; - export const DHCP_FORM_NAMES = { DHCPv4: 'dhcpv4', DHCPv6: 'dhcpv6', diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index b0bbea5a..934268ac 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -25,7 +25,6 @@ import { DHCP_VALUES_PLACEHOLDERS, FILTERED, FILTERED_STATUS, - IP_MATCH_LIST_STATUS, SERVICES_ID_NAME_MAP, STANDARD_DNS_PORT, STANDARD_HTTPS_PORT, @@ -133,16 +132,14 @@ export const normalizeTopStats = (stats) => ( })) ); -export const addClientInfo = (data, clients, param) => ( - data.map((row) => { - const clientIp = row[param]; - const info = clients.find((item) => item[clientIp]) || ''; - return { - ...row, - info: info?.[clientIp] ?? '', - }; - }) -); +export const addClientInfo = (data, clients, param) => data.map((row) => { + const clientIp = row[param]; + const info = clients.find((item) => item[clientIp]) || ''; + return { + ...row, + info: info?.[clientIp] ?? '', + }; +}); export const normalizeFilters = (filters) => ( filters ? filters.map((filter) => { @@ -530,75 +527,6 @@ export const isIpInCidr = (ip, cidr) => { } }; -/** - * The purpose of this method is to quickly check - * if this IP can possibly be in the specified CIDR range. - * - * @param ip {string} - * @param listItem {string} - * @returns {boolean} - */ -const isIpQuickMatchCIDR = (ip, listItem) => { - const ipv6 = ip.indexOf(':') !== -1; - const cidrIpv6 = listItem.indexOf(':') !== -1; - if (ipv6 !== cidrIpv6) { - // CIDR is for a different IP type - return false; - } - - if (cidrIpv6) { - // We don't do quick check for IPv6 addresses - return true; - } - - const idx = listItem.indexOf('/'); - if (idx === -1) { - // Not a CIDR, return false immediately - return false; - } - - const cidrIp = listItem.substring(0, idx); - const cidrRange = parseInt(listItem.substring(idx + 1), 10); - if (Number.isNaN(cidrRange)) { - // Not a valid CIDR - return false; - } - - const parts = cidrIp.split('.'); - if (parts.length !== 4) { - // Invalid IP, return immediately - return false; - } - - // Now depending on the range we check if the IP can possibly be in that range - if (cidrRange < 8) { - // Use the slow approach - return true; - } - - if (cidrRange < 16) { - // Check the first part - // Example: 0.0.0.0/8 matches 0.*.*.* - return ip.indexOf(`${parts[0]}.`) === 0; - } - - if (cidrRange < 24) { - // Check the first two parts - // Example: 0.0.0.0/16 matches 0.0.*.* - return ip.indexOf(`${parts[0]}.${parts[1]}.`) === 0; - } - - if (cidrRange <= 32) { - // Check the first two parts - // Example: 0.0.0.0/16 matches 0.0.*.* - return ip.indexOf(`${parts[0]}.${parts[1]}.${parts[2]}.`) === 0; - } - - // range for IPv4 CIDR cannot be more than 32 - // no need to check further, this CIDR is invalid - return false; -}; - /** * * @param ipOrCidr @@ -622,50 +550,6 @@ export const findAddressType = (address) => { } }; -/** - * @param ip {string} - * @param list {string} - * @returns {'EXACT' | 'CIDR' | 'NOT_FOND'} - */ -export const getIpMatchListStatus = (ip, list) => { - if (!ip || !list) { - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } - - const listArr = list.trim() - .split('\n'); - - try { - for (let i = 0; i < listArr.length; i += 1) { - const listItem = listArr[i]; - - if (ip === listItem.trim()) { - return IP_MATCH_LIST_STATUS.EXACT; - } - - // Using ipaddr.js is quite slow so we first do a quick check - // to see if it's possible that this IP may be in the specified CIDR range - if (isIpQuickMatchCIDR(ip, listItem)) { - const parsedIp = ipaddr.parse(ip); - const isItemAnIp = ipaddr.isValid(listItem); - const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem); - - if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) { - return IP_MATCH_LIST_STATUS.EXACT; - } - - if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) { - return IP_MATCH_LIST_STATUS.CIDR; - } - } - } - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } catch (e) { - console.error(e); - return IP_MATCH_LIST_STATUS.NOT_FOUND; - } -}; - /** * @param ids {string[]} * @returns {Object} diff --git a/client/src/reducers/dhcp.js b/client/src/reducers/dhcp.js index 5f9f0f58..d6e2868a 100644 --- a/client/src/reducers/dhcp.js +++ b/client/src/reducers/dhcp.js @@ -2,7 +2,6 @@ import { handleActions } from 'redux-actions'; import * as actions from '../actions'; import { enrichWithConcatenatedIpAddresses } from '../helpers/helpers'; -// todo: figure out if we cat get rid of redux-form state duplication const dhcp = handleActions( { [actions.getDhcpStatusRequest]: (state) => ({ diff --git a/client/webpack.dev.js b/client/webpack.dev.js index afcc3506..3d34cb28 100644 --- a/client/webpack.dev.js +++ b/client/webpack.dev.js @@ -44,6 +44,7 @@ const getDevServerConfig = (proxyUrl = BASE_URL) => { proxy: { [proxyUrl]: `http://${devServerHost}:${port}`, }, + open: true, }; };