- client: Fix query logs UI issues

Close #1828

Squashed commit of the following:

commit bf96b9f2cc99a94a1289c47b04cde136cf0c9f37
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 20:44:22 2020 +0300

    Remove field domain from response tooltip

commit bba35fdbed6d1e2e532c8effaf2da69de3f2c078
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 20:29:24 2020 +0300

    Unify mobile modal

commit 5ee2da41594497fd64eadf0fd64c24afdad94e44
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 19:02:47 2020 +0300

    Delete unnecessary comment

commit ac3a3f13009ad508ddd7eb31aadf7e590a5c2829
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:59:44 2020 +0300

    minor

commit 4b1969a53ce2fcfc859c228b27816459bd8bd1d0
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:56:51 2020 +0300

    Fix safari mediaQuery change listener issue

commit d85de5c4e90d2460632e593cffe3ceea3137e92c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 18:10:30 2020 +0300

    Fix logs input search markup (for different locales)

commit 6d704399c5379dfda663503b3a5b1d12a92732b2
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 16:05:35 2020 +0300

    Fix whois_info markup, fix domain name overflow

commit 4c900f60a9c6b71b427d968177252eb168c424c0
Merge: a3955c98 38366ba8
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 13:42:43 2020 +0300

    Merge branch 'master' into fix/1828

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-15 20:55:13 +03:00
parent 38366ba801
commit e46db985e8
10 changed files with 98 additions and 119 deletions

View File

@ -62,7 +62,7 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
return (
<>
<div className="logs__row logs__row--overflow logs__row--column">
{formatClientCell(row, t)}
{formatClientCell(row, true)}
</div>
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}

View File

@ -9,7 +9,7 @@ const DomainCell = ({ value }) => {
return (
<div className="logs__row">
<div className="logs__text logs__text--domain" title={value}>
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}

View File

@ -33,7 +33,7 @@ const getClientCell = ({
const isFiltered = checkFiltered(reason);
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !name,
'mt-2': isDetailed && !name && !whois_info,
'white-space--nowrap': isDetailed,
});
@ -80,9 +80,9 @@ const getClientCell = ({
})}
<div
className={nameClass}>
<div data-tip={true} data-for={id}>{formatClientCell(row, t, isDetailed)}</div>
<div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
{isDetailed && name
&& <div className="detailed-info d-none d-sm-block logs__text"
&& !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
title={name}>{name}</div>}
</div>
{renderBlockingButton(isFiltered, domain)}

View File

@ -9,8 +9,7 @@ import getHintElement from './getHintElement';
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
const {
reason, filterId, rule, status, upstream, elapsedMs,
domain, response, originalResponse,
reason, filterId, rule, status, upstream, elapsedMs, response, originalResponse,
} = row.original;
const { filters, whitelistFilters } = filtering;
@ -41,7 +40,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
const FILTERED_STATUS_TO_FIELDS_MAP = {
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
domain,
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
@ -49,7 +47,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
response_table_header: renderResponses(response),
},
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
domain,
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
@ -59,7 +56,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
original_response: renderResponses(originalResponse),
},
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
domain,
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
@ -68,21 +64,19 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
response_code: status,
},
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
domain,
encryption_status: boldStatusLabel,
filter,
rule_label: rule,
response_code: status,
},
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
domain,
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
response_code: status,
response_table_header: renderResponses(response),
},
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
domain,
encryption_status: boldStatusLabel,
filter,
rule_label: rule,
@ -93,7 +87,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
},
};
const fields = FILTERED_STATUS_TO_FIELDS_MAP[reason]
const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
@ -108,7 +102,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
contentItemClass: 'text-truncate key-colon o-hidden',
xlinkHref: 'question',
title: 'response_details',
content: fields,
content,
placement: 'bottom',
})}
<div className="text-truncate">

View File

@ -144,18 +144,20 @@ const Form = (props) => {
e.preventDefault();
}}
>
<Field
id={FORM_NAMES.search}
name={FORM_NAMES.search}
component={renderFilterField}
type="text"
className={classNames('form-control--search form-control--transparent', className)}
placeholder={t('domain_or_client')}
tooltip={t('query_log_strict_search')}
onClearInputClick={onInputClear}
onKeyDown={onEnterPress}
normalizeOnBlur={normalizeOnBlur}
/>
<div className="field__search">
<Field
id={FORM_NAMES.search}
name={FORM_NAMES.search}
component={renderFilterField}
type="text"
className={classNames('form-control--search form-control--transparent', className)}
placeholder={t('domain_or_client')}
tooltip={t('query_log_strict_search')}
onClearInputClick={onInputClear}
onKeyDown={onEnterPress}
normalizeOnBlur={normalizeOnBlur}
/>
</div>
<div className="field__select">
<Field
name={FORM_NAMES.response_status}

