+ client: handle check host

This commit is contained in:
Ildar Kamalov 2020-01-22 17:25:50 +03:00 committed by Simon Zolin
parent 0d7c01d50f
commit 8ec7c37715
12 changed files with 318 additions and 4 deletions

View File

@ -446,5 +446,17 @@
"autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuardHome by default.",
"tags_title": "Tags",
"tags_desc": "You can select the tags that correspond to the client. Tags can be included in the filtering rules and allow you to apply them more accurately. <0>Learn more</0>",
"form_select_tags": "Select client tags"
"form_select_tags": "Select client tags",
"check_title": "Check the filtering",
"check_desc": "Check if the host name is filtered",
"check": "Check",
"form_enter_host": "Enter a host name",
"filtered_custom_rules": "Filtered by Custom filtering rules",
"host_whitelisted": "The host is whitelisted",
"check_ip": "IP addresses: {{ip}}",
"check_cname": "CNAME: {{cname}}",
"check_reason": "Reason: {{reason}}",
"check_rule": "Rule: {{rule}}",
"check_service": "Service name: {{service}}",
"check_not_found": "Doesn't exist in any filter"
}

View File

@ -161,3 +161,23 @@ export const setFiltersConfig = config => async (dispatch, getState) => {
dispatch(setFiltersConfigFailure());
}
};
export const checkHostRequest = createAction('CHECK_HOST_REQUEST');
export const checkHostFailure = createAction('CHECK_HOST_FAILURE');
export const checkHostSuccess = createAction('CHECK_HOST_SUCCESS');
export const checkHost = host => async (dispatch) => {
dispatch(checkHostRequest());
try {
const data = await apiClient.checkHost(host);
const [hostname] = Object.values(host);
dispatch(checkHostSuccess({
hostname,
...data,
}));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(checkHostFailure());
}
};

View File

@ -82,6 +82,7 @@ class Api {
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' };
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' };
getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS;
@ -141,6 +142,12 @@ class Api {
return this.makeRequest(path, method, parameters);
}
checkHost(params) {
const { path, method } = this.FILTERING_CHECK_HOST;
const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method);
}
// Parental
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };

View File

