Merge: fix #1421
Full rework of the query log Squashed commit of the following: commit e8a72eb223551f17e637136713dae03accf8ab9e Author: Andrey Meshkov <am@adguard.com> Date: Thu Jun 18 00:31:53 2020 +0300 fix race in whois test commit 801d28197f888fa21f83c9a0b49e3c9472c08513 Merge: 9d9787fdb1c951fbAuthor: Andrey Meshkov <am@adguard.com> Date: Thu Jun 18 00:28:13 2020 +0300 Merge branch 'master' into feature/1421 commit 9d9787fd79b17f76c7baed52c12ac462fd00a5e4 Merge: 4ce337ca 08e238ab Author: Andrey Meshkov <am@adguard.com> Date: Thu Jun 18 00:27:32 2020 +0300 Merge commit 4ce337ca7aec163edf87a038bb25fb44e64f8613 Author: Andrey Meshkov <am@adguard.com> Date: Thu Jun 18 00:22:49 2020 +0300 -(home): fix whois test commit 08e238ab0e723b1e354f58245e9a8d5017b392c9 Author: ArtemBaskal <a.baskal@adguard.com> Date: Thu Jun 18 00:13:41 2020 +0300 Add comments commit 5f108065952bcc25dce1c2eee3f9401d2641a6e9 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 23:47:50 2020 +0300 Make tooltip position absolute for touch commit 4c30a583165e5d007d4b01b657de8751a7bd8c7b Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 20:39:44 2020 +0300 Prevent scroll hide for touch devices commit 62da97931f5921613762614717c62c77ddb6b8db Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 20:06:24 2020 +0300 Review changes: ipad tooltip commit 12dddcca8caca51c157b5d25dfa3ca03ba7f0c06 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 16:59:16 2020 +0300 Add close tooltip event for ipad commit 62191e41d5bf67317f9f1dc6c6af08cbabb4bf90 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 16:39:40 2020 +0300 Add success toast on logs refresh commit 2ebdd6a8124269d737c8060c3247aaf35d85cb8b Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 16:01:37 2020 +0300 Fix pagination commit 5820c92bacd93d05a3d66d42ee95f099e1c5d9e9 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jun 17 11:31:15 2020 +0300 Revert "Render table in chunks" This reverts commit cdfcd849ccddc1bc35591edac7904129431470c9. commit cdfcd849ccddc1bc35591edac7904129431470c9 Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 18:42:18 2020 +0300 Render table in chunks commit cc8c5e64274bf6e806e2e8a4bf305af745c3ed2a Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 17:35:24 2020 +0300 Add pagination button hover effect commit f7e134091a1556784a5fea9d83c50353536126ef Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 16:28:00 2020 +0300 Make loader position absolute commit a7b887b57d903f1f7ac967b861b5cc677728efc4 Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 15:42:20 2020 +0300 Ignore clients find without params commit ecb322fefd4a161d79f28d17fe27827ee91701e4 Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 15:30:48 2020 +0300 Styles changes commit 9323ce3938bf04e1290eade09201ba0790a250c0 Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 14:32:23 2020 +0300 Review styles changes commit e0faa04ba3643f01b2ca99524cdd52b0731725c7 Merge: 9857682315e71435Author: ArtemBaskal <a.baskal@adguard.com> Date: Tue Jun 16 12:08:45 2020 +0300 Merge branch '1421-new-qlog-v2' into feature/1421 commit 9857682371e8d9a3a91933cfb58a26b3470675d9 Author: ArtemBaskal <a.baskal@adguard.com> Date: Mon Jun 15 18:32:02 2020 +0300 Fix response cell ... and 88 more commits
This commit is contained in:
106
client/src/components/Logs/Cells/getClientCell.js
Normal file
106
client/src/components/Logs/Cells/getClientCell.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { formatClientCell } from '../../../helpers/formatClientCell';
|
||||
import getHintElement from './getHintElement';
|
||||
import { checkFiltered } from '../../../helpers/helpers';
|
||||
import { BLOCK_ACTIONS } from '../../../helpers/constants';
|
||||
|
||||
const getClientCell = ({
|
||||
row, t, isDetailed, toggleBlocking, autoClients, processingRules,
|
||||
}) => {
|
||||
const {
|
||||
reason, client, domain, info: { name },
|
||||
} = row.original;
|
||||
|
||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||
const country = autoClient && autoClient.whois_info && autoClient.whois_info.country;
|
||||
const city = autoClient && autoClient.whois_info && autoClient.whois_info.city;
|
||||
const network = autoClient && autoClient.whois_info && autoClient.whois_info.orgname;
|
||||
const source = autoClient && autoClient.source;
|
||||
|
||||
const id = nanoid();
|
||||
|
||||
const data = {
|
||||
address: client,
|
||||
name,
|
||||
country,
|
||||
city,
|
||||
network,
|
||||
source_label: source,
|
||||
};
|
||||
|
||||
const processedData = Object.entries(data);
|
||||
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||
'mt-2': isDetailed && !name,
|
||||
'white-space--nowrap': isDetailed,
|
||||
});
|
||||
|
||||
const hintClass = classNames('icons mr-4 icon--small cursor--pointer icon--light-gray', {
|
||||
'my-3': isDetailed,
|
||||
});
|
||||
|
||||
const renderBlockingButton = (isFiltered, domain) => {
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
|
||||
const buttonClass = classNames('logs__action button__action', {
|
||||
'btn-outline-secondary': isFiltered,
|
||||
'btn-outline-danger': !isFiltered,
|
||||
'logs__action--detailed': isDetailed,
|
||||
});
|
||||
|
||||
const onClick = () => toggleBlocking(buttonType, domain);
|
||||
|
||||
return (
|
||||
<div className={buttonClass}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonClass}`}
|
||||
onClick={onClick}
|
||||
disabled={processingRules}
|
||||
>
|
||||
{t(buttonType)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="logs__row o-hidden h-100">
|
||||
{processedData && getHintElement({
|
||||
className: hintClass,
|
||||
columnClass: 'grid grid--limited',
|
||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75',
|
||||
dataTip: true,
|
||||
xlinkHref: 'question',
|
||||
contentItemClass: 'text-truncate key-colon',
|
||||
title: 'client_details',
|
||||
content: processedData,
|
||||
place: 'bottom',
|
||||
})}
|
||||
<div
|
||||
className={nameClass}>
|
||||
<div data-tip={true} data-for={id}>{formatClientCell(row, t, isDetailed)}</div>
|
||||
{isDetailed && name
|
||||
&& <div className="detailed-info d-none d-sm-block logs__text"
|
||||
title={name}>{name}</div>}
|
||||
</div>
|
||||
{renderBlockingButton(isFiltered, domain)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getClientCell.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
isDetailed: PropTypes.bool.isRequired,
|
||||
toggleBlocking: PropTypes.func.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
processingRules: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default getClientCell;
|
||||
28
client/src/components/Logs/Cells/getDateCell.js
Normal file
28
client/src/components/Logs/Cells/getDateCell.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import { formatTime, formatDateTime } from '../../../helpers/helpers';
|
||||
import {
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
} from '../../../helpers/constants';
|
||||
|
||||
const getDateCell = (row, isDetailed) => {
|
||||
const { time } = row.original;
|
||||
|
||||
if (!time) {
|
||||
return '–';
|
||||
}
|
||||
|
||||
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
|
||||
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
|
||||
|
||||
return (
|
||||
<div className="logs__cell">
|
||||
<div className="logs__time" title={formattedTime}>{formattedTime}</div>
|
||||
{isDetailed && <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={formattedDate}>{formattedDate}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default getDateCell;
|
||||
121
client/src/components/Logs/Cells/getDomainCell.js
Normal file
121
client/src/components/Logs/Cells/getDomainCell.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import getHintElement from './getHintElement';
|
||||
import {
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
LONG_TIME_FORMAT,
|
||||
SCHEME_TO_PROTOCOL_MAP,
|
||||
} from '../../../helpers/constants';
|
||||
import { formatDateTime, formatTime } from '../../../helpers/helpers';
|
||||
|
||||
const getDomainCell = (props) => {
|
||||
const {
|
||||
row, t, isDetailed, dnssec_enabled,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
tracker, type, answer_dnssec, client_proto, domain, time,
|
||||
} = row.original;
|
||||
|
||||
const hasTracker = !!tracker;
|
||||
|
||||
const source = tracker && tracker.sourceData && tracker.sourceData.name;
|
||||
|
||||
const lockIconClass = classNames('icons', 'icon--small', 'd-none', 'd-sm-block', 'cursor--pointer', {
|
||||
'icon--active': answer_dnssec,
|
||||
'icon--disabled': !answer_dnssec,
|
||||
'my-3': isDetailed,
|
||||
});
|
||||
|
||||
const privacyIconClass = classNames('icons', 'mx-2', 'icon--small', 'd-none', 'd-sm-block', 'cursor--pointer', {
|
||||
'icon--active': hasTracker,
|
||||
'icon--disabled': !hasTracker,
|
||||
'my-3': isDetailed,
|
||||
});
|
||||
|
||||
const dnssecHint = getHintElement({
|
||||
className: lockIconClass,
|
||||
tooltipClass: 'py-4 px-5 pb-45',
|
||||
dataTip: answer_dnssec,
|
||||
xlinkHref: 'lock',
|
||||
columnClass: 'w-100',
|
||||
content: 'validated_with_dnssec',
|
||||
place: 'bottom',
|
||||
});
|
||||
|
||||
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
||||
const ip = type ? `${t('type_table_header')}: ${type}` : '';
|
||||
|
||||
const requestDetailsObj = {
|
||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||
domain,
|
||||
type_table_header: type,
|
||||
protocol,
|
||||
};
|
||||
|
||||
const knownTrackerDataObj = {
|
||||
name_table_header: tracker && tracker.name,
|
||||
category_label: tracker && tracker.category,
|
||||
source_label: source && <a href={`//${source}`} className="link--green">{source}</a>,
|
||||
};
|
||||
|
||||
const renderGrid = (content, idx) => {
|
||||
const preparedContent = typeof content === 'string' ? t(content) : content;
|
||||
const className = classNames('text-truncate key-colon o-hidden', {
|
||||
'word-break--break-all white-space--normal': preparedContent.length > 100,
|
||||
});
|
||||
return <div key={idx} className={className}>{preparedContent}</div>;
|
||||
};
|
||||
|
||||
const getGrid = (contentObj, title, className) => [
|
||||
<div key={title} className={classNames('pb-2 grid--title', className)}>{t(title)}</div>,
|
||||
<div key={`${title}-1`} className="grid grid--limited">{React.Children.map(Object.entries(contentObj), renderGrid)}</div>,
|
||||
];
|
||||
|
||||
const requestDetails = getGrid(requestDetailsObj, 'request_details');
|
||||
|
||||
const renderContent = hasTracker ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) : requestDetails;
|
||||
|
||||
const trackerHint = getHintElement({
|
||||
className: privacyIconClass,
|
||||
tooltipClass: 'pt-4 pb-5 px-5 mw-75',
|
||||
dataTip: true,
|
||||
xlinkHref: 'privacy',
|
||||
contentItemClass: 'key-colon',
|
||||
renderContent,
|
||||
place: 'bottom',
|
||||
});
|
||||
|
||||
const valueClass = classNames('w-100', {
|
||||
'px-2 d-flex justify-content-center flex-column': isDetailed,
|
||||
});
|
||||
|
||||
const details = [ip, protocol].filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
return (
|
||||
<div className="logs__row o-hidden">
|
||||
{dnssec_enabled && dnssecHint}
|
||||
{trackerHint}
|
||||
<div className={valueClass}>
|
||||
<div className="text-truncate" title={domain}>{domain}</div>
|
||||
{details && isDetailed
|
||||
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={details}>{details}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getDomainCell.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
isDetailed: PropTypes.bool.isRequired,
|
||||
toggleBlocking: PropTypes.func.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
dnssec_enabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default getDomainCell;
|
||||
76
client/src/components/Logs/Cells/getHintElement.js
Normal file
76
client/src/components/Logs/Cells/getHintElement.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import CustomTooltip from '../Tooltip/CustomTooltip';
|
||||
|
||||
const getHintElement = ({
|
||||
className,
|
||||
contentItemClass,
|
||||
columnClass,
|
||||
dataTip,
|
||||
xlinkHref,
|
||||
content,
|
||||
title,
|
||||
place,
|
||||
tooltipClass,
|
||||
trigger,
|
||||
overridePosition,
|
||||
scrollHide,
|
||||
renderContent,
|
||||
}) => {
|
||||
const id = 'id';
|
||||
|
||||
const [isHovered, hover] = useState(false);
|
||||
|
||||
const openTooltip = () => hover(true);
|
||||
const closeTooltip = () => hover(false);
|
||||
|
||||
return <div onMouseEnter={openTooltip}
|
||||
onMouseLeave={closeTooltip}>
|
||||
<div data-tip={dataTip}
|
||||
data-for={dataTip ? id : undefined}
|
||||
data-event={trigger}
|
||||
>
|
||||
{xlinkHref && <svg className={className}>
|
||||
<use xlinkHref={`#${xlinkHref}`} />
|
||||
</svg>}
|
||||
</div>
|
||||
{isHovered && dataTip
|
||||
&& <CustomTooltip
|
||||
className={tooltipClass}
|
||||
id={id}
|
||||
columnClass={columnClass}
|
||||
contentItemClass={contentItemClass}
|
||||
title={title}
|
||||
place={place}
|
||||
content={content}
|
||||
trigger={trigger}
|
||||
overridePosition={overridePosition}
|
||||
scrollHide={scrollHide}
|
||||
renderContent={renderContent}
|
||||
/>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
getHintElement.propTypes = {
|
||||
className: PropTypes.string,
|
||||
contentItemClass: PropTypes.string,
|
||||
columnClass: PropTypes.string,
|
||||
tooltipClass: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
place: PropTypes.string,
|
||||
dataTip: PropTypes.string,
|
||||
xlinkHref: PropTypes.string,
|
||||
overridePosition: PropTypes.func,
|
||||
scrollHide: PropTypes.bool,
|
||||
trigger: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.string),
|
||||
]),
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
]),
|
||||
renderContent: PropTypes.arrayOf(PropTypes.element),
|
||||
};
|
||||
|
||||
export default getHintElement;
|
||||
120
client/src/components/Logs/Cells/getResponseCell.js
Normal file
120
client/src/components/Logs/Cells/getResponseCell.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { formatElapsedMs } from '../../../helpers/helpers';
|
||||
import {
|
||||
CUSTOM_FILTERING_RULES_ID,
|
||||
FILTERED_STATUS,
|
||||
FILTERED_STATUS_TO_META_MAP,
|
||||
} from '../../../helpers/constants';
|
||||
import getHintElement from './getHintElement';
|
||||
|
||||
const getFilterName = (filters, whitelistFilters, filterId, t) => {
|
||||
if (filterId === CUSTOM_FILTERING_RULES_ID) {
|
||||
return t('custom_filter_rules');
|
||||
}
|
||||
|
||||
const filter = filters.find((filter) => filter.id === filterId)
|
||||
|| whitelistFilters.find((filter) => filter.id === filterId);
|
||||
let filterName = '';
|
||||
|
||||
if (filter) {
|
||||
filterName = filter.name;
|
||||
}
|
||||
|
||||
if (!filterName) {
|
||||
filterName = t('unknown_filter', { filterId });
|
||||
}
|
||||
|
||||
return filterName;
|
||||
};
|
||||
|
||||
const getResponseCell = (row, filtering, t, isDetailed) => {
|
||||
const {
|
||||
reason, filterId, rule, status, upstream, elapsedMs, domain, response,
|
||||
} = row.original;
|
||||
|
||||
const { filters, whitelistFilters } = filtering;
|
||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||
|
||||
const statusLabel = t((FILTERED_STATUS_TO_META_MAP[reason]
|
||||
&& FILTERED_STATUS_TO_META_MAP[reason].label) || reason);
|
||||
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
||||
|
||||
const renderResponses = (responseArr) => {
|
||||
if (responseArr.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return <div>{responseArr.map((response) => {
|
||||
const className = classNames('white-space--nowrap', {
|
||||
'white-space--normal': response.length > 100,
|
||||
});
|
||||
|
||||
return <div key={response} className={className}>{`${response}\n`}</div>;
|
||||
})}</div>;
|
||||
};
|
||||
|
||||
const FILTERED_STATUS_TO_FIELDS_MAP = {
|
||||
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
||||
domain,
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
response_table_header: renderResponses(response),
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
||||
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,
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
||||
domain,
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
},
|
||||
};
|
||||
|
||||
const fields = FILTERED_STATUS_TO_FIELDS_MAP[reason]
|
||||
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
|
||||
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
|
||||
|
||||
const detailedInfo = reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE
|
||||
|| reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||
? filter : formattedElapsedMs;
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
{fields && getHintElement({
|
||||
className: classNames('icons mr-4 icon--small cursor--pointer icon--light-gray', { 'my-3': isDetailed }),
|
||||
columnClass: 'grid grid--limited',
|
||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details',
|
||||
contentItemClass: 'text-truncate key-colon o-hidden',
|
||||
dataTip: true,
|
||||
xlinkHref: 'question',
|
||||
title: 'response_details',
|
||||
content: fields,
|
||||
place: 'bottom',
|
||||
})}
|
||||
<div className="text-truncate">
|
||||
<div className="text-truncate" title={statusLabel}>{statusLabel}</div>
|
||||
{isDetailed && <div
|
||||
className="detailed-info d-none d-sm-block pt-1 text-truncate" title={detailedInfo}>{detailedInfo}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default getResponseCell;
|
||||
Reference in New Issue
Block a user