Files
AdGuardHome/client/src/components/Logs/Cells/index.js
Eugene Burkov 6a4a9a0239 cherry-pick: 3978 Query Log ECS
Merge in DNS/adguard-home from 3978-ecs-ip to master

Updates #3978.

Squashed commit of the following:

commit 915b94afa4b6d90169f73d4fa171bc81bcc267a7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:46:40 2022 +0300

    all: rm dot

commit 2dd2ed081b199de7e5d8269dae5d08d53b5eea6d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:42:45 2022 +0300

    client: imp txt

commit 8d5a23df739f0b650f9f3870141fd83e8fa0c1e0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:36:04 2022 +0300

    client: imp text

commit 69c856749a20144822ef3f1f67c5f3e3c24f5374
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:24:56 2022 +0300

    client: imp description

commit cd0150128ad29d1874492735a5d621c0803ad0bd
Merge: 28181fbc e0b557ed
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 2 21:02:16 2022 +0300

    Merge branch 'master' into 3978-ecs-ip

commit 28181fbc79eb22e7fd13cbd1d5a3c040af9fa2a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 2 20:45:50 2022 +0300

    client: show ecs

commit cdc5e7f8c4155b798426d815eed0da547ef6efb7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:15:56 2022 +0300

    openapi: fix milestone

commit 404d6d822fa1ba4ed4cd41d92d4c1b805342fe55
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:08:21 2022 +0300

    all: fix deps, docs

commit 8fb80526f1e251d3b7b193c53a4a6dee0e22c145
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 19:39:34 2022 +0300

    all: add querylog ecs backend
2022-06-02 16:29:22 +03:00

