+ client: Add block and unblock buttons to 'check the filtering' result

Close #1734

Squashed commit of the following:

commit b7e274318aea773b6b7effba0fcf13859a23d45c
Merge: 77c823a4 2f6f65a8
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Oct 1 23:36:49 2020 +0300

    Merge branch 'master' into feature/1734

commit 77c823a46ad6e094d332b40375a32511c8f6ff11
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Sep 24 22:36:56 2020 +0300

    Add blocking button

commit bb77029d46ce18e582db04ccf31f7ed7c57de572
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Sep 23 22:02:45 2020 +0300

    Refactor check info

commit bd8cf052a0458c767aa65d0d8749648f8e10fdba
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Sep 23 21:15:24 2020 +0300

    Fix prop processingCheck

commit 5a1d696f7e15f2e1367ff3f9a1767ee3c2344b1b
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Sep 23 20:35:21 2020 +0300

    Refactor Check component
This commit is contained in:
Artem Baskal 2020-10-02 11:33:12 +03:00
parent 2f6f65a81f
commit 128229ad73
4 changed files with 142 additions and 212 deletions

View File

@ -1,155 +1,124 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next';
import { withTranslation } from 'react-i18next'; import classNames from 'classnames';
import i18next from 'i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { import {
checkFiltered, checkFiltered,
checkRewrite, checkRewrite,
checkRewriteHosts, checkRewriteHosts,
checkBlackList,
checkNotFilteredNotFound,
checkWhiteList, checkWhiteList,
checkSafeSearch, checkSafeSearch,
checkSafeBrowsing, checkSafeBrowsing,
checkParental, getFilterName, checkParental,
getFilterName,
} from '../../../helpers/helpers'; } from '../../../helpers/helpers';
import { FILTERED } from '../../../helpers/constants'; import { BLOCK_ACTIONS, FILTERED, FILTERED_STATUS } from '../../../helpers/constants';
import { toggleBlocking } from '../../../actions';
const getTitle = (reason, filterName, t, onlyFiltered) => { const renderBlockingButton = (isFiltered, domain) => {
if (checkNotFilteredNotFound(reason)) { const processingRules = useSelector((state) => state.filtering.processingRules);
return t('check_not_found'); const dispatch = useDispatch();
} const { t } = useTranslation();
if (checkRewrite(reason)) { const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
return t('rewrite_applied');
}
if (checkRewriteHosts(reason)) { const onClick = async () => {
return t('rewrite_hosts_applied'); await dispatch(toggleBlocking(buttonType, domain));
} };
if (checkBlackList(reason)) { const buttonClass = classNames('mt-3 button-action button-action--main button-action--active button-action--small', {
return filterName; 'button-action--unblock': isFiltered,
} });
if (checkWhiteList(reason)) { return <button type="button"
return ( className={buttonClass}
<div> onClick={onClick}
{filterName} disabled={processingRules}
</div> >
); {t(buttonType)}
} </button>;
if (onlyFiltered) {
const filterKey = reason.replace(FILTERED, '');
return (
<div>
{t('query_log_filtered', { filter: filterKey })}
</div>
);
}
return (
<Fragment>
<div>
{t('check_reason', { reason })}
</div>
<div>
{filterName}
</div>
</Fragment>
);
}; };
const getColor = (reason) => { const getTitle = (reason) => {
if (checkFiltered(reason)) { const { t } = useTranslation();
return 'red';
} if (checkRewrite(reason) || checkRewriteHosts(reason)) {
return 'blue';
} if (checkWhiteList(reason)) {
return 'green';
}
return ''; const filters = useSelector((state) => state.filtering.filters, shallowEqual);
}; const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const filter_id = useSelector((state) => state.filtering.check.filter_id);
const Info = ({ const filterName = getFilterName(
filters, filters,
whitelistFilters,
hostname,
reason,
filter_id,
rule,
service_name,
cname,
ip_addrs,
t,
}) => {
const filterName = getFilterName(filters,
whitelistFilters, whitelistFilters,
filter_id, filter_id,
'filtered_custom_rules', 'filtered_custom_rules',
(filter) => (filter?.name ? t('query_log_filtered', { filter: filter.name }) : '')); (filter) => (filter?.name ? t('query_log_filtered', { filter: filter.name }) : ''),
);
const getReasonFiltered = (reason) => {
const filterKey = reason.replace(FILTERED, '');
return i18next.t('query_log_filtered', { filter: filterKey });
};
const REASON_TO_TITLE_MAP = {
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: t('check_not_found'),
[FILTERED_STATUS.REWRITE]: t('rewrite_applied'),
[FILTERED_STATUS.REWRITE_HOSTS]: t('rewrite_hosts_applied'),
[FILTERED_STATUS.FILTERED_BLACK_LIST]: filterName,
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: filterName,
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: getReasonFiltered(reason),
[FILTERED_STATUS.FILTERED_SAFE_BROWSING]: getReasonFiltered(reason),
[FILTERED_STATUS.FILTERED_PARENTAL]: getReasonFiltered(reason),
};
if (Object.prototype.hasOwnProperty.call(REASON_TO_TITLE_MAP, reason)) {
return REASON_TO_TITLE_MAP[reason];
}
return <>
<div>{t('check_reason', { reason })}</div>
<div>{filterName}</div>
</>;
};
const Info = () => {
const {
hostname,
reason,
rule,
service_name,
cname,
ip_addrs,
} = useSelector((state) => state.filtering.check, shallowEqual);
const { t } = useTranslation();
const title = getTitle(reason);
const className = classNames('card mb-0 p-3', {
'logs__row--red': checkFiltered(reason),
'logs__row--blue': checkRewrite(reason) || checkRewriteHosts(reason),
'logs__row--green': checkWhiteList(reason),
});
const onlyFiltered = checkSafeSearch(reason) const onlyFiltered = checkSafeSearch(reason)
|| checkSafeBrowsing(reason) || checkSafeBrowsing(reason)
|| checkParental(reason); || checkParental(reason);
const title = getTitle(reason, filterName, t, onlyFiltered);
const color = getColor(reason);
if (onlyFiltered) { const isFiltered = checkFiltered(reason);
return (
<div className={`card mb-0 p-3 ${color}`}>
<div>
<strong>{hostname}</strong>
</div>
<div>{title}</div> return <div className={className}>
</div> <div><strong>{hostname}</strong></div>
); <div>{title}</div>
} {!onlyFiltered
&& <>
return ( {rule && <div>{t('check_rule', { rule })}</div>}
<div className={`card mb-0 p-3 ${color}`}> {service_name && <div>{t('check_service', { service: service_name })}</div>}
<div> {cname && <div>{t('check_cname', { cname })}</div>}
<strong>{hostname}</strong> {ip_addrs && <div>{t('check_ip', { ip: ip_addrs.join(', ') })}</div>}
</div> {renderBlockingButton(isFiltered, hostname)}
</>}
<div>{title}</div> </div>;
{rule && (
<div>{t('check_rule', { rule })}</div>
)}
{service_name && (
<div>{t('check_service', { service: service_name })}</div>
)}
{cname && (
<div>{t('check_cname', { cname })}</div>
)}
{ip_addrs && (
<div>
{t('check_ip', { ip: ip_addrs.join(', ') })}
</div>
)}
</div>
);
}; };
Info.propTypes = { export default Info;
filters: PropTypes.array.isRequired,
whitelistFilters: PropTypes.array.isRequired,
hostname: PropTypes.string.isRequired,
reason: PropTypes.string.isRequired,
filter_id: PropTypes.number,
rule: PropTypes.string,
service_name: PropTypes.string,
cname: PropTypes.string,
ip_addrs: PropTypes.array,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Info);