View File

@ -13,7 +13,8 @@
}
.card-table .logs__row {
overflow: visible;
overflow: hidden;
text-overflow: ellipsis;
}
.logs__row--center {
@ -57,10 +58,6 @@
width: 100%;
}
.logs__text--domain {
max-width: 285px;
}
.logs__text--wrap,
.logs__text--whois {
line-height: 1.4;
@ -202,6 +199,7 @@
.logs__whois {
display: inline;
font-size: 12px;
white-space: nowrap;
}
.logs__whois::after {
@ -455,15 +453,11 @@
color: var(--danger);
}
.ml-small {
margin-left: 3.3125rem;
}
.form-control--search {
width: 39.125rem;
box-shadow: 0 1px 0 #ddd;
padding: 0 2.5rem;
height: 2.25rem;
flex-grow: 1;
}
.form-control--transparent {
@ -493,31 +487,12 @@
}
.form-control--container {
max-width: 100%;
flex: auto;
}
@media (max-width: 1279.98px) {
.form-control--search {
max-width: 30.125rem;
}
.form-control--container {
max-width: 70%;
}
.form-control--search {
max-width: 50%;
}
}
@media (max-width: 991.98px) {
.form-control--search {
max-width: 40%;
}
.form-control--container {
max-width: 100%;
}
.field__search {
display: flex;
flex-grow: 1;
}
@media (max-width: 767.98px) {
@ -528,6 +503,19 @@
.ml-small {
margin-left: 1.5rem;
}
.form-control--container {
width: 100%;
flex-direction: column;
}
.form-control--search {
width: 100%;
}
.field__select {
margin-top: 1.5rem;
}
}
@media (max-width: 575px) {
@ -544,16 +532,6 @@
}
}
@media (max-width: 500px) {
.form-control--search {
max-width: 85%;
}
.field__select {
margin-top: 1.5rem;
}
}
.loading__container > .-loading-inner {
top: 10rem !important;
bottom: initial !important;

View File

@ -25,7 +25,7 @@ import {
formatDateTime,
formatElapsedMs,
formatTime,
processContent,
} from '../../helpers/helpers';
import Loading from '../ui/Loading';
import { getSourceData } from '../../helpers/trackers/trackers';
@ -302,6 +302,7 @@ const Table = (props) => {
filterId,
rule,
originalResponse,
status,
} = rowInfo.original;
const hasTracker = !!tracker;
@ -328,17 +329,20 @@ const Table = (props) => {
};
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 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 { filters, whitelistFilters } = filtering;
const filter = getFilterName(filters, whitelistFilters, filterId, t);
const detailedData = {
time_table_header: formatTime(time, LONG_TIME_FORMAT),
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
encryption_status: status,
encryption_status: isBlocked
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
domain,
type_table_header: type,
protocol,
@ -346,12 +350,19 @@ const Table = (props) => {
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>,
&& <a
href={sourceData.url}
target="_blank"
rel="noopener noreferrer"
className="link--green">{sourceData.name}
</a>,
response_details: 'title',
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter: isBlocked ? filter : null,
rule_label: rule,
response_table_header: response?.join('\n'),
response_code: status,
client_details: 'title',
ip_address: client,
name: info?.name,
@ -360,41 +371,14 @@ const Table = (props) => {
network,
source_label: source,
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
[buttonType]: <div onClick={onToggleBlock}
className="title--border bg--danger text-center">{t(buttonType)}</div>,
};
const { filters, whitelistFilters } = filtering;
const filter = getFilterName(filters, whitelistFilters, filterId, t);
const detailedDataBlocked = {
time_table_header: formatTime(time, LONG_TIME_FORMAT),
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
encryption_status: statusBlocked,
domain,
type_table_header: type,
protocol,
known_tracker: 'title',
table_name: tracker?.name,
category_label: hasTracker && captitalizeWords(tracker.category),
source_label: hasTracker && sourceData
&& <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
className="link--green">{sourceData.name}</a>,
response_details: 'title',
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter,
rule_label: rule,
response_table_header: response?.join('\n'),
original_response: originalResponse?.join('\n'),
[buttonType]: <div onClick={onToggleBlock}
className="title--border text-center">{t(buttonType)}</div>,
className={classNames('title--border text-center', {
'bg--danger': isBlocked,
})}>{t(buttonType)}</div>,
};
const detailedDataCurrent = isBlocked ? detailedDataBlocked : detailedData;
setDetailedDataCurrent(detailedDataCurrent);
setDetailedDataCurrent(processContent(detailedData));
setButtonType(buttonType);
setModalOpened(true);
}

View File

@ -5,6 +5,7 @@ import Modal from 'react-modal';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import queryString from 'query-string';
import classNames from 'classnames';
import {
BLOCK_ACTIONS,
TABLE_DEFAULT_PAGE_SIZE,
@ -27,7 +28,7 @@ import {
import { addSuccessToast } from '../../actions/toasts';
import './Logs.css';
export const processContent = (data, buttonType) => Object.entries(data)
const processContent = (data, buttonType) => Object.entries(data)
.map(([key, value]) => {
if (!value) {
return null;
@ -49,7 +50,9 @@ export const processContent = (data, buttonType) => Object.entries(data)
return isHidden ? null : <Fragment key={key}>
<div
className={`key__${key} ${keyClass} ${(isBoolean && value === true) ? 'font-weight-bold' : ''}`}>
className={classNames(`key__${key}`, keyClass, {
'font-weight-bold': isBoolean && value === true,
})}>
<Trans>{isButton ? value : key}</Trans>
</div>
<div className={`value__${key} text-pre text-truncate`}>
@ -133,7 +136,16 @@ const Logs = (props) => {
};
useEffect(() => {
mediaQuery.addEventListener('change', mediaQueryHandler);
try {
mediaQuery.addEventListener('change', mediaQueryHandler);
} catch (e1) {
try {
// Safari 13.1 do not support mediaQuery.addEventListener('change', handler)
mediaQuery.addListener(mediaQueryHandler);
} catch (e2) {
console.error(e2);
}
}
(async () => {
setIsLoading(true);
@ -153,7 +165,16 @@ const Logs = (props) => {
})();
return () => {
mediaQuery.removeEventListener('change', mediaQueryHandler);
try {
mediaQuery.removeEventListener('change', mediaQueryHandler);
} catch (e1) {
try {
mediaQuery.removeListener(mediaQueryHandler);
} catch (e2) {
console.error(e2);
}
}
dispatch(resetFilteredLogs());
};
}, []);

View File

@ -2,14 +2,14 @@ import React from 'react';
import { normalizeWhois } from './helpers';
import { WHOIS_ICONS } from './constants';
const getFormattedWhois = (whois, t) => {
const getFormattedWhois = (whois) => {
const whoisInfo = normalizeWhois(whois);
return (
Object.keys(whoisInfo)
.map((key) => {
const icon = WHOIS_ICONS[key];
return (
<span className="logs__whois text-muted" key={key} title={t(key)}>
<span className="logs__whois text-muted " key={key} title={whoisInfo[key]}>
{icon && (
<>
<svg className="logs__whois-icon icons">
@ -24,7 +24,7 @@ const getFormattedWhois = (whois, t) => {
);
};
export const formatClientCell = (row, t, isDetailed = false) => {
export const formatClientCell = (row, isDetailed = false) => {
const { value, original: { info } } = row;
let whoisContainer = '';
let nameContainer = value;
@ -33,7 +33,7 @@ export const formatClientCell = (row, t, isDetailed = false) => {
const { name, whois_info } = info;
if (name) {
nameContainer = isDetailed
nameContainer = !whois_info && isDetailed
? <small title={value}>{value}</small>
: <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
{name}
@ -42,10 +42,10 @@ export const formatClientCell = (row, t, isDetailed = false) => {
</div>;
}
if (whois_info) {
if (whois_info && isDetailed) {
whoisContainer = (
<div className="logs__text logs__text--wrap logs__text--whois">
{getFormattedWhois(whois_info, t)}
{getFormattedWhois(whois_info)}
</div>
);
}

View File

@ -318,7 +318,8 @@ export const splitByNewLine = (text) => text.split('\n')
* @returns {string}
*/
export const trimMultilineString = (text) => splitByNewLine(text)
.map((line) => line.trim()).join('\n');
.map((line) => line.trim())
.join('\n');
/**
* @param {string} text
@ -636,7 +637,6 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
response_status,
})}`;
export const processContent = (content) => (Array.isArray(content)
? content.filter(([, value]) => value)
.flat() : content);