267 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { memo } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import propTypes from 'prop-types';
import {
captitalizeWords,
checkFiltered,
getRulesToFilterList,
formatDateTime,
formatElapsedMs,
formatTime,
getBlockingClientName,
getServiceName,
processContent,
} from '../../../helpers/helpers';
import {
BLOCK_ACTIONS,
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
FILTERED_STATUS,
FILTERED_STATUS_TO_META_MAP,
LONG_TIME_FORMAT,
QUERY_STATUS_COLORS,
SCHEME_TO_PROTOCOL_MAP,
} from '../../../helpers/constants';
import { getSourceData } from '../../../helpers/trackers/trackers';
import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
import DateCell from './DateCell';
import DomainCell from './DomainCell';
import ResponseCell from './ResponseCell';
import ClientCell from './ClientCell';
import { toggleClientBlock } from '../../../actions/access';
import { getBlockClientInfo, BUTTON_PREFIX } from './helpers';
import { updateLogs } from '../../../actions/queryLogs';
import '../Logs.css';
const Row = memo(({
style,
rowProps,
rowProps: { reason },
isSmallScreen,
setDetailedDataCurrent,
setButtonType,
setModalOpened,
}) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const processingSet = useSelector((state) => state.access.processingSet);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const clients = useSelector((state) => state.dashboard.clients);
const onClick = () => {
if (!isSmallScreen) {
return;
}
const {
answer_dnssec,
client,
domain,
elapsedMs,
client_info,
response,
time,
tracker,
upstream,
type,
client_proto,
client_id,
rules,
originalResponse,
status,
service_name,
cached,
} = rowProps;
const hasTracker = !!tracker;
const autoClient = autoClients
.find((autoClient) => autoClient.name === client);
const source = autoClient?.source;
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
const isFiltered = checkFiltered(reason);
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const onToggleBlock = () => {
dispatch(toggleBlocking(buttonType, domain));
};
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
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 {
confirmMessage,
buttonKey: blockingClientKey,
lastRuleInAllowlist,
} = getBlockClientInfo(
client,
client_info?.disallowed || false,
client_info?.disallowed_rule || '',
allowedСlients,
);
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
const clientNameBlockingFor = getBlockingClientName(clients, client);
const upstreamString = cached
? t('served_from_cache', { value: upstream, i: <i /> })
: upstream;
const onBlockingForClientClick = () => {
dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor));
};
const onBlockingClientClick = async () => {
if (window.confirm(confirmMessage)) {
await dispatch(
toggleClientBlock(
client,
client_info?.disallowed || false,
client_info?.disallowed_rule || '',
),
);
await dispatch(updateLogs());
setModalOpened(false);
}
};
const blockButton = <button
className={classNames('title--border text-center button-action--arrow-option', { 'bg--danger': !isBlocked })}
onClick={onToggleBlock}>
{t(buttonType)}
</button>;
const blockForClientButton = <button
className='text-center font-weight-bold py-2 button-action--arrow-option'
onClick={onBlockingForClientClick}>
{t(blockingForClientKey)}
</button>;
const blockClientButton = <button
className='text-center font-weight-bold py-2 button-action--arrow-option'
onClick={onBlockingClientClick}
disabled={processingSet || lastRuleInAllowlist}>
{t(blockingClientKey)}
</button>;
const detailedData = {
time_table_header: formatTime(time, LONG_TIME_FORMAT),
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
encryption_status: isBlocked
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name
&& { service_name: getServiceName(service_name) }),
domain,
type_table_header: type,
protocol,
known_tracker: hasTracker && 'title',
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>,
response_details: 'title',
install_settings_dns: upstreamString,
elapsed: formattedElapsedMs,
...(rules.length > 0
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
),
response_table_header: response?.join('\n'),
response_code: status,
client_details: 'title',
ip_address: client,
name: client_info?.name || client_id,
country: client_info?.whois?.country,
city: client_info?.whois?.city,
network: client_info?.whois?.orgname,
source_label: source,
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
original_response: originalResponse?.join('\n'),
[BUTTON_PREFIX + buttonType]: blockButton,
[BUTTON_PREFIX + blockingForClientKey]: blockForClientButton,
[BUTTON_PREFIX + blockingClientKey]: blockClientButton,
};
setDetailedDataCurrent(processContent(detailedData));
setButtonType(buttonType);
setModalOpened(true);
};
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const className = classNames('d-flex px-5 logs__row',
`logs__row--${FILTERED_STATUS_TO_META_MAP?.[reason]?.COLOR ?? QUERY_STATUS_COLORS.WHITE}`, {
'logs__cell--detailed': isDetailed,
});
return <div style={style} className={className} onClick={onClick} role="row">
<DateCell {...rowProps} />
<DomainCell {...rowProps} />
<ResponseCell {...rowProps} />
<ClientCell {...rowProps} />
</div>;
});
Row.displayName = 'Row';
Row.propTypes = {
style: propTypes.object,
rowProps: propTypes.shape({
reason: propTypes.string.isRequired,
answer_dnssec: propTypes.bool.isRequired,
client: propTypes.string.isRequired,
domain: propTypes.string.isRequired,
elapsedMs: propTypes.string.isRequired,
response: propTypes.array.isRequired,
time: propTypes.string.isRequired,
tracker: propTypes.object,
upstream: propTypes.string.isRequired,
cached: propTypes.bool.isRequired,
type: propTypes.string.isRequired,
client_proto: propTypes.string.isRequired,
client_id: propTypes.string,
ecs: propTypes.string,
client_info: propTypes.shape({
name: propTypes.string.isRequired,
whois: propTypes.shape({
country: propTypes.string,
city: propTypes.string,
orgname: propTypes.string,
}).isRequired,
disallowed: propTypes.bool.isRequired,
disallowed_rule: propTypes.string.isRequired,
}),
rules: propTypes.arrayOf(propTypes.shape({
text: propTypes.string.isRequired,
filter_list_id: propTypes.number.isRequired,
})),
originalResponse: propTypes.array,
status: propTypes.string.isRequired,
service_name: propTypes.string,
}).isRequired,
isSmallScreen: propTypes.bool.isRequired,
setDetailedDataCurrent: propTypes.func.isRequired,
setButtonType: propTypes.func.isRequired,
setModalOpened: propTypes.func.isRequired,
};
export default Row;