View File

@ -1,99 +1,66 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Field, reduxForm } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import flow from 'lodash/flow'; import { useSelector } from 'react-redux';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
import { renderInputField } from '../../../helpers/form'; import { renderInputField } from '../../../helpers/form';
import Info from './Info'; import Info from './Info';
import { FORM_NAME } from '../../../helpers/constants'; import { FORM_NAME } from '../../../helpers/constants';
const Check = (props) => { const Check = (props) => {
const { const {
t,
handleSubmit,
pristine, pristine,
invalid, invalid,
processing, handleSubmit,
check,
filters,
whitelistFilters,
} = props; } = props;
const { const { t } = useTranslation();
hostname,
reason,
filter_id,
rule,
service_name,
cname,
ip_addrs,
} = check;
return ( const processingCheck = useSelector((state) => state.filtering.processingCheck);
<Card const hostname = useSelector((state) => state.filtering.check.hostname);
title={t('check_title')}
subtitle={t('check_desc')} return <Card
> title={t('check_title')}
<form onSubmit={handleSubmit}> subtitle={t('check_desc')}
<div className="row"> >
<div className="col-12 col-md-6"> <form onSubmit={handleSubmit}>
<div className="input-group"> <div className="row">
<Field <div className="col-12 col-md-6">
id="name" <div className="input-group">
name="name" <Field
component={renderInputField} id="name"
type="text" name="name"
className="form-control" component={renderInputField}
placeholder={t('form_enter_host')} type="text"
/> className="form-control"
<span className="input-group-append"> placeholder={t('form_enter_host')}
<button />
className="btn btn-success btn-standard btn-large" <span className="input-group-append">
type="submit" <button
onClick={handleSubmit} className="btn btn-success btn-standard btn-large"
disabled={pristine || invalid || processing} type="submit"
> onClick={handleSubmit}
<Trans>check</Trans> disabled={pristine || invalid || processingCheck}
</button> >
</span> {t('check')}
</div> </button>
{check.hostname && ( </span>
<Fragment>
<hr />
<Info
filters={filters}
whitelistFilters={whitelistFilters}
hostname={hostname}
reason={reason}
filter_id={filter_id}
rule={rule}
service_name={service_name}
cname={cname}
ip_addrs={ip_addrs}
/>
</Fragment>
)}
</div> </div>
{hostname && <>
<hr />
<Info />
</>}
</div> </div>
</form> </div>
</Card> </form>
); </Card>;
}; };
Check.propTypes = { Check.propTypes = {
t: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired, pristine: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired, invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
check: PropTypes.object.isRequired,
filters: PropTypes.array.isRequired,
whitelistFilters: PropTypes.array.isRequired,
}; };
export default flow([ export default reduxForm({ form: FORM_NAME.DOMAIN_CHECK })(Check);
withTranslation(),
reduxForm({ form: FORM_NAME.DOMAIN_CHECK }),
])(Check);

View File

@ -45,11 +45,7 @@ class CustomRules extends Component {
const { const {
t, t,
filtering: { filtering: {
filters,
whitelistFilters,
userRules, userRules,
processingCheck,
check,
}, },
} = this.props; } = this.props;
@ -92,13 +88,7 @@ class CustomRules extends Component {
<hr /> <hr />
<Examples /> <Examples />
</Card> </Card>
<Check <Check onSubmit={this.handleCheck} />
filters={filters}
whitelistFilters={whitelistFilters}
check={check}
onSubmit={this.handleCheck}
processing={processingCheck}
/>
</> </>
); );
} }

View File

@ -238,6 +238,10 @@
border: 0; border: 0;
} }
.button-action--small {
width: fit-content;
}
.button-action--unblock { .button-action--unblock {
background: var(--btn-unblock); background: var(--btn-unblock);
} }