badguardhome/client/src/components/Logs/index.js

388 lines
15 KiB
JavaScript
Raw Normal View History

import React, { Component, Fragment } from 'react';
2018-08-30 14:25:33 +00:00
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { saveAs } from 'file-saver/FileSaver';
import escapeRegExp from 'lodash/escapeRegExp';
import endsWith from 'lodash/endsWith';
import { Trans, withNamespaces } from 'react-i18next';
2019-07-22 12:32:12 +00:00
import { HashLink as Link } from 'react-router-hash-link';
2019-03-20 14:04:32 +00:00
import { formatTime, getClientName } from '../../helpers/helpers';
2018-10-14 20:24:11 +00:00
import { getTrackerData } from '../../helpers/trackers/trackers';
2018-08-30 14:25:33 +00:00
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import Loading from '../ui/Loading';
2018-10-30 14:27:47 +00:00
import PopoverFiltered from '../ui/PopoverFilter';
import Popover from '../ui/Popover';
2018-09-04 09:43:13 +00:00
import './Logs.css';
2018-08-30 14:25:33 +00:00
const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
class Logs extends Component {
componentDidMount() {
this.getLogs();
this.props.getFilteringStatus();
this.props.getClients();
2018-08-30 14:25:33 +00:00
}
componentDidUpdate(prevProps) {
// get logs when queryLog becomes enabled
if (this.props.dashboard.queryLogEnabled && !prevProps.dashboard.queryLogEnabled) {
this.props.getLogs();
}
}
getLogs = () => {
// get logs on initialization if queryLogIsEnabled
if (this.props.dashboard.queryLogEnabled) {
this.props.getLogs();
}
}
2018-10-30 14:27:47 +00:00
renderTooltip(isFiltered, rule, filter) {
if (rule) {
2018-10-30 14:27:47 +00:00
return (isFiltered && <PopoverFiltered rule={rule} filter={filter}/>);
}
return '';
}
toggleBlocking = (type, domain) => {
const { userRules } = this.props.filtering;
2018-11-05 04:26:32 +00:00
const { t } = this.props;
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
2018-10-08 15:44:12 +00:00
const baseRule = `||${domain}^$important`;
2018-10-09 08:00:48 +00:00
const baseUnblocking = `@@${baseRule}`;
const blockingRule = type === 'block' ? baseUnblocking : baseRule;
const unblockingRule = type === 'block' ? baseRule : baseUnblocking;
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
if (userRules.match(preparedBlockingRule)) {
this.props.setRules(userRules.replace(`${blockingRule}`, ''));
2018-11-09 06:51:28 +00:00
this.props.addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
} else if (!userRules.match(preparedUnblockingRule)) {
this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
2018-11-09 06:51:28 +00:00
this.props.addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
}
this.props.getFilteringStatus();
}
renderBlockingButton(isFiltered, domain) {
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
2018-11-12 01:32:49 +00:00
const buttonText = isFiltered ? 'unblock_btn' : 'block_btn';
2018-11-26 15:17:34 +00:00
const buttonType = isFiltered ? 'unblock' : 'block';
return (
<div className="logs__action">
<button
type="button"
className={`btn btn-sm ${buttonClass}`}
2018-11-26 15:17:34 +00:00
onClick={() => this.toggleBlocking(buttonType, domain)}
disabled={this.props.filtering.processingRules}
>
<Trans>{buttonText}</Trans>
</button>
</div>
);
}
2018-08-30 14:25:33 +00:00
renderLogs(logs) {
2019-03-20 14:04:32 +00:00
const { t, dashboard } = this.props;
2018-08-30 14:25:33 +00:00
const columns = [{
2018-11-09 06:51:28 +00:00
Header: t('time_table_header'),
2018-08-30 14:25:33 +00:00
accessor: 'time',
2018-09-04 09:43:13 +00:00
maxWidth: 110,
2018-10-02 15:14:41 +00:00
filterable: false,
Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>),
2018-08-30 14:25:33 +00:00
}, {
2018-11-09 06:51:28 +00:00
Header: t('domain_name_table_header'),
2018-08-30 14:25:33 +00:00
accessor: 'domain',
2018-09-04 09:43:13 +00:00
Cell: (row) => {
const response = row.value;
const trackerData = getTrackerData(response);
2018-09-04 09:43:13 +00:00
return (
<div className="logs__row" title={response}>
2018-09-04 09:43:13 +00:00
<div className="logs__text">
{response}
</div>
{trackerData && <Popover data={trackerData}/>}
2018-09-04 09:43:13 +00:00
</div>
);
},
2018-08-30 14:25:33 +00:00
}, {
2018-11-09 06:51:28 +00:00
Header: t('type_table_header'),
2018-08-30 14:25:33 +00:00
accessor: 'type',
2018-09-04 09:43:13 +00:00
maxWidth: 60,
2018-08-30 14:25:33 +00:00
}, {
2018-11-09 06:51:28 +00:00
Header: t('response_table_header'),
2018-08-30 14:25:33 +00:00
accessor: 'response',
Cell: (row) => {
const responses = row.value;
2018-09-05 15:20:38 +00:00
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;
2018-10-30 14:27:47 +00:00
const { filterId } = row.original;
const { filters } = this.props.filtering;
2019-07-22 12:32:12 +00:00
const isRewrite = reason && reason === 'Rewrite';
2018-10-30 14:27:47 +00:00
let filterName = '';
if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
if (filterId === 0) {
2018-11-26 15:17:34 +00:00
filterName = t('custom_filter_rules');
2018-10-30 14:27:47 +00:00
} 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 });
}
2018-10-30 14:27:47 +00:00
}
}
2018-09-05 15:20:38 +00:00
if (isFiltered) {
return (
<div className="logs__row">
2018-09-07 16:06:09 +00:00
<span className="logs__text" title={parsedFilteredReason}>
{parsedFilteredReason}
</span>
2018-10-30 14:27:47 +00:00
{this.renderTooltip(isFiltered, rule, filterName)}
2018-09-05 15:20:38 +00:00
</div>
);
}
2018-08-30 14:25:33 +00:00
if (responses.length > 0) {
const liNodes = responses.map((response, index) =>
2018-09-04 09:43:13 +00:00
(<li key={index} title={response}>{response}</li>));
2018-10-30 14:27:47 +00:00
const isRenderTooltip = reason === 'NotFilteredWhiteList';
2018-09-04 09:43:13 +00:00
return (
2019-07-22 12:32:12 +00:00
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
<ul className="list-unstyled">{liNodes}</ul>
2018-10-30 14:27:47 +00:00
{this.renderTooltip(isRenderTooltip, rule, filterName)}
2018-09-04 09:43:13 +00:00
</div>
);
2018-08-30 14:25:33 +00:00
}
2018-09-04 09:43:13 +00:00
return (
2019-07-22 12:32:12 +00:00
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
2018-11-09 06:51:28 +00:00
<span><Trans>empty_response_status</Trans></span>
2018-10-30 14:27:47 +00:00
{this.renderTooltip(isFiltered, rule, filterName)}
2018-09-04 09:43:13 +00:00
</div>
);
2018-08-30 14:25:33 +00:00
},
2018-10-02 15:14:41 +00:00
filterMethod: (filter, row) => {
if (filter.value === 'filtered') {
2018-10-02 15:30:34 +00:00
// eslint-disable-next-line no-underscore-dangle
2018-11-26 15:17:34 +00:00
return row._original.reason.indexOf('Filtered') === 0 || row._original.reason === 'NotFilteredWhiteList';
2018-10-02 15:14:41 +00:00
}
return true;
},
Filter: ({ filter, onChange }) =>
<select
onChange={event => onChange(event.target.value)}
className="form-control"
value={filter ? filter.value : 'all'}
>
2018-11-09 06:51:28 +00:00
<option value="all">{ t('show_all_filter_type') }</option>
<option value="filtered">{ t('show_filtered_type') }</option>
2018-10-02 15:14:41 +00:00
</select>,
}, {
2018-11-28 07:23:50 +00:00
Header: t('client_table_header'),
accessor: 'client',
2018-09-04 09:43:13 +00:00
maxWidth: 250,
Cell: (row) => {
const { reason } = row.original;
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
2019-07-22 12:32:12 +00:00
const isRewrite = reason && reason === 'Rewrite';
2019-05-23 11:14:22 +00:00
const clientName = getClientName(dashboard.clients, row.value)
|| getClientName(dashboard.autoClients, row.value);
2019-03-20 14:04:32 +00:00
let client;
if (clientName) {
client = <span>{clientName} <small>({row.value})</small></span>;
} else {
client = row.value;
}
2019-07-22 12:32:12 +00:00
if (isRewrite) {
return (
<Fragment>
<div className="logs__row">
{client}
</div>
<div className="logs__action">
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
<Trans>configure</Trans>
</Link>
</div>
</Fragment>
);
}
return (
<Fragment>
<div className="logs__row">
2019-03-20 14:04:32 +00:00
{client}
</div>
{this.renderBlockingButton(isFiltered, row.original.domain)}
</Fragment>
);
},
},
];
2018-08-30 14:25:33 +00:00
if (logs) {
return (<ReactTable
className='logs__table'
2018-10-02 15:14:41 +00:00
filterable
data={logs}
2018-08-30 14:25:33 +00:00
columns={columns}
2018-10-02 15:14:41 +00:00
showPagination={true}
defaultPageSize={50}
2018-08-30 14:25:33 +00:00
minRows={7}
// Text
2018-11-09 06:51:28 +00:00
previousText={ t('previous_btn') }
nextText={ t('next_btn') }
loadingText={ t('loading_table_status') }
pageText={ t('page_table_footer_text') }
ofText={ t('of_table_footer_text') }
rowsText={ t('rows_table_footer_text') }
noDataText={ t('no_logs_found') }
2018-10-08 15:44:12 +00:00
defaultFilterMethod={(filter, row) => {
const id = filter.pivotId || filter.id;
return row[id] !== undefined ?
String(row[id]).indexOf(filter.value) !== -1 : true;
}}
2018-08-30 14:25:33 +00:00
defaultSorted={[
{
id: 'time',
desc: true,
},
]}
getTrProps={(_state, rowInfo) => {
// highlight filtered requests
if (!rowInfo) {
return {};
}
if (rowInfo.original.reason.indexOf('Filtered') === 0) {
return {
className: 'red',
};
} else if (rowInfo.original.reason === 'NotFilteredWhiteList') {
return {
className: 'green',
};
2019-07-22 12:32:12 +00:00
} else if (rowInfo.original.reason === 'Rewrite') {
return {
className: 'blue',
};
}
return {
className: '',
};
}}
2018-08-30 14:25:33 +00:00
/>);
}
return undefined;
}
handleDownloadButton = async (e) => {
e.preventDefault();
const data = await this.props.downloadQueryLog();
const jsonStr = JSON.stringify(data);
const dataBlob = new Blob([jsonStr], { type: 'text/plain;charset=utf-8' });
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
};
renderButtons(queryLogEnabled, logStatusProcessing) {
2018-10-02 15:14:41 +00:00
if (queryLogEnabled) {
return (
<Fragment>
<button
2018-10-02 15:14:41 +00:00
className="btn btn-gray btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
2018-11-09 06:51:28 +00:00
><Trans>disabled_log_btn</Trans></button>
2018-10-02 15:14:41 +00:00
<button
className="btn btn-primary btn-sm mr-2"
type="submit"
onClick={this.handleDownloadButton}
2018-11-09 06:51:28 +00:00
><Trans>download_log_file_btn</Trans></button>
<button
2018-10-02 15:14:41 +00:00
className="btn btn-outline-primary btn-sm"
type="submit"
onClick={this.getLogs}
2018-11-09 06:51:28 +00:00
><Trans>refresh_btn</Trans></button>
2018-10-02 15:14:41 +00:00
</Fragment>
);
}
return (
<button
className="btn btn-success btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
2018-11-09 06:51:28 +00:00
><Trans>enabled_log_btn</Trans></button>
2018-10-02 15:14:41 +00:00
);
2018-08-30 14:25:33 +00:00
}
render() {
const { queryLogs, dashboard, t } = this.props;
2018-08-30 14:25:33 +00:00
const { queryLogEnabled } = dashboard;
return (
2018-10-02 15:14:41 +00:00
<Fragment>
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
2018-10-02 15:14:41 +00:00
<div className="page-title__actions">
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
2018-10-02 15:14:41 +00:00
</div>
</PageTitle>
2018-08-30 14:25:33 +00:00
<Card>
2019-03-20 14:04:32 +00:00
{
queryLogEnabled
&& queryLogs.getLogsProcessing
&& dashboard.processingClients
&& <Loading />
}
{
queryLogEnabled
&& !queryLogs.getLogsProcessing
&& !dashboard.processingClients
&& this.renderLogs(queryLogs.logs)
}
2018-08-30 14:25:33 +00:00
</Card>
2018-10-02 15:14:41 +00:00
</Fragment>
2018-08-30 14:25:33 +00:00
);
}
}
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,
getClients: PropTypes.func.isRequired,
2018-08-30 14:25:33 +00:00
};
export default withNamespaces()(Logs);