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:
Eugene Bujak 2018-09-28 20:07:38 +03:00
commit 0c3c8dba9b
5 changed files with 113 additions and 8 deletions

View File

@ -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) {

View File

@ -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"

View File

@ -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;
}

View File

@ -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;

View File

@ -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(