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,
};
});