@ -0,0 +1,127 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import { checkFiltered, checkRewrite, checkBlackList, checkNotFilteredNotFound, checkWhiteList } from '../../../helpers/helpers';
const getFilterName = (id, filters, t) => {
if (id === 0) {
return t('filtered_custom_rules');
}
const filter = filters.find(filter => filter.id === id);
if (filter && filter.name) {
return t('query_log_filtered', { filter: filter.name });
}
return '';
};
const getTitle = (reason, filterName, t) => {
if (checkNotFilteredNotFound(reason)) {
return t('check_not_found');
}
if (checkRewrite(reason)) {
return t('rewrite_applied');
}
if (checkBlackList(reason)) {
return filterName;
}
if (checkWhiteList(reason)) {
return (
<Fragment>
<div>
{t('host_whitelisted')}
</div>
<div>
{filterName}
</div>
</Fragment>
);
}
return (
<Fragment>
<div>
{t('check_reason', { reason })}
</div>
<div>
{filterName}
</div>
</Fragment>
);
};
const getColor = (reason) => {
if (checkFiltered(reason)) {
return 'red';
} else if (checkRewrite(reason)) {
return 'blue';
} else if (checkWhiteList(reason)) {
return 'green';
}
return '';
};
const Info = ({
filters,
hostname,
reason,
filter_id,
rule,
service_name,
cname,
ip_addrs,
t,
}) => {
const filterName = getFilterName(filter_id, filters, t);
const title = getTitle(reason, filterName, t);
const color = getColor(reason);
return (
<div className={`card mb-0 p-3 ${color}`}>
<div>
<strong>{hostname}</strong>
</div>
<div>{title}</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 = {
filters: 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 withNamespaces()(Info);

View File

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

View File

@ -26,7 +26,7 @@ class UserRules extends Component {
/>
<div className="card-actions">
<button
className="btn btn-success btn-standard"
className="btn btn-success btn-standard btn-large"
type="submit"
onClick={this.handleSubmit}
>

View File

@ -8,8 +8,9 @@ import Card from '../ui/Card';
import CellWrap from '../ui/CellWrap';
import UserRules from './UserRules';
import Modal from './Modal';
import { formatDetailedDateTime } from '../../helpers/helpers';
import Check from './Check';
import { formatDetailedDateTime } from '../../helpers/helpers';
import { MODAL_TYPE } from '../../helpers/constants';
class Filters extends Component {
@ -76,6 +77,10 @@ class Filters extends Component {
return { name: '', url: '' };
};
handleCheck = (values) => {
this.props.checkHost(values);
}
columns = [
{
Header: <Trans>enabled_table_header</Trans>,
@ -180,6 +185,8 @@ class Filters extends Component {
processingFilters,
modalType,
modalFilterUrl,
processingCheck,
check,
} = filtering;
const currentFilterData = this.getFilter(modalFilterUrl, filters);
@ -216,7 +223,7 @@ class Filters extends Component {
/>
<div className="card-actions">
<button
className="btn btn-success btn-standard mr-2"
className="btn btn-success btn-standard mr-2 btn-large"
type="submit"
onClick={() =>
toggleFilteringModal({ type: MODAL_TYPE.ADD })
@ -242,6 +249,14 @@ class Filters extends Component {
handleRulesSubmit={this.handleRulesSubmit}
/>
</div>
<div className="col-md-12">
<Check
filters={filters}
check={check}
onSubmit={this.handleCheck}
processing={processingCheck}
/>
</div>
</div>
</div>
<Modal
@ -274,6 +289,7 @@ Filters.propTypes = {
processingConfigFilter: PropTypes.bool.isRequired,
processingRemoveFilter: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
processingCheck: PropTypes.bool.isRequired,
}),
removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired,
@ -282,6 +298,7 @@ Filters.propTypes = {
handleRulesChange: PropTypes.func.isRequired,
refreshFilters: PropTypes.func.isRequired,
editFilter: PropTypes.func.isRequired,
checkHost: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};

View File

@ -112,3 +112,15 @@
font-size: 14px;
}
}
.card .red {
background-color: #fff4f2;
}
.card .green {
background-color: #f1faf3;
}
.card .blue {
background-color: #ecf7ff;
}

View File

@ -9,6 +9,7 @@ import {
refreshFilters,
handleRulesChange,
editFilter,
checkHost,
} from '../actions/filtering';
import Filters from '../components/Filters';
@ -28,6 +29,7 @@ const mapDispatchToProps = {
refreshFilters,
handleRulesChange,
editFilter,
checkHost,
};
export default connect(

View File

@ -349,10 +349,14 @@ export const ENCRYPTION_SOURCE = {
export const FILTERED_STATUS = {
FILTERED_BLACK_LIST: 'FilteredBlackList',
NOT_FILTERED_WHITE_LIST: 'NotFilteredWhiteList',
NOT_FILTERED_NOT_FOUND: 'NotFilteredNotFound',
FILTERED_BLOCKED_SERVICE: 'FilteredBlockedService',
REWRITE: 'Rewrite',
};
export const FILTERED = 'Filtered';
export const NOT_FILTERED = 'NotFiltered';
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];

View File

@ -22,6 +22,8 @@ import {
DEFAULT_DATE_FORMAT_OPTIONS,
DETAILED_DATE_FORMAT_OPTIONS,
DEFAULT_LANGUAGE,
FILTERED_STATUS,
FILTERED,
} from './constants';
/**
@ -418,3 +420,9 @@ export const createOnBlurHandler = (event, input, normalizeOnBlur) => (
normalizeOnBlur
? input.onBlur(normalizeOnBlur(event.target.value))
: input.onBlur());
export const checkFiltered = reason => reason.indexOf(FILTERED) === 0;
export const checkRewrite = reason => reason === FILTERED_STATUS.REWRITE;
export const checkBlackList = reason => reason === FILTERED_STATUS.FILTERED_BLACK_LIST;
export const checkWhiteList = reason => reason === FILTERED_STATUS.NOT_FILTERED_WHITE_LIST;
export const checkNotFilteredNotFound = reason => reason === FILTERED_STATUS.NOT_FILTERED_NOT_FOUND;

View File

@ -79,6 +79,14 @@ const filtering = handleActions(
...payload,
processingSetConfig: false,
}),
[actions.checkHostRequest]: state => ({ ...state, processingCheck: true }),
[actions.checkHostFailure]: state => ({ ...state, processingCheck: false }),
[actions.checkHostSuccess]: (state, { payload }) => ({
...state,
check: payload,
processingCheck: false,
}),
},
{
isModalOpen: false,
@ -89,6 +97,7 @@ const filtering = handleActions(
processingConfigFilter: false,
processingRemoveFilter: false,
processingSetConfig: false,
processingCheck: false,
isFilterAdded: false,
filters: [],
userRules: '',
@ -96,6 +105,7 @@ const filtering = handleActions(
enabled: true,
modalType: '',
modalFilterUrl: '',
check: {},
},
);