- client: Fix query logs UI issues

Close #1828

Squashed commit of the following:

commit a3955c989a939866c6772b147547344b3f8769c4
Merge: c91c41cb 2759d81a
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 15:14:47 2020 +0300

    Merge branch 'master' into fix/1828

commit c91c41cbc5f616e0af1092424e42b909d2f43f7c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 13:48:54 2020 +0300

    Fix cell overflow

commit 19e1d31a40f2e1bb1189a85b72507bcc364d4e0c
Merge: af31f48c a33164bf
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 12:36:44 2020 +0300

    Merge branch 'master' into fix/1828

commit af31f48c4d2699ebfbd2034711c51499b42e40f5
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 10:45:57 2020 +0300

    minor

commit d9507c5f3f5758e587766ae0fa45f1b9ad703ccf
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 10 18:34:22 2020 +0300

    - client: Fix query logs UI issues
This commit is contained in:
Artem Baskal 2020-07-13 15:23:13 +03:00
parent 2759d81afe
commit 8a417604a9
9 changed files with 76 additions and 51 deletions

View File

@ -480,6 +480,7 @@
"whois": "Whois", "whois": "Whois",
"filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts lists.", "filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts lists.",
"blocked_by_response": "Blocked by CNAME or IP in response", "blocked_by_response": "Blocked by CNAME or IP in response",
"blocked_by_cname_or_ip": "Blocked by CNAME or IP",
"try_again": "Try again", "try_again": "Try again",
"domain_desc": "Enter the domain name or wildcard you want to be rewritten.", "domain_desc": "Enter the domain name or wildcard you want to be rewritten.",
"example_rewrite_domain": "rewrite responses for this domain name only.", "example_rewrite_domain": "rewrite responses for this domain name only.",
@ -560,5 +561,6 @@
"filter_category_general_desc": "Lists that block tracking and advertising on most of the devices", "filter_category_general_desc": "Lists that block tracking and advertising on most of the devices",
"filter_category_security_desc": "Lists that specialize on blocking malware, phishing or scam domains", "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_regional_desc": "Lists that focus on regional ads and tracking servers",
"filter_category_other_desc": "Other blocklists" "filter_category_other_desc": "Other blocklists",
"original_response": "Original response"
} }

View File

