all: sync with master

This commit is contained in:
Ainar Garipov
2024-07-03 15:38:37 +03:00
parent f73717ec08
commit 158d4f0249
352 changed files with 33842 additions and 33276 deletions

View File

@@ -3,36 +3,55 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom';
import propTypes from 'prop-types';
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
import { BLOCK_ACTIONS } from '../../../helpers/constants';
import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
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';
import { RootState } from '../../../initialState';
const ClientCell = ({
client,
client_id,
client_info,
domain,
reason,
}) => {
interface ClientCellProps {
client: string;
client_id?: string;
client_info?: {
name: string;
whois: {
country?: string;
city?: string;
orgname?: string;
};
disallowed: boolean;
disallowed_rule: string;
};
domain: string;
reason: string;
}
const ClientCell = ({ client, client_id, client_info, domain, reason }: ClientCellProps) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const history = useHistory();
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const autoClients = useSelector((state: RootState) => state.dashboard.autoClients, shallowEqual);
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
const allowedClients = useSelector((state: RootState) => state.access.allowed_clients, shallowEqual);
const [isOptionsOpened, setOptionsOpened] = useState(false);
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
const clients = useSelector((state) => state.dashboard.clients);
const autoClient = autoClients.find((autoClient: any) => autoClient.name === client);
const clients = useSelector((state: RootState) => state.dashboard.clients);
const source = autoClient?.source;
const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0;
const clientName = client_info?.name || client_id;
@@ -57,7 +76,7 @@ const ClientCell = ({
const isFiltered = checkFiltered(reason);
const clientIds = clients.map((c) => c.ids).flat();
const clientIds = clients.map((c: any) => c.ids).flat();
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !client_info?.name && !whoisAvailable,
@@ -68,7 +87,7 @@ const ClientCell = ({
'my-3': isDetailed,
});
const renderBlockingButton = (isFiltered, domain) => {
const renderBlockingButton = (isFiltered: any, domain: any) => {
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
const {
@@ -79,7 +98,7 @@ const ClientCell = ({
client,
client_info?.disallowed || false,
client_info?.disallowed_rule || '',
allowedСlients,
allowedClients,
);
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
@@ -108,11 +127,13 @@ const ClientCell = ({
name: blockingClientKey,
onClick: async () => {
if (window.confirm(confirmMessage)) {
await dispatch(toggleClientBlock(
client,
client_info?.disallowed || false,
client_info?.disallowed_rule || '',
));
await dispatch(
toggleClientBlock(
client,
client_info?.disallowed || false,
client_info?.disallowed_rule || '',
),
);
await dispatch(updateLogs());
setOptionsOpened(false);
}
@@ -130,21 +151,19 @@ const ClientCell = ({
});
}
const getOptions = (options) => {
const getOptions = (options: any) => {
if (options.length === 0) {
return null;
}
return (
<>
{options.map(({
name, onClick, disabled, className,
}) => (
{options.map(({ name, onClick, disabled, className }: any) => (
<button
key={name}
className={classNames('button-action--arrow-option px-4 py-1', className)}
onClick={onClick}
disabled={disabled}
>
disabled={disabled}>
{t(name)}
</button>
))}
@@ -160,11 +179,7 @@ const ClientCell = ({
return (
<div className={containerClass}>
<button
type="button"
className="btn btn-icon btn-sm px-0"
onClick={() => setOptionsOpened(true)}
>
<button type="button" className="btn btn-icon btn-sm px-0" onClick={() => setOptionsOpened(true)}>
<svg className="icon24 icon--lightgray button-action__icon">
<use xlinkHref="#bullets" />
</svg>
@@ -188,10 +203,7 @@ const ClientCell = ({
};
return (
<div
className="o-hidden h-100 logs__cell logs__cell--client"
role="gridcell"
>
<div className="o-hidden h-100 logs__cell logs__cell--client" role="gridcell">
<IconTooltip
className={hintClass}
columnClass="grid grid--limited"
@@ -202,6 +214,7 @@ const ClientCell = ({
content={processedData}
placement="bottom"
/>
<div className={nameClass}>
<div data-tip={true} data-for={id}>
{renderFormattedClientCell(client, clientInfo, isDetailed, true)}
@@ -210,8 +223,7 @@ const ClientCell = ({
<Link
className="detailed-info d-none d-sm-block logs__text logs__text--link logs__text--client"
to={`logs?search="${encodeURIComponent(clientName)}"`}
title={clientName}
>
title={clientName}>
{clientName}
</Link>
)}
@@ -221,21 +233,4 @@ const ClientCell = ({
);
};
ClientCell.propTypes = {
client: propTypes.string.isRequired,
client_id: 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,
}),
domain: propTypes.string.isRequired,
reason: propTypes.string.isRequired,
};
export default ClientCell;

View File

@@ -1,29 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import propTypes from 'prop-types';
import { formatDateTime, formatTime } from '../../../helpers/helpers';
import { DEFAULT_SHORT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT } from '../../../helpers/constants';
const DateCell = ({ time }) => {
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
if (!time) {
return '';
}
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
return <div className="logs__cell logs__cell logs__cell--date text-truncate" role="gridcell">
<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>;
};
DateCell.propTypes = {
time: propTypes.string.isRequired,
};
export default DateCell;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { formatDateTime, formatTime } from '../../../helpers/helpers';
import { DEFAULT_SHORT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT } from '../../../helpers/constants';
import { RootState } from '../../../initialState';
interface DateCellProps {
time: string;
}
const DateCell = ({ time }: DateCellProps) => {
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
if (!time) {
return <></>;
}
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
return (
<div className="logs__cell logs__cell logs__cell--date text-truncate" role="gridcell">
<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 DateCell;

View File

@@ -1,16 +1,32 @@
import React from 'react';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import propTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import {
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
LONG_TIME_FORMAT,
SCHEME_TO_PROTOCOL_MAP,
} from '../../../helpers/constants';
import { captitalizeWords, formatDateTime, formatTime } from '../../../helpers/helpers';
import { getSourceData } from '../../../helpers/trackers/trackers';
import IconTooltip from './IconTooltip';
import { RootState } from '../../../initialState';
interface DomainCellProps {
answer_dnssec: boolean;
client_proto: string;
domain: string;
unicodeName?: string;
time: string;
type: string;
tracker?: {
name: string;
category: string;
};
ecs?: string;
}
const DomainCell = ({
answer_dnssec,
@@ -21,10 +37,12 @@ const DomainCell = ({
tracker,
type,
ecs,
}) => {
}: DomainCellProps) => {
const { t } = useTranslation();
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const dnssec_enabled = useSelector((state: RootState) => state.dnsConfig.dnssec_enabled);
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
const hasTracker = !!tracker;
@@ -43,7 +61,15 @@ const DomainCell = ({
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
const ip = type ? `${t('type_table_header')}: ${type}` : '';
let requestDetailsObj = {
let requestDetailsObj: {
time_table_header: string;
date: string;
domain: string;
punycode?: string;
ecs?: string;
type_table_header?: string;
protocol?: string;
} = {
time_table_header: formatTime(time, LONG_TIME_FORMAT),
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
domain,
@@ -76,24 +102,16 @@ const DomainCell = ({
name_table_header: tracker?.name,
category_label: hasTracker && captitalizeWords(tracker.category),
source_label: sourceData && (
<a
href={sourceData.url}
target="_blank"
rel="noopener noreferrer"
className="link--green"
>
<a href={sourceData.url} target="_blank" rel="noopener noreferrer" className="link--green">
{sourceData.name}
</a>
),
};
const renderGrid = (content, idx) => {
const renderGrid = (content: any, idx: any) => {
const preparedContent = typeof content === 'string' ? t(content) : content;
const className = classNames(
'text-truncate o-hidden',
{ 'overflow-break': preparedContent?.length > 100 },
);
const className = classNames('text-truncate o-hidden', { 'overflow-break': preparedContent?.length > 100 });
return (
<div key={idx} className={className}>
@@ -102,10 +120,11 @@ const DomainCell = ({
);
};
const getGrid = (contentObj, title, className) => [
const getGrid = (contentObj: any, title: string, className?: string) => [
<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>,
@@ -113,7 +132,9 @@ const DomainCell = ({
const requestDetails = getGrid(requestDetailsObj, 'request_details');
const renderContent = hasTracker ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) : requestDetails;
const renderContent = hasTracker
? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4'))
: requestDetails;
const valueClass = classNames('w-100 text-truncate', {
'px-2 d-flex justify-content-center flex-column': isDetailed,
@@ -122,10 +143,7 @@ const DomainCell = ({
const details = [ip, protocol].filter(Boolean).join(', ');
return (
<div
className="d-flex o-hidden logs__cell logs__cell logs__cell--domain"
role="gridcell"
>
<div className="d-flex o-hidden logs__cell logs__cell logs__cell--domain" role="gridcell">
{dnssec_enabled && (
<IconTooltip
className={lockIconClass}
@@ -137,14 +155,16 @@ const DomainCell = ({
placement="bottom"
/>
)}
<IconTooltip
className={privacyIconClass}
tooltipClass="pt-4 pb-5 px-5 mw-75"
xlinkHref="privacy"
contentItemClass="key-colon"
renderContent={renderContent}
place="bottom"
placement="bottom"
/>
<div className={valueClass}>
{unicodeName ? (
<div className="text-truncate overflow-break-mobile" title={unicodeName}>
@@ -156,10 +176,7 @@ const DomainCell = ({
</div>
)}
{details && isDetailed && (
<div
className="detailed-info d-none d-sm-block text-truncate"
title={details}
>
<div className="detailed-info d-none d-sm-block text-truncate" title={details}>
{details}
</div>
)}
@@ -168,15 +185,4 @@ const DomainCell = ({
);
};
DomainCell.propTypes = {
answer_dnssec: propTypes.bool.isRequired,
client_proto: propTypes.string.isRequired,
domain: propTypes.string.isRequired,
unicodeName: propTypes.string,
time: propTypes.string.isRequired,
type: propTypes.string.isRequired,
tracker: propTypes.object,
ecs: propTypes.string,
};
export default DomainCell;

View File

@@ -1,54 +0,0 @@
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames';
import React from 'react';
import { toggleDetailedLogs } from '../../../actions/queryLogs';
import HeaderCell from './HeaderCell';
const Header = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const disableDetailedMode = () => dispatch(toggleDetailedLogs(false));
const enableDetailedMode = () => dispatch(toggleDetailedLogs(true));
const HEADERS = [
{
className: 'logs__cell--date',
content: 'time_table_header',
},
{
className: 'logs__cell--domain',
content: 'request_table_header',
},
{
className: 'logs__cell--response',
content: 'response_table_header',
},
{
className: 'logs__cell--client',
content: <>
{t('client_table_header')}
{<span>
<svg className={classNames('icons icon--24 icon--green cursor--pointer mr-2', { 'icon--selected': !isDetailed })}
onClick={disableDetailedMode}
>
<title>{t('compact')}</title>
<use xlinkHref='#list' /></svg>
<svg className={classNames('icons icon--24 icon--green cursor--pointer', { 'icon--selected': isDetailed })}
onClick={enableDetailedMode}
>
<title>{t('default')}</title>
<use xlinkHref='#detailed_list' />
</svg>
</span>}
</>,
},
];
return <div className="logs__cell--header__container px-5" role="row">
{HEADERS.map(HeaderCell)}
</div>;
};
export default Header;

View File

@@ -0,0 +1,73 @@
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames';
import React from 'react';
import { toggleDetailedLogs } from '../../../actions/queryLogs';
import HeaderCell from './HeaderCell';
import { RootState } from '../../../initialState';
const Header = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
const disableDetailedMode = () => dispatch(toggleDetailedLogs(false));
const enableDetailedMode = () => dispatch(toggleDetailedLogs(true));
const HEADERS = [
{
className: 'logs__cell--date',
content: 'time_table_header',
},
{
className: 'logs__cell--domain',
content: 'request_table_header',
},
{
className: 'logs__cell--response',
content: 'response_table_header',
},
{
className: 'logs__cell--client',
content: (
<>
{t('client_table_header')}
{
<span>
<svg
className={classNames('icons icon--24 icon--green cursor--pointer mr-2', {
'icon--selected': !isDetailed,
})}
onClick={disableDetailedMode}>
<title>{t('compact')}</title>
<use xlinkHref="#list" />
</svg>
<svg
className={classNames('icons icon--24 icon--green cursor--pointer', {
'icon--selected': isDetailed,
})}
onClick={enableDetailedMode}>
<title>{t('default')}</title>
<use xlinkHref="#detailed_list" />
</svg>
</span>
}
</>
),
},
];
return (
<div className="logs__cell--header__container px-5" role="row">
{HEADERS.map(HeaderCell)}
</div>
);
};
export default Header;

View File

@@ -1,22 +0,0 @@
import classNames from 'classnames';
import React from 'react';
import propTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
const HeaderCell = ({ content, className }, idx) => {
const { t } = useTranslation();
return <div
key={idx}
className={classNames('logs__cell--header__item logs__cell logs__text--bold', className)}
role="columnheader"
>
{typeof content === 'string' ? t(content) : content}
</div>;
};
HeaderCell.propTypes = {
content: propTypes.oneOfType([propTypes.string, propTypes.element]).isRequired,
className: propTypes.string,
};
export default HeaderCell;

View File

@@ -0,0 +1,23 @@
import classNames from 'classnames';
import React from 'react';
import { useTranslation } from 'react-i18next';
interface HeaderCellProps {
content: string | React.ReactElement;
className?: string;
}
const HeaderCell = ({ content, className }: HeaderCellProps, idx: any) => {
const { t } = useTranslation();
return (
<div
key={idx}
className={classNames('logs__cell--header__item logs__cell logs__text--bold', className)}
role="columnheader">
{typeof content === 'string' ? t(content) : content}
</div>
);
};
export default HeaderCell;

View File

@@ -91,18 +91,24 @@
margin: -0.5rem 0 0;
}
.grid > .key__time_table_header, .grid > .key__data, .grid > .key__encryption_status, .grid > .key__elapsed {
.grid > .key__time_table_header,
.grid > .key__data,
.grid > .key__encryption_status,
.grid > .key__elapsed {
grid-column: 1 / span 1;
}
.grid > .value__time_table_header, .grid > .value__data, .grid > .value__encryption_status, .grid > .value__elapsed {
.grid > .value__time_table_header,
.grid > .value__data,
.grid > .value__encryption_status,
.grid > .value__elapsed {
grid-column: 2 / span 1;
margin: 0 !important;
}
}
.grid .key-colon:nth-child(odd)::after {
content: ":";
content: ':';
}
.grid__one-row {
@@ -127,7 +133,7 @@
}
.title--border:before {
content: "";
content: '';
position: absolute;
left: 0;
border-top: 0.5px solid var(--gray-d8) !important;

View File

@@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import classNames from 'classnames';
import { processContent } from '../../../helpers/helpers';
import Tooltip from '../../ui/Tooltip';
import 'react-popper-tooltip/dist/styles.css';
import './IconTooltip.css';
import { SHOW_TOOLTIP_DELAY } from '../../../helpers/constants';
const IconTooltip = ({
className,
contentItemClass,
columnClass,
triggerClass,
canShowTooltip = true,
xlinkHref,
title,
placement,
tooltipClass,
content,
trigger,
onVisibilityChange,
defaultTooltipShown,
delayHide,
renderContent = content ? React.Children.map(
processContent(content),
(item, idx) => <div key={idx} className={contentItemClass}>
<Trans>{item || '—'}</Trans>
</div>,
) : null,
}) => {
const tooltipContent = <>
{title
&& <div className="pb-4 h-25 grid-content font-weight-bold"><Trans>{title}</Trans></div>}
<div className={classNames(columnClass)}>{renderContent}</div>
</>;
const tooltipClassName = classNames('tooltip-custom__container', tooltipClass, { 'd-none': !canShowTooltip });
return <Tooltip
className={tooltipClassName}
content={tooltipContent}
placement={placement}
triggerClass={triggerClass}
trigger={trigger}
onVisibilityChange={onVisibilityChange}
delayShow={trigger === 'click' ? 0 : SHOW_TOOLTIP_DELAY}
delayHide={delayHide}
defaultTooltipShown={defaultTooltipShown}
>
{xlinkHref && <svg className={className}>
<use xlinkHref={`#${xlinkHref}`} />
</svg>}
</Tooltip>;
};
IconTooltip.propTypes = {
className: PropTypes.string,
trigger: PropTypes.string,
triggerClass: PropTypes.string,
contentItemClass: PropTypes.string,
columnClass: PropTypes.string,
tooltipClass: PropTypes.string,
title: PropTypes.string,
placement: PropTypes.string,
canShowTooltip: PropTypes.bool,
xlinkHref: PropTypes.string,
content: PropTypes.node,
renderContent: PropTypes.arrayOf(PropTypes.element),
onVisibilityChange: PropTypes.func,
defaultTooltipShown: PropTypes.bool,
delayHide: PropTypes.number,
};
export default IconTooltip;

View File

@@ -0,0 +1,94 @@
import React from 'react';
import { Trans } from 'react-i18next';
import classNames from 'classnames';
import PopperJS from 'popper.js';
import { TriggerTypes } from 'react-popper-tooltip';
import { processContent } from '../../../helpers/helpers';
import Tooltip from '../../ui/Tooltip';
import 'react-popper-tooltip/dist/styles.css';
import './IconTooltip.css';
import { SHOW_TOOLTIP_DELAY } from '../../../helpers/constants';
interface IconTooltipProps {
className?: string;
trigger?: TriggerTypes;
triggerClass?: string;
contentItemClass?: string;
columnClass?: string;
tooltipClass?: string;
title?: string;
placement?: PopperJS.Placement;
canShowTooltip?: boolean;
xlinkHref?: string;
content?: React.ReactNode;
renderContent?: React.ReactElement[];
onVisibilityChange?: (...args: unknown[]) => unknown;
defaultTooltipShown?: boolean;
delayHide?: number;
}
const IconTooltip = ({
className,
contentItemClass,
columnClass,
triggerClass,
canShowTooltip = true,
xlinkHref,
title,
placement,
tooltipClass,
content,
trigger,
onVisibilityChange,
defaultTooltipShown,
delayHide,
renderContent = content
? React.Children.map(
processContent(content),
(item, idx) => (
<div key={idx} className={contentItemClass}>
<Trans>{item || '—'}</Trans>
</div>
),
)
: null,
}: IconTooltipProps) => {
const tooltipContent = (
<>
{title && (
<div className="pb-4 h-25 grid-content font-weight-bold">
<Trans>{title}</Trans>
</div>
)}
<div className={classNames(columnClass)}>{renderContent}</div>
</>
);
const tooltipClassName = classNames('tooltip-custom__container', tooltipClass, { 'd-none': !canShowTooltip });
return (
<Tooltip
className={tooltipClassName}
content={tooltipContent}
placement={placement}
triggerClass={triggerClass}
trigger={trigger}
onVisibilityChange={onVisibilityChange}
delayShow={trigger === 'click' ? 0 : SHOW_TOOLTIP_DELAY}
delayHide={delayHide}
defaultTooltipShown={defaultTooltipShown}>
{xlinkHref && (
<svg className={className}>
<use xlinkHref={`#${xlinkHref}`} />
</svg>
)}
</Tooltip>
);
};
export default IconTooltip;

View File

@@ -1,141 +0,0 @@
import { useTranslation } from 'react-i18next';
import { shallowEqual, useSelector } from 'react-redux';
import classNames from 'classnames';
import React from 'react';
import propTypes from 'prop-types';
import {
getRulesToFilterList,
formatElapsedMs,
getFilterNames,
getServiceName,
} from '../../../helpers/helpers';
import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants';
import IconTooltip from './IconTooltip';
const ResponseCell = ({
elapsedMs,
originalResponse,
reason,
response,
status,
upstream,
rules,
service_name,
cached,
}) => {
const { t } = useTranslation();
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const services = useSelector((store) => store?.services);
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
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 renderResponses = (responseArr) => {
if (!responseArr || responseArr.length === 0) {
return '';
}
return <div>{responseArr.map((response) => {
const className = classNames('white-space--nowrap', {
'overflow-break': response.length > 100,
});
return <div key={response} className={className}>{`${response}\n`}</div>;
})}</div>;
};
const COMMON_CONTENT = {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
...(cached
&& {
served_from_cache_label: (
<svg className="icons icon--20 icon--green mb-1">
<use xlinkHref="#check" />
</svg>
),
}
),
elapsed: formattedElapsedMs,
response_code: status,
...(service_name && services.allServices
&& { service_name: getServiceName(services.allServices, service_name) }
),
...(rules.length > 0
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
),
response_table_header: renderResponses(response),
original_response: renderResponses(originalResponse),
};
const content = rules.length > 0
? Object.entries(COMMON_CONTENT)
: Object.entries({
...COMMON_CONTENT,
filter: '',
});
const getDetailedInfo = (reason) => {
switch (reason) {
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
if (!service_name || !services.allServices) {
return formattedElapsedMs;
}
return getServiceName(services.allServices, service_name);
case FILTERED_STATUS.FILTERED_BLACK_LIST:
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
return getFilterNames(rules, filters, whitelistFilters).join(', ');
default:
return formattedElapsedMs;
}
};
const detailedInfo = getDetailedInfo(reason);
return (
<div className="logs__cell logs__cell--response" role="gridcell">
<IconTooltip
className={classNames('icons mr-4 icon--24 icon--lightgray logs__question', { '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'
xlinkHref='question'
title='response_details'
content={content}
placement='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>
);
};
ResponseCell.propTypes = {
elapsedMs: propTypes.string.isRequired,
originalResponse: propTypes.array.isRequired,
reason: propTypes.string.isRequired,
response: propTypes.array.isRequired,
status: propTypes.string.isRequired,
upstream: propTypes.string.isRequired,
cached: propTypes.bool.isRequired,
rules: propTypes.arrayOf(propTypes.shape({
text: propTypes.string.isRequired,
filter_list_id: propTypes.number.isRequired,
})),
service_name: propTypes.string,
};
export default ResponseCell;

View File

@@ -0,0 +1,150 @@
import { useTranslation } from 'react-i18next';
import { shallowEqual, useSelector } from 'react-redux';
import classNames from 'classnames';
import React from 'react';
import { getRulesToFilterList, formatElapsedMs, getFilterNames, getServiceName } from '../../../helpers/helpers';
import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants';
import IconTooltip from './IconTooltip';
import { RootState } from '../../../initialState';
interface ResponseCellProps {
elapsedMs: string;
originalResponse?: unknown[];
reason: string;
response: unknown[];
status: string;
upstream: string;
cached: boolean;
rules?: {
text: string;
filter_list_id: number;
}[];
service_name?: string;
}
const ResponseCell = ({
elapsedMs,
originalResponse,
reason,
response,
status,
upstream,
rules,
service_name,
cached,
}: ResponseCellProps) => {
const { t } = useTranslation();
const filters = useSelector((state: RootState) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state: RootState) => state.filtering.whitelistFilters, shallowEqual);
const isDetailed = useSelector((state: RootState) => state.queryLogs.isDetailed);
const services = useSelector((store: RootState) => store?.services);
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
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 renderResponses = (responseArr: any) => {
if (!responseArr || responseArr.length === 0) {
return '';
}
return (
<div>
{responseArr.map((response: any) => {
const className = classNames('white-space--nowrap', {
'overflow-break': response.length > 100,
});
return <div key={response} className={className}>{`${response}\n`}</div>;
})}
</div>
);
};
const COMMON_CONTENT = {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
...(cached && {
served_from_cache_label: (
<svg className="icons icon--20 icon--green mb-1">
<use xlinkHref="#check" />
</svg>
),
}),
elapsed: formattedElapsedMs,
response_code: status,
...(service_name &&
services.allServices && { service_name: getServiceName(services.allServices, service_name) }),
...(rules.length > 0 && { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }),
response_table_header: renderResponses(response),
original_response: renderResponses(originalResponse),
};
const content =
rules.length > 0
? Object.entries(COMMON_CONTENT)
: Object.entries({
...COMMON_CONTENT,
filter: '',
});
const getDetailedInfo = (reason: any) => {
switch (reason) {
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
if (!service_name || !services.allServices) {
return formattedElapsedMs;
}
return getServiceName(services.allServices, service_name);
case FILTERED_STATUS.FILTERED_BLACK_LIST:
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
return getFilterNames(rules, filters, whitelistFilters).join(', ');
default:
return formattedElapsedMs;
}
};
const detailedInfo = getDetailedInfo(reason);
return (
<div className="logs__cell logs__cell--response" role="gridcell">
<IconTooltip
className={classNames('icons mr-4 icon--24 icon--lightgray logs__question', { '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"
xlinkHref="question"
title="response_details"
content={content}
placement="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 ResponseCell;

View File

@@ -2,20 +2,20 @@ import i18next from 'i18next';
export const BUTTON_PREFIX = 'btn_';
export const getBlockClientInfo = (ip, disallowed, disallowed_rule, allowedСlients) => {
export const getBlockClientInfo = (ip: any, disallowed: any, disallowed_rule: any, allowedClients: any) => {
let confirmMessage;
if (disallowed) {
confirmMessage = i18next.t('client_confirm_unblock', { ip: disallowed_rule || ip });
} else {
confirmMessage = `${i18next.t('adg_will_drop_dns_queries')} ${i18next.t('client_confirm_block', { ip })}`;
if (allowedСlients.length > 0) {
if (allowedClients.length > 0) {
confirmMessage = confirmMessage.concat(`\n\n${i18next.t('filter_allowlist', { disallowed_rule })}`);
}
}
const buttonKey = i18next.t(disallowed ? 'allow_this_client' : 'disallow_this_client');
const lastRuleInAllowlist = !disallowed && allowedСlients === disallowed_rule;
const lastRuleInAllowlist = !disallowed && allowedClients === disallowed_rule;
return {
confirmMessage,

View File

@@ -1,285 +0,0 @@
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 services = useSelector((store) => store?.services);
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 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 = (
<>
<div className="title--border" />
<button
type="button"
className={
classNames(
'button-action--arrow-option mb-1',
{ 'bg--danger': !isBlocked },
{ 'bg--green': isFiltered },
)}
onClick={onToggleBlock}
>
{t(buttonType)}
</button>
</>
);
const blockForClientButton = <button
className='text-center font-weight-bold py-1 button-action--arrow-option'
onClick={onBlockingForClientClick}>
{t(blockingForClientKey)}
</button>;
const blockClientButton = <button
className='text-center font-weight-bold py-1 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 && services.allServices
&& { service_name: getServiceName(services.allServices, 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: upstream,
...(cached
&& {
served_from_cache_label: (
<svg className="icons icon--20 icon--green">
<use xlinkHref="#check" />
</svg>
),
}
),
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;

View File

@@ -0,0 +1,306 @@
import React, { Dispatch, memo, SetStateAction } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
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';
import { RootState } from '../../../initialState';
interface RowProps {
style?: object;
rowProps: {
reason: string;
answer_dnssec: boolean;
client: string;
domain: string;
elapsedMs: string;
response: unknown[];
time: string;
tracker?: {
name: string;
category: string;
};
upstream: string;
cached: boolean;
type: string;
client_proto: string;
client_id?: string;
ecs?: string;
client_info?: {
name: string;
whois: {
country?: string;
city?: string;
orgname?: string;
};
disallowed: boolean;
disallowed_rule: string;
};
rules?: {
text: string;
filter_list_id: number;
}[];
originalResponse?: unknown[];
status: string;
service_name?: string;
};
isSmallScreen: boolean;
setDetailedDataCurrent: Dispatch<SetStateAction<any>>;
setButtonType: (...args: unknown[]) => unknown;
setModalOpened: (...args: unknown[]) => unknown;
}
const Row = memo(
({
style,
rowProps,
rowProps: { reason },
isSmallScreen,
setDetailedDataCurrent,
setButtonType,
setModalOpened,
}: RowProps) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const dnssec_enabled = useSelector((state: RootState) => state.dnsConfig.dnssec_enabled);
const filters = useSelector((state: RootState) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state: RootState) => state.filtering.whitelistFilters, shallowEqual);
const autoClients = useSelector((state: RootState) => state.dashboard.autoClients, shallowEqual);
const processingSet = useSelector((state: RootState) => state.access.processingSet);
const allowedClients = useSelector((state: RootState) => state.access.allowed_clients, shallowEqual);
const services = useSelector((state: RootState) => state?.services);
const clients = useSelector((state: RootState) => 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: any) => 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 || '',
allowedClients,
);
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
const clientNameBlockingFor = getBlockingClientName(clients, client);
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 = (
<>
<div className="title--border" />
<button
type="button"
className={classNames(
'button-action--arrow-option mb-1',
{ 'bg--danger': !isBlocked },
{ 'bg--green': isFiltered },
)}
onClick={onToggleBlock}>
{t(buttonType)}
</button>
</>
);
const blockForClientButton = (
<button
className="text-center font-weight-bold py-1 button-action--arrow-option"
onClick={onBlockingForClientClick}>
{t(blockingForClientKey)}
</button>
);
const blockClientButton = (
<button
className="text-center font-weight-bold py-1 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 &&
services.allServices && { service_name: getServiceName(services.allServices, 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: upstream,
...(cached && {
served_from_cache_label: (
<svg className="icons icon--20 icon--green">
<use xlinkHref="#check" />
</svg>
),
}),
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: RootState) => 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';
export default Row;