Merge pull request #43 in DNS/adguard-dns from feature/341 to master
* commit 'e20bfe9d08d6c60c8f37ec49dcda2f446bdf0ce5': Replace line endings on save Add "block" and "unblock" buttons to the Query Log
This commit is contained in:
commit
0c3c8dba9b
|
@ -293,7 +293,10 @@ export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
|
||||||
export const setRules = rules => async (dispatch) => {
|
export const setRules = rules => async (dispatch) => {
|
||||||
dispatch(setRulesRequest());
|
dispatch(setRulesRequest());
|
||||||
try {
|
try {
|
||||||
await apiClient.setRules(rules);
|
const replacedLineEndings = rules
|
||||||
|
.replace(/^\n/g, '')
|
||||||
|
.replace(/\n\s*\n/g, '\n');
|
||||||
|
await apiClient.setRules(replacedLineEndings);
|
||||||
dispatch(addSuccessToast('Custom rules saved'));
|
dispatch(addSuccessToast('Custom rules saved'));
|
||||||
dispatch(setRulesSuccess());
|
dispatch(setRulesSuccess());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default class UserRules extends Component {
|
||||||
subtitle="Enter one rule on a line. You can use either adblock rules or hosts files syntax."
|
subtitle="Enter one rule on a line. You can use either adblock rules or hosts files syntax."
|
||||||
>
|
>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<textarea className="form-control" value={this.props.userRules} onChange={this.handleChange} />
|
<textarea className="form-control form-control--textarea" value={this.props.userRules} onChange={this.handleChange} />
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
<button
|
<button
|
||||||
className="btn btn-success btn-standart"
|
className="btn btn-success btn-standart"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
.logs__row {
|
.logs__row {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row--overflow {
|
.logs__row--overflow {
|
||||||
|
@ -24,3 +26,36 @@
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__action {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .rt-td {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .rt-tr:hover .logs__action {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .rt-tr-group:first-child .tooltip-custom:before {
|
||||||
|
top: calc(100% + 12px);
|
||||||
|
bottom: initial;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs__table .rt-tr-group:first-child .tooltip-custom:after {
|
||||||
|
top: initial;
|
||||||
|
bottom: -4px;
|
||||||
|
border-top: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid #585965;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import { saveAs } from 'file-saver/FileSaver';
|
import { saveAs } from 'file-saver/FileSaver';
|
||||||
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
|
import endsWith from 'lodash/endsWith';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
|
@ -13,6 +15,7 @@ const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
|
||||||
class Logs extends Component {
|
class Logs extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getLogs();
|
this.getLogs();
|
||||||
|
this.props.getFilteringStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -36,6 +39,48 @@ class Logs extends Component {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleBlocking = (type, domain) => {
|
||||||
|
const { userRules } = this.props.filtering;
|
||||||
|
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
||||||
|
let blockingRule = `@@||${domain}^$important`;
|
||||||
|
let unblockingRule = `||${domain}^$important`;
|
||||||
|
|
||||||
|
if (type === 'unblock') {
|
||||||
|
blockingRule = `||${domain}^$important`;
|
||||||
|
unblockingRule = `@@||${domain}^$important`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`, ''));
|
||||||
|
this.props.addSuccessToast(`Removing rule from custom list: ${blockingRule}`);
|
||||||
|
} else if (!userRules.match(preparedUnblockingRule)) {
|
||||||
|
this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
|
||||||
|
this.props.addSuccessToast(`Adding rule to custom list: ${unblockingRule}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.getFilteringStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBlockingButton(isFiltered, domain) {
|
||||||
|
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
|
||||||
|
const buttonText = isFiltered ? 'Unblock' : 'Block';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs__action">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${buttonClass}`}
|
||||||
|
onClick={() => this.toggleBlocking(buttonText.toLowerCase(), domain)}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderLogs(logs) {
|
renderLogs(logs) {
|
||||||
const columns = [{
|
const columns = [{
|
||||||
Header: 'Time',
|
Header: 'Time',
|
||||||
|
@ -85,14 +130,14 @@ class Logs extends Component {
|
||||||
(<li key={index} title={response}>{response}</li>));
|
(<li key={index} title={response}>{response}</li>));
|
||||||
return (
|
return (
|
||||||
<div className="logs__row">
|
<div className="logs__row">
|
||||||
{ this.renderTooltip(isFiltered, rule)}
|
{this.renderTooltip(isFiltered, rule)}
|
||||||
<ul className="list-unstyled">{liNodes}</ul>
|
<ul className="list-unstyled">{liNodes}</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="logs__row">
|
<div className="logs__row">
|
||||||
{ this.renderTooltip(isFiltered, rule) }
|
{this.renderTooltip(isFiltered, rule)}
|
||||||
<span>Empty</span>
|
<span>Empty</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -101,11 +146,25 @@ class Logs extends Component {
|
||||||
Header: 'Client',
|
Header: 'Client',
|
||||||
accessor: 'client',
|
accessor: 'client',
|
||||||
maxWidth: 250,
|
maxWidth: 250,
|
||||||
|
Cell: (row) => {
|
||||||
|
const { reason } = row.original;
|
||||||
|
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className="logs__row">
|
||||||
|
{row.value}
|
||||||
|
</div>
|
||||||
|
{this.renderBlockingButton(isFiltered, row.original.domain)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (logs) {
|
if (logs) {
|
||||||
return (<ReactTable
|
return (<ReactTable
|
||||||
|
className='logs__table'
|
||||||
data={logs}
|
data={logs}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
showPagination={false}
|
showPagination={false}
|
||||||
|
@ -187,6 +246,11 @@ Logs.propTypes = {
|
||||||
dashboard: PropTypes.object,
|
dashboard: PropTypes.object,
|
||||||
toggleLogStatus: PropTypes.func,
|
toggleLogStatus: PropTypes.func,
|
||||||
downloadQueryLog: PropTypes.func,
|
downloadQueryLog: PropTypes.func,
|
||||||
|
getFilteringStatus: PropTypes.func,
|
||||||
|
filtering: PropTypes.object,
|
||||||
|
userRules: PropTypes.string,
|
||||||
|
setRules: PropTypes.func,
|
||||||
|
addSuccessToast: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logs;
|
export default Logs;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { getLogs, toggleLogStatus, downloadQueryLog } from '../actions';
|
import { getLogs, toggleLogStatus, downloadQueryLog, getFilteringStatus, setRules, addSuccessToast } from '../actions';
|
||||||
import Logs from '../components/Logs';
|
import Logs from '../components/Logs';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const { queryLogs, dashboard } = state;
|
const { queryLogs, dashboard, filtering } = state;
|
||||||
const props = { queryLogs, dashboard };
|
const props = { queryLogs, dashboard, filtering };
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ const mapDispatchToProps = {
|
||||||
getLogs,
|
getLogs,
|
||||||
toggleLogStatus,
|
toggleLogStatus,
|
||||||
downloadQueryLog,
|
downloadQueryLog,
|
||||||
|
getFilteringStatus,
|
||||||
|
setRules,
|
||||||
|
addSuccessToast,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
Loading…
Reference in New Issue