@ -68,7 +68,7 @@ const getClientCell = ({
return ( return (
<div className="logs__row o-hidden h-100"> <div className="logs__row o-hidden h-100">
{processedData && getHintElement({ {getHintElement({
className: hintClass, className: hintClass,
columnClass: 'grid grid--limited', columnClass: 'grid grid--limited',
tooltipClass: 'px-5 pb-5 pt-4 mw-75', tooltipClass: 'px-5 pb-5 pt-4 mw-75',

View File

@ -33,16 +33,6 @@ const getDomainCell = (props) => {
'my-3': isDetailed, 'my-3': isDetailed,
}); });
const dnssecHint = getHintElement({
className: lockIconClass,
tooltipClass: 'py-4 px-5 pb-45',
canShowTooltip: answer_dnssec,
xlinkHref: 'lock',
columnClass: 'w-100',
content: 'validated_with_dnssec',
placement: 'bottom',
});
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || ''; const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
const ip = type ? `${t('type_table_header')}: ${type}` : ''; const ip = type ? `${t('type_table_header')}: ${type}` : '';
@ -100,7 +90,15 @@ const getDomainCell = (props) => {
return ( return (
<div className="logs__row o-hidden"> <div className="logs__row o-hidden">
{dnssec_enabled && dnssecHint} {dnssec_enabled && getHintElement({
className: lockIconClass,
tooltipClass: 'py-4 px-5 pb-45',
canShowTooltip: answer_dnssec,
xlinkHref: 'lock',
columnClass: 'w-100',
content: 'validated_with_dnssec',
placement: 'bottom',
})}
{trackerHint} {trackerHint}
<div className={valueClass}> <div className={valueClass}>
<div className="text-truncate" title={domain}>{domain}</div> <div className="text-truncate" title={domain}>{domain}</div>

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import './Tooltip.css'; import './Tooltip.css';
import 'react-popper-tooltip/dist/styles.css'; import 'react-popper-tooltip/dist/styles.css';
import { HIDE_TOOLTIP_DELAY } from '../../../helpers/constants'; import { HIDE_TOOLTIP_DELAY } from '../../../helpers/constants';
import { processContent } from '../../../helpers/helpers';
const getHintElement = ({ const getHintElement = ({
className, className,
@ -17,34 +18,34 @@ const getHintElement = ({
placement, placement,
tooltipClass, tooltipClass,
content, content,
renderContent = React.Children.map( renderContent = content ? React.Children.map(
content, processContent(content),
(item, idx) => <div key={idx} className={contentItemClass}> (item, idx) => <div key={idx} className={contentItemClass}>
<Trans>{item || '—'}</Trans> <Trans>{item || '—'}</Trans>
</div>, </div>,
), ) : null,
}) => <TooltipTrigger placement={placement} trigger="hover" delayHide={HIDE_TOOLTIP_DELAY} tooltip={ }) => <TooltipTrigger placement={placement} trigger="hover" delayHide={HIDE_TOOLTIP_DELAY} tooltip={
({ ({
tooltipRef, tooltipRef,
getTooltipProps, getTooltipProps,
}) => <div {...getTooltipProps({ }) => <div {...getTooltipProps({
ref: tooltipRef, ref: tooltipRef,
className: classNames('tooltip__container', tooltipClass, { 'd-none': !canShowTooltip }), className: classNames('tooltip__container', tooltipClass, { 'd-none': !canShowTooltip }),
})} })}
> >
{title && <div className="pb-4 h-25 grid-content font-weight-bold"> {title && <div className="pb-4 h-25 grid-content font-weight-bold">
<Trans>{title}</Trans> <Trans>{title}</Trans>
</div>} </div>}
<div className={classNames(columnClass)}>{renderContent}</div> <div className={classNames(columnClass)}>{renderContent}</div>
</div> </div>
}>{({ }>{({
getTriggerProps, triggerRef, getTriggerProps, triggerRef,
}) => <span {...getTriggerProps({ ref: triggerRef })}> }) => <span {...getTriggerProps({ ref: triggerRef })}>
{xlinkHref && <svg className={className}> {xlinkHref && <svg className={className}>
<use xlinkHref={`#${xlinkHref}`} /> <use xlinkHref={`#${xlinkHref}`} />
</svg>} </svg>}
</span>} </span>}
</TooltipTrigger>; </TooltipTrigger>;
getHintElement.propTypes = { getHintElement.propTypes = {
className: PropTypes.string, className: PropTypes.string,

View File

@ -9,18 +9,24 @@ import getHintElement from './getHintElement';
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => { const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
const { const {
reason, filterId, rule, status, upstream, elapsedMs, domain, response, reason, filterId, rule, status, upstream, elapsedMs,
domain, response, originalResponse,
} = row.original; } = row.original;
const { filters, whitelistFilters } = filtering; const { filters, whitelistFilters } = filtering;
const formattedElapsedMs = formatElapsedMs(elapsedMs, t); const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
const statusLabel = t(FILTERED_STATUS_TO_META_MAP[reason]?.label || reason); const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>; const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
const filter = getFilterName(filters, whitelistFilters, filterId, t); const filter = getFilterName(filters, whitelistFilters, filterId, t);
const renderResponses = (responseArr) => { const renderResponses = (responseArr) => {
if (responseArr.length === 0) { if (responseArr?.length === 0) {
return ''; return '';
} }
@ -50,6 +56,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
filter, filter,
rule_label: rule, rule_label: rule,
response_code: status, response_code: status,
original_response: renderResponses(originalResponse),
}, },
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: { [FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
domain, domain,
@ -78,9 +85,11 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
domain, domain,
encryption_status: boldStatusLabel, encryption_status: boldStatusLabel,
filter, filter,
rule_label: rule,
install_settings_dns: upstream, install_settings_dns: upstream,
elapsed: formattedElapsedMs, elapsed: formattedElapsedMs,
response_code: status, response_code: status,
original_response: renderResponses(originalResponse),
}, },
}; };
@ -88,13 +97,11 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason]) ? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound); : Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
const detailedInfo = reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|| reason === FILTERED_STATUS.FILTERED_BLACK_LIST
? filter : formattedElapsedMs;
return ( return (
<div className="logs__row"> <div className="logs__row">
{fields && getHintElement({ {getHintElement({
className: classNames('icons mr-4 icon--small cursor--pointer icon--light-gray', { 'my-3': isDetailed }), className: classNames('icons mr-4 icon--small cursor--pointer icon--light-gray', { 'my-3': isDetailed }),
columnClass: 'grid grid--limited', columnClass: 'grid grid--limited',
tooltipClass: 'px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details', tooltipClass: 'px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details',
@ -107,7 +114,8 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
<div className="text-truncate"> <div className="text-truncate">
<div className="text-truncate" title={statusLabel}>{statusLabel}</div> <div className="text-truncate" title={statusLabel}>{statusLabel}</div>
{isDetailed && <div {isDetailed && <div
className="detailed-info d-none d-sm-block pt-1 text-truncate" title={detailedInfo}>{detailedInfo}</div>} className="detailed-info d-none d-sm-block pt-1 text-truncate"
title={detailedInfo}>{detailedInfo}</div>}
</div> </div>
</div> </div>
); );

View File

@ -12,7 +12,7 @@ import {
FILTERED_STATUS_TO_META_MAP, FILTERED_STATUS_TO_META_MAP,
TABLE_DEFAULT_PAGE_SIZE, TABLE_DEFAULT_PAGE_SIZE,
SCHEME_TO_PROTOCOL_MAP, SCHEME_TO_PROTOCOL_MAP,
CUSTOM_FILTERING_RULES_ID, CUSTOM_FILTERING_RULES_ID, FILTERED_STATUS,
} from '../../helpers/constants'; } from '../../helpers/constants';
import getDateCell from './Cells/getDateCell'; import getDateCell from './Cells/getDateCell';
import getDomainCell from './Cells/getDomainCell'; import getDomainCell from './Cells/getDomainCell';
@ -300,6 +300,8 @@ const Table = (props) => {
type, type,
client_proto, client_proto,
filterId, filterId,
rule,
originalResponse,
} = rowInfo.original; } = rowInfo.original;
const hasTracker = !!tracker; const hasTracker = !!tracker;
@ -317,12 +319,16 @@ const Table = (props) => {
const formattedElapsedMs = formatElapsedMs(elapsedMs, t); const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
const isFiltered = checkFiltered(reason); 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 buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const onToggleBlock = () => { const onToggleBlock = () => {
toggleBlocking(buttonType, domain); toggleBlocking(buttonType, domain);
}; };
const status = t(FILTERED_STATUS_TO_META_MAP[reason]?.label || reason); const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
const status = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
const statusBlocked = <div className="bg--danger">{status}</div>; const statusBlocked = <div className="bg--danger">{status}</div>;
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || ''; const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
@ -379,12 +385,14 @@ const Table = (props) => {
install_settings_dns: upstream, install_settings_dns: upstream,
elapsed: formattedElapsedMs, elapsed: formattedElapsedMs,
filter, filter,
rule_label: rule,
response_table_header: response?.join('\n'), response_table_header: response?.join('\n'),
original_response: originalResponse?.join('\n'),
[buttonType]: <div onClick={onToggleBlock} [buttonType]: <div onClick={onToggleBlock}
className="title--border text-center">{t(buttonType)}</div>, className="title--border text-center">{t(buttonType)}</div>,
}; };
const detailedDataCurrent = isFiltered ? detailedDataBlocked : detailedData; const detailedDataCurrent = isBlocked ? detailedDataBlocked : detailedData;
setDetailedDataCurrent(detailedDataCurrent); setDetailedDataCurrent(detailedDataCurrent);
setButtonType(buttonType); setButtonType(buttonType);

View File

@ -24,6 +24,10 @@ const INITIAL_REQUEST_DATA = ['', TABLE_FIRST_PAGE, INITIAL_REQUEST];
export const processContent = (data, buttonType) => Object.entries(data) export const processContent = (data, buttonType) => Object.entries(data)
.map(([key, value]) => { .map(([key, value]) => {
if (!value) {
return null;
}
const isTitle = value === 'title'; const isTitle = value === 'title';
const isButton = key === buttonType; const isButton = key === buttonType;
const isBoolean = typeof value === 'boolean'; const isBoolean = typeof value === 'boolean';

View File

@ -52,7 +52,7 @@ export const formatClientCell = (row, t, isDetailed = false) => {
} }
return ( return (
<div className="logs__text" title={value}> <div className="logs__text mw-100" title={value}>
<> <>
{nameContainer} {nameContainer}
{whoisContainer} {whoisContainer}

View File

@ -86,18 +86,16 @@ export const normalizeLogs = (logs) => logs.map((log) => {
const { host: domain, type } = question; const { host: domain, type } = question;
const response = answer ? answer.map((response) => { const processResponse = (data) => (data ? data.map((response) => {
const { value, type, ttl } = response; const { value, type, ttl } = response;
return `${type}: ${value} (ttl=${ttl})`; return `${type}: ${value} (ttl=${ttl})`;
}) : []; }) : []);
const tracker = getTrackerData(domain);
return { return {
time, time,
domain, domain,
type, type,
response, response: processResponse(answer),
reason, reason,
client, client,
client_proto, client_proto,
@ -106,7 +104,8 @@ export const normalizeLogs = (logs) => logs.map((log) => {
status, status,
serviceName: service_name, serviceName: service_name,
originalAnswer: original_answer, originalAnswer: original_answer,
tracker, originalResponse: processResponse(original_answer),
tracker: getTrackerData(domain),
answer_dnssec, answer_dnssec,
elapsedMs, elapsedMs,
upstream, upstream,
@ -618,3 +617,8 @@ export const selectCompletedFields = (values) => Object.entries(values)
} }
return acc; return acc;
}, {}); }, {});
export const processContent = (content) => (Array.isArray(content)
? content.filter(([, value]) => value)
.flat() : content);