diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index b6401284..03a7741c 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -176,6 +176,8 @@ "rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules", "query_log_disabled_toast": "Query log disabled", "query_log_enabled_toast": "Query log enabled", + "query_log_response_status": "Status: {{value}}", + "query_log_filtered": "Filtered by {{filter}}", "source_label": "Source", "found_in_known_domain_db": "Found in the known domains database.", "category_label": "Category", diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index cd105626..313879b6 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -9,13 +9,12 @@ justify-content: center; } -.logs__row--overflow { - overflow: hidden; +.logs__row--column { + align-items: flex-start; + flex-direction: column; } -.logs__row--column { - flex-direction: column; - align-items: flex-start; +.logs__row--overflow { overflow: hidden; } @@ -103,3 +102,20 @@ border-color: #1991eb; box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25); } + +.logs__text-wrap { + display: flex; + align-items: center; + max-width: 100%; +} + +.logs__list-wrap { + display: flex; + max-width: 100%; +} + +.logs__list-item { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js index 93a1e420..5d5471e9 100644 --- a/client/src/components/Logs/index.js +++ b/client/src/components/Logs/index.js @@ -8,7 +8,7 @@ import { Trans, withNamespaces } from 'react-i18next'; import { HashLink as Link } from 'react-router-hash-link'; import { formatTime, getClientName } from '../../helpers/helpers'; -import { SERVICES } from '../../helpers/constants'; +import { SERVICES, FILTERED_STATUS } from '../../helpers/constants'; import { getTrackerData } from '../../helpers/trackers/trackers'; import PageTitle from '../ui/PageTitle'; import Card from '../ui/Card'; @@ -18,6 +18,11 @@ import Popover from '../ui/Popover'; import './Logs.css'; const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt'; +const FILTERED_REASON = 'Filtered'; +const RESPONSE_FILTER = { + ALL: 'all', + FILTERED: 'filtered', +}; class Logs extends Component { componentDidMount() { @@ -38,11 +43,29 @@ class Logs extends Component { if (this.props.dashboard.queryLogEnabled) { this.props.getLogs(); } - } + }; renderTooltip = (isFiltered, rule, filter, service) => isFiltered && ; + renderResponseList = (response, status) => { + if (response.length > 0) { + const listItems = response.map((response, index) => ( +
  • + {response} +
  • + )); + + return ; + } + + return ( +
    + query_log_response_status +
    + ); + }; + toggleBlocking = (type, domain) => { const { userRules } = this.props.filtering; const { t } = this.props; @@ -63,7 +86,7 @@ class Logs extends Component { } this.props.getFilteringStatus(); - } + }; renderBlockingButton(isFiltered, domain) { const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger'; @@ -84,228 +107,244 @@ class Logs extends Component { ); } + checkFiltered = reason => reason.indexOf(FILTERED_REASON) === 0; + + checkRewrite = reason => reason === FILTERED_STATUS.REWRITE; + + checkWhiteList = reason => reason === FILTERED_STATUS.NOT_FILTERED_WHITE_LIST; + + getTimeCell = ({ value }) => ( +
    + + {formatTime(value)} + +
    + ); + + getDomainCell = (row) => { + const response = row.value; + const trackerData = getTrackerData(response); + + return ( +
    +
    {response}
    + {trackerData && } +
    + ); + }; + + getResponseCell = ({ value: responses, original }) => { + const { + reason, filterId, rule, status, + } = original; + const { t, filtering } = this.props; + const { filters } = filtering; + + const isFiltered = this.checkFiltered(reason); + const filterKey = reason.replace(FILTERED_REASON, ''); + const parsedFilteredReason = t('query_log_filtered', { filter: filterKey }); + const isRewrite = this.checkRewrite(reason); + const isWhiteList = this.checkWhiteList(reason); + const isBlockedService = reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE; + const currentService = SERVICES.find(service => service.id === original.serviceName); + const serviceName = currentService && currentService.name; + let filterName = ''; + + if (filterId === 0) { + filterName = t('custom_filter_rules'); + } else { + const filterItem = Object.keys(filters).filter(key => filters[key].id === filterId)[0]; + + if ( + typeof filterItem !== 'undefined' && + typeof filters[filterItem] !== 'undefined' + ) { + filterName = filters[filterItem].name; + } + + if (!filterName) { + filterName = t('unknown_filter', { filterId }); + } + } + + return ( +
    +
    + {(isFiltered || isBlockedService) && ( + + {parsedFilteredReason} + + )} + {isBlockedService + ? this.renderTooltip(isFiltered, '', '', serviceName) + : this.renderTooltip(isFiltered, rule, filterName)} + {isRewrite && ( + + rewrite_applied + + )} +
    +
    + {this.renderResponseList(responses, status)} + {isWhiteList && this.renderTooltip(isWhiteList, rule, filterName)} +
    +
    + ); + }; + + getClientCell = ({ original, value }) => { + const { dashboard } = this.props; + const { reason, domain } = original; + const isFiltered = this.checkFiltered(reason); + const isRewrite = this.checkRewrite(reason); + const clientName = + getClientName(dashboard.clients, value) || getClientName(dashboard.autoClients, value); + let client = value; + + if (clientName) { + client = ( + + {clientName} ({value}) + + ); + } + + return ( + +
    {client}
    + {isRewrite ? ( +
    + + configure + +
    + ) : ( + this.renderBlockingButton(isFiltered, domain) + )} +
    + ); + }; + renderLogs(logs) { - const { t, dashboard } = this.props; - const columns = [{ - Header: t('time_table_header'), - accessor: 'time', - maxWidth: 110, - filterable: false, - Cell: ({ value }) => (
    {formatTime(value)}
    ), - }, { - Header: t('domain_name_table_header'), - accessor: 'domain', - Cell: (row) => { - const response = row.value; - const trackerData = getTrackerData(response); - - return ( -
    -
    - {response} -
    - {trackerData && } -
    - ); + const { t } = this.props; + const columns = [ + { + Header: t('time_table_header'), + accessor: 'time', + maxWidth: 90, + filterable: false, + Cell: this.getTimeCell, }, - }, { - Header: t('type_table_header'), - accessor: 'type', - maxWidth: 60, - }, { - Header: t('response_table_header'), - accessor: 'response', - Cell: (row) => { - const responses = row.value; - const { reason } = row.original; - const isFiltered = row ? reason.indexOf('Filtered') === 0 : false; - const parsedFilteredReason = reason.replace('Filtered', 'Filtered by '); - const rule = row && row.original && row.original.rule; - const { filterId } = row.original; - const { filters } = this.props.filtering; - const isRewrite = reason && reason === 'Rewrite'; - let filterName = ''; - - if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') { - if (filterId === 0) { - filterName = t('custom_filter_rules'); - } else { - const filterItem = Object.keys(filters) - .filter(key => filters[key].id === filterId); - - if (typeof filterItem !== 'undefined' && typeof filters[filterItem] !== 'undefined') { - filterName = filters[filterItem].name; - } - - if (!filterName) { - filterName = t('unknown_filter', { filterId }); - } + { + Header: t('domain_name_table_header'), + accessor: 'domain', + minWidth: 180, + Cell: this.getDomainCell, + }, + { + Header: t('type_table_header'), + accessor: 'type', + maxWidth: 60, + }, + { + Header: t('response_table_header'), + accessor: 'response', + minWidth: 250, + Cell: this.getResponseCell, + filterMethod: (filter, row) => { + if (filter.value === RESPONSE_FILTER.FILTERED) { + // eslint-disable-next-line no-underscore-dangle + const { reason } = row._original; + return ( + this.checkFiltered(reason) || + this.checkWhiteList(reason) + ); } - } - - if (reason === 'FilteredBlockedService') { - const getService = SERVICES - .find(service => service.id === row.original.serviceName); - const serviceName = getService && getService.name; - - return ( -
    - - {parsedFilteredReason} - - {this.renderTooltip(isFiltered, '', '', serviceName)} -
    - ); - } - - if (isFiltered) { - return ( -
    - - {parsedFilteredReason} - - {this.renderTooltip(isFiltered, rule, filterName)} -
    - ); - } - - if (responses.length > 0) { - const liNodes = responses.map((response, index) => - (
  • {response}
  • )); - const isRenderTooltip = reason === 'NotFilteredWhiteList'; - - return ( -
    - {isRewrite && rewrite_applied} - - {this.renderTooltip(isRenderTooltip, rule, filterName)} -
    - ); - } - return ( -
    - {isRewrite && rewrite_applied} - empty_response_status - {this.renderTooltip(isFiltered, rule, filterName)} -
    - ); + return true; + }, + Filter: ({ filter, onChange }) => ( + + ), }, - filterMethod: (filter, row) => { - if (filter.value === 'filtered') { - // eslint-disable-next-line no-underscore-dangle - return row._original.reason.indexOf('Filtered') === 0 || row._original.reason === 'NotFilteredWhiteList'; - } - return true; + { + Header: t('client_table_header'), + accessor: 'client', + maxWidth: 220, + minWidth: 220, + Cell: this.getClientCell, }, - Filter: ({ filter, onChange }) => - , - }, { - Header: t('client_table_header'), - accessor: 'client', - maxWidth: 250, - Cell: (row) => { - const { reason } = row.original; - const isFiltered = row ? reason.indexOf('Filtered') === 0 : false; - const isRewrite = reason && reason === 'Rewrite'; - const clientName = getClientName(dashboard.clients, row.value) - || getClientName(dashboard.autoClients, row.value); - let client; - - if (clientName) { - client = {clientName} ({row.value}); - } else { - client = row.value; - } - - if (isRewrite) { - return ( - -
    - {client} -
    -
    - - configure - -
    -
    - ); - } - - return ( - -
    - {client} -
    - {this.renderBlockingButton(isFiltered, row.original.domain)} -
    - ); - }, - }, ]; if (logs) { - return ( { - const id = filter.pivotId || filter.id; - return row[id] !== undefined ? - String(row[id]).indexOf(filter.value) !== -1 : true; - }} - defaultSorted={[ - { - id: 'time', - desc: true, - }, - ]} - getTrProps={(_state, rowInfo) => { - // highlight filtered requests - if (!rowInfo) { - return {}; - } + return ( + { + const id = filter.pivotId || filter.id; + return row[id] !== undefined + ? String(row[id]).indexOf(filter.value) !== -1 + : true; + }} + defaultSorted={[ + { + id: 'time', + desc: true, + }, + ]} + getTrProps={(_state, rowInfo) => { + if (!rowInfo) { + return {}; + } - if (rowInfo.original.reason.indexOf('Filtered') === 0) { - return { - className: 'red', - }; - } else if (rowInfo.original.reason === 'NotFilteredWhiteList') { - return { - className: 'green', - }; - } else if (rowInfo.original.reason === 'Rewrite') { - return { - className: 'blue', - }; - } + const { reason } = rowInfo.original; - return { - className: '', - }; - }} - />); + if (this.checkFiltered(reason)) { + return { + className: 'red', + }; + } else if (this.checkWhiteList(reason)) { + return { + className: 'green', + }; + } else if (this.checkRewrite(reason)) { + return { + className: 'blue', + }; + } + + return { + className: '', + }; + }} + /> + ); } - return undefined; + + return null; } handleDownloadButton = async (e) => { @@ -325,17 +364,23 @@ class Logs extends Component { type="submit" onClick={() => this.props.toggleLogStatus(queryLogEnabled)} disabled={logStatusProcessing} - >disabled_log_btn + > + disabled_log_btn + + > + download_log_file_btn + + > + refresh_btn + ); } @@ -346,7 +391,9 @@ class Logs extends Component { type="submit" onClick={() => this.props.toggleLogStatus(queryLogEnabled)} disabled={logStatusProcessing} - >enabled_log_btn + > + enabled_log_btn + ); } @@ -355,24 +402,19 @@ class Logs extends Component { const { queryLogEnabled } = dashboard; return ( - +
    {this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
    - { - queryLogEnabled - && queryLogs.getLogsProcessing - && dashboard.processingClients - && - } - { - queryLogEnabled - && !queryLogs.getLogsProcessing - && !dashboard.processingClients - && this.renderLogs(queryLogs.logs) - } + {queryLogEnabled && + queryLogs.getLogsProcessing && + dashboard.processingClients && } + {queryLogEnabled && + !queryLogs.getLogsProcessing && + !dashboard.processingClients && + this.renderLogs(queryLogs.logs)}
    ); @@ -380,20 +422,17 @@ class Logs extends Component { } Logs.propTypes = { - getLogs: PropTypes.func, - queryLogs: PropTypes.object, - dashboard: PropTypes.object, - toggleLogStatus: PropTypes.func, - downloadQueryLog: PropTypes.func, - getFilteringStatus: PropTypes.func, - filtering: PropTypes.object, - userRules: PropTypes.string, - setRules: PropTypes.func, - addSuccessToast: PropTypes.func, - processingRules: PropTypes.bool, - logStatusProcessing: PropTypes.bool, - t: PropTypes.func, + getLogs: PropTypes.func.isRequired, + queryLogs: PropTypes.object.isRequired, + dashboard: PropTypes.object.isRequired, + toggleLogStatus: PropTypes.func.isRequired, + downloadQueryLog: PropTypes.func.isRequired, + getFilteringStatus: PropTypes.func.isRequired, + filtering: PropTypes.object.isRequired, + setRules: PropTypes.func.isRequired, + addSuccessToast: PropTypes.func.isRequired, getClients: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, }; export default withNamespaces()(Logs); diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index e72794be..74ac19c1 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -253,3 +253,10 @@ export const ENCRYPTION_SOURCE = { PATH: 'path', CONTENT: 'content', }; + +export const FILTERED_STATUS = { + FILTERED_BLACK_LIST: 'FilteredBlackList', + NOT_FILTERED_WHITE_LIST: 'NotFilteredWhiteList', + FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService', + REWRITE: 'Rewrite', +}; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index aeeba588..def170f0 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -28,6 +28,7 @@ export const normalizeLogs = logs => logs.map((log) => { filterId, rule, service_name, + status, } = log; const { host: domain, type } = question; const responsesArray = response ? response.map((response) => { @@ -44,6 +45,7 @@ export const normalizeLogs = logs => logs.map((log) => { filterId, rule, serviceName: service_name, + status, }; });