import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation, Trans } from 'react-i18next';
import ReactTable from 'react-table';
import classNames from 'classnames';
import endsWith from 'lodash/endsWith';
import escapeRegExp from 'lodash/escapeRegExp';
import {
BLOCK_ACTIONS,
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
LONG_TIME_FORMAT,
FILTERED_STATUS_TO_META_MAP,
TABLE_DEFAULT_PAGE_SIZE,
SCHEME_TO_PROTOCOL_MAP,
CUSTOM_FILTERING_RULES_ID, FILTERED_STATUS,
} from '../../helpers/constants';
import getDateCell from './Cells/getDateCell';
import getDomainCell from './Cells/getDomainCell';
import getClientCell from './Cells/getClientCell';
import getResponseCell from './Cells/getResponseCell';
import {
captitalizeWords,
checkFiltered,
formatDateTime,
formatElapsedMs,
formatTime,
processContent,
} from '../../helpers/helpers';
import Loading from '../ui/Loading';
import { getSourceData } from '../../helpers/trackers/trackers';
const Table = (props) => {
const {
setDetailedDataCurrent,
setButtonType,
setModalOpened,
isSmallScreen,
setIsLoading,
filtering,
isDetailed,
toggleDetailedLogs,
setLogsPage,
setLogsPagination,
processingGetLogs,
logs,
pages,
page,
isLoading,
} = props;
const { t } = useTranslation();
const toggleBlocking = (type, domain) => {
const {
setRules, getFilteringStatus, addSuccessToast,
} = props;
const { userRules } = filtering;
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
const baseRule = `||${domain}^$important`;
const baseUnblocking = `@@${baseRule}`;
const blockingRule = type === BLOCK_ACTIONS.BLOCK ? baseUnblocking : baseRule;
const unblockingRule = type === BLOCK_ACTIONS.BLOCK ? baseRule : baseUnblocking;
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
const matchPreparedBlockingRule = userRules.match(preparedBlockingRule);
const matchPreparedUnblockingRule = userRules.match(preparedUnblockingRule);
if (matchPreparedBlockingRule) {
setRules(userRules.replace(`${blockingRule}`, ''));
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
} else if (!matchPreparedUnblockingRule) {
setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
} else if (matchPreparedUnblockingRule) {
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
return;
} else if (!matchPreparedBlockingRule) {
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
return;
}
getFilteringStatus();
};
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 columns = [
{
Header: t('time_table_header'),
accessor: 'time',
Cell: (row) => getDateCell(row, isDetailed),
minWidth: 70,
maxHeight: 60,
headerClassName: 'logs__text',
},
{
Header: t('request_table_header'),
accessor: 'domain',
Cell: (row) => {
const {
isDetailed,
autoClients,
dnssec_enabled,
} = props;
return getDomainCell({
row,
t,
isDetailed,
toggleBlocking,
autoClients,
dnssec_enabled,
});
},
minWidth: 180,
maxHeight: 60,
headerClassName: 'logs__text',
},
{
Header: t('response_table_header'),
accessor: 'response',
Cell: (row) => getResponseCell(
row,
filtering,
t,
isDetailed,
getFilterName,
),
minWidth: 150,
maxHeight: 60,
headerClassName: 'logs__text',
},
{
Header: function Header() {
return
{t('client_table_header')}
{
}
;
},
accessor: 'client',
Cell: (row) => {
const {
isDetailed,
autoClients,
filtering: { processingRules },
} = props;
return getClientCell({
row,
t,
isDetailed,
toggleBlocking,
autoClients,
processingRules,
});
},
minWidth: 123,
maxHeight: 60,
headerClassName: 'logs__text',
className: 'pb-0',
},
];
const changePage = async (page) => {
setIsLoading(true);
const { oldest, getLogs, pages } = props;
const isLastPage = pages && (page + 1 === pages);
await Promise.all([
setLogsPage(page),
setLogsPagination({
page,
pageSize: TABLE_DEFAULT_PAGE_SIZE,
}),
].concat(isLastPage ? getLogs(oldest, page) : []));
setIsLoading(false);
};
const tableClass = classNames('logs__table', {
'logs__table--detailed': isDetailed,
});
return (
{t('loading_table_status')}
>
}
getLoadingProps={() => ({ className: 'loading__container' })}
rowsText={t('rows_table_footer_text')}
noDataText={!processingGetLogs
&& }
pageText=''
ofText=''
showPagination={logs.length > 0}
getPaginationProps={() => ({ className: 'custom-pagination custom-pagination--padding' })}
getTbodyProps={() => ({ className: 'd-block' })}
previousText={
}
nextText={
}
renderTotalPagesCount={() => false}
getTrGroupProps={(_state, rowInfo) => {
if (!rowInfo) {
return {};
}
const { reason } = rowInfo.original;
const colorClass = FILTERED_STATUS_TO_META_MAP[reason] ? FILTERED_STATUS_TO_META_MAP[reason].color : 'white';
return { className: colorClass };
}}
getTrProps={(state, rowInfo) => ({
className: isDetailed ? 'row--detailed' : '',
onClick: () => {
if (isSmallScreen) {
const { dnssec_enabled, autoClients } = props;
const {
answer_dnssec,
client,
domain,
elapsedMs,
info,
reason,
response,
time,
tracker,
upstream,
type,
client_proto,
filterId,
rule,
originalResponse,
status,
} = rowInfo.original;
const hasTracker = !!tracker;
const autoClient = autoClients
.find((autoClient) => autoClient.name === client);
const { whois_info } = info;
const country = whois_info?.country;
const city = whois_info?.city;
const network = whois_info?.orgname;
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 = () => {
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 { 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: isBlocked
? {requestStatus}
: requestStatus,
domain,
type_table_header: type,
protocol,
known_tracker: hasTracker && 'title',
table_name: tracker?.name,
category_label: hasTracker && captitalizeWords(tracker.category),
tracker_source: hasTracker && sourceData
&& {sourceData.name}
,
response_details: 'title',
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter: rule ? filter : null,
rule_label: rule,
response_table_header: response?.join('\n'),
response_code: status,
client_details: 'title',
ip_address: client,
name: info?.name,
country,
city,
network,
source_label: source,
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
original_response: originalResponse?.join('\n'),
[buttonType]: {t(buttonType)}
,
};
setDetailedDataCurrent(processContent(detailedData));
setButtonType(buttonType);
setModalOpened(true);
}
},
})}
/>
);
};
Table.propTypes = {
logs: PropTypes.array.isRequired,
pages: PropTypes.number.isRequired,
page: PropTypes.number.isRequired,
autoClients: PropTypes.array.isRequired,
defaultPageSize: PropTypes.number,
oldest: PropTypes.string.isRequired,
filtering: PropTypes.object.isRequired,
processingGetLogs: PropTypes.bool.isRequired,
processingGetConfig: PropTypes.bool.isRequired,
isDetailed: PropTypes.bool.isRequired,
setLogsPage: PropTypes.func.isRequired,
setLogsPagination: PropTypes.func.isRequired,
getLogs: PropTypes.func.isRequired,
toggleDetailedLogs: PropTypes.func.isRequired,
setRules: PropTypes.func.isRequired,
addSuccessToast: PropTypes.func.isRequired,
getFilteringStatus: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
setIsLoading: PropTypes.func.isRequired,
dnssec_enabled: PropTypes.bool.isRequired,
setDetailedDataCurrent: PropTypes.func.isRequired,
setButtonType: PropTypes.func.isRequired,
setModalOpened: PropTypes.func.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
};
export default Table;