Merge: + client: add response information to the filtered query log items
Closes #912 * commit '428706399ae5bcd01daf4b4146c81a481fd680ef': + client: add response information to the filtered query log items
This commit is contained in:
commit
47e5fcb14c
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 && <PopoverFiltered rule={rule} filter={filter} service={service} />;
|
||||
|
||||
renderResponseList = (response, status) => {
|
||||
if (response.length > 0) {
|
||||
const listItems = response.map((response, index) => (
|
||||
<li key={index} title={response} className="logs__list-item">
|
||||
{response}
|
||||
</li>
|
||||
));
|
||||
|
||||
return <ul className="list-unstyled">{listItems}</ul>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Trans values={{ value: status }}>query_log_response_status</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,56 +107,58 @@ class Logs extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderLogs(logs) {
|
||||
const { t, dashboard } = this.props;
|
||||
const columns = [{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
maxWidth: 110,
|
||||
filterable: false,
|
||||
Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>),
|
||||
}, {
|
||||
Header: t('domain_name_table_header'),
|
||||
accessor: 'domain',
|
||||
Cell: (row) => {
|
||||
checkFiltered = reason => reason.indexOf(FILTERED_REASON) === 0;
|
||||
|
||||
checkRewrite = reason => reason === FILTERED_STATUS.REWRITE;
|
||||
|
||||
checkWhiteList = reason => reason === FILTERED_STATUS.NOT_FILTERED_WHITE_LIST;
|
||||
|
||||
getTimeCell = ({ value }) => (
|
||||
<div className="logs__row">
|
||||
<span className="logs__text" title={value}>
|
||||
{formatTime(value)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
getDomainCell = (row) => {
|
||||
const response = row.value;
|
||||
const trackerData = getTrackerData(response);
|
||||
|
||||
return (
|
||||
<div className="logs__row" title={response}>
|
||||
<div className="logs__text">
|
||||
{response}
|
||||
</div>
|
||||
<div className="logs__text">{response}</div>
|
||||
{trackerData && <Popover data={trackerData} />}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
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';
|
||||
};
|
||||
|
||||
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 (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
|
||||
if (filterId === 0) {
|
||||
filterName = t('custom_filter_rules');
|
||||
} else {
|
||||
const filterItem = Object.keys(filters)
|
||||
.filter(key => filters[key].id === filterId);
|
||||
const filterItem = Object.keys(filters).filter(key => filters[key].id === filterId)[0];
|
||||
|
||||
if (typeof filterItem !== 'undefined' && typeof filters[filterItem] !== 'undefined') {
|
||||
if (
|
||||
typeof filterItem !== 'undefined' &&
|
||||
typeof filters[filterItem] !== 'undefined'
|
||||
) {
|
||||
filterName = filters[filterItem].name;
|
||||
}
|
||||
|
||||
|
@ -141,126 +166,136 @@ class Logs extends Component {
|
|||
filterName = t('unknown_filter', { filterId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reason === 'FilteredBlockedService') {
|
||||
const getService = SERVICES
|
||||
.find(service => service.id === row.original.serviceName);
|
||||
const serviceName = getService && getService.name;
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<div className="logs__row logs__row--column">
|
||||
<div className="logs__text-wrap">
|
||||
{(isFiltered || isBlockedService) && (
|
||||
<span className="logs__text" title={parsedFilteredReason}>
|
||||
{parsedFilteredReason}
|
||||
</span>
|
||||
{this.renderTooltip(isFiltered, '', '', serviceName)}
|
||||
)}
|
||||
{isBlockedService
|
||||
? this.renderTooltip(isFiltered, '', '', serviceName)
|
||||
: this.renderTooltip(isFiltered, rule, filterName)}
|
||||
{isRewrite && (
|
||||
<strong>
|
||||
<Trans>rewrite_applied</Trans>
|
||||
</strong>
|
||||
)}
|
||||
</div>
|
||||
<div className="logs__list-wrap">
|
||||
{this.renderResponseList(responses, status)}
|
||||
{isWhiteList && this.renderTooltip(isWhiteList, rule, filterName)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (isFiltered) {
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<span className="logs__text" title={parsedFilteredReason}>
|
||||
{parsedFilteredReason}
|
||||
</span>
|
||||
{this.renderTooltip(isFiltered, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (responses.length > 0) {
|
||||
const liNodes = responses.map((response, index) =>
|
||||
(<li key={index} title={response}>{response}</li>));
|
||||
const isRenderTooltip = reason === 'NotFilteredWhiteList';
|
||||
|
||||
return (
|
||||
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
|
||||
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
|
||||
<ul className="list-unstyled">{liNodes}</ul>
|
||||
{this.renderTooltip(isRenderTooltip, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
|
||||
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
|
||||
<span><Trans>empty_response_status</Trans></span>
|
||||
{this.renderTooltip(isFiltered, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
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;
|
||||
},
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="form-control"
|
||||
value={filter ? filter.value : 'all'}
|
||||
>
|
||||
<option value="all">{ t('show_all_filter_type') }</option>
|
||||
<option value="filtered">{ t('show_filtered_type') }</option>
|
||||
</select>,
|
||||
}, {
|
||||
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;
|
||||
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 = <span>{clientName} <small>({row.value})</small></span>;
|
||||
} else {
|
||||
client = row.value;
|
||||
client = (
|
||||
<span>
|
||||
{clientName} <small>({value})</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isRewrite) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{client}
|
||||
</div>
|
||||
<div className="logs__row">{client}</div>
|
||||
{isRewrite ? (
|
||||
<div className="logs__action">
|
||||
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
|
||||
<Trans>configure</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
this.renderBlockingButton(isFiltered, domain)
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderLogs(logs) {
|
||||
const { t } = this.props;
|
||||
const columns = [
|
||||
{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
maxWidth: 90,
|
||||
filterable: false,
|
||||
Cell: this.getTimeCell,
|
||||
},
|
||||
{
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{client}
|
||||
</div>
|
||||
{this.renderBlockingButton(isFiltered, row.original.domain)}
|
||||
</Fragment>
|
||||
);
|
||||
return true;
|
||||
},
|
||||
Filter: ({ filter, onChange }) => (
|
||||
<select
|
||||
className="form-control"
|
||||
onChange={event => onChange(event.target.value)}
|
||||
value={filter ? filter.value : RESPONSE_FILTER.ALL}
|
||||
>
|
||||
<option value={RESPONSE_FILTER.ALL}>
|
||||
<Trans>show_all_filter_type</Trans>
|
||||
</option>
|
||||
<option value={RESPONSE_FILTER.FILTERED}>
|
||||
<Trans>show_filtered_type</Trans>
|
||||
</option>
|
||||
</select>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: t('client_table_header'),
|
||||
accessor: 'client',
|
||||
maxWidth: 220,
|
||||
minWidth: 220,
|
||||
Cell: this.getClientCell,
|
||||
},
|
||||
];
|
||||
|
||||
if (logs) {
|
||||
return (<ReactTable
|
||||
className='logs__table'
|
||||
return (
|
||||
<ReactTable
|
||||
className="logs__table"
|
||||
filterable
|
||||
data={logs}
|
||||
columns={columns}
|
||||
showPagination={true}
|
||||
defaultPageSize={50}
|
||||
minRows={7}
|
||||
// Text
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
|
@ -270,8 +305,9 @@ class Logs extends Component {
|
|||
noDataText={t('no_logs_found')}
|
||||
defaultFilterMethod={(filter, row) => {
|
||||
const id = filter.pivotId || filter.id;
|
||||
return row[id] !== undefined ?
|
||||
String(row[id]).indexOf(filter.value) !== -1 : true;
|
||||
return row[id] !== undefined
|
||||
? String(row[id]).indexOf(filter.value) !== -1
|
||||
: true;
|
||||
}}
|
||||
defaultSorted={[
|
||||
{
|
||||
|
@ -280,20 +316,21 @@ class Logs extends Component {
|
|||
},
|
||||
]}
|
||||
getTrProps={(_state, rowInfo) => {
|
||||
// highlight filtered requests
|
||||
if (!rowInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (rowInfo.original.reason.indexOf('Filtered') === 0) {
|
||||
const { reason } = rowInfo.original;
|
||||
|
||||
if (this.checkFiltered(reason)) {
|
||||
return {
|
||||
className: 'red',
|
||||
};
|
||||
} else if (rowInfo.original.reason === 'NotFilteredWhiteList') {
|
||||
} else if (this.checkWhiteList(reason)) {
|
||||
return {
|
||||
className: 'green',
|
||||
};
|
||||
} else if (rowInfo.original.reason === 'Rewrite') {
|
||||
} else if (this.checkRewrite(reason)) {
|
||||
return {
|
||||
className: 'blue',
|
||||
};
|
||||
|
@ -303,9 +340,11 @@ class Logs extends Component {
|
|||
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}
|
||||
><Trans>disabled_log_btn</Trans></button>
|
||||
>
|
||||
<Trans>disabled_log_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={this.handleDownloadButton}
|
||||
><Trans>download_log_file_btn</Trans></button>
|
||||
>
|
||||
<Trans>download_log_file_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
type="submit"
|
||||
onClick={this.getLogs}
|
||||
><Trans>refresh_btn</Trans></button>
|
||||
>
|
||||
<Trans>refresh_btn</Trans>
|
||||
</button>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -346,7 +391,9 @@ class Logs extends Component {
|
|||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>enabled_log_btn</Trans></button>
|
||||
>
|
||||
<Trans>enabled_log_btn</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -361,18 +408,13 @@ class Logs extends Component {
|
|||
</div>
|
||||
</PageTitle>
|
||||
<Card>
|
||||
{
|
||||
queryLogEnabled
|
||||
&& queryLogs.getLogsProcessing
|
||||
&& dashboard.processingClients
|
||||
&& <Loading />
|
||||
}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& !queryLogs.getLogsProcessing
|
||||
&& !dashboard.processingClients
|
||||
&& this.renderLogs(queryLogs.logs)
|
||||
}
|
||||
{queryLogEnabled &&
|
||||
queryLogs.getLogsProcessing &&
|
||||
dashboard.processingClients && <Loading />}
|
||||
{queryLogEnabled &&
|
||||
!queryLogs.getLogsProcessing &&
|
||||
!dashboard.processingClients &&
|
||||
this.renderLogs(queryLogs.logs)}
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue