Merge: fix #822 - Whitelist filter rules
Squashed commit of the following: commit 350c6d5fadd77145b801df8887284bf4d64fbd19 Author: Ildar Kamalov <i.kamalov@adguard.com> Date: Wed Feb 26 15:43:29 2020 +0300 * client: update translations commit a884dffcd59f2259e2eee2c1e5a3270819bf8962 Author: Ildar Kamalov <i.kamalov@adguard.com> Date: Mon Feb 17 17:32:10 2020 +0300 + client: handle whitelist filters commit a586ec5bc614ffb0e01584a1fbdc7292b4865e68 Author: ArtemBaskal <a.baskal@adguard.com> Date: Wed Jan 29 18:16:59 2020 +0300 + client: add whitelist commit a52c3de62cf2fa34be6394771fb8bb56b4ee81e3 Author: Simon Zolin <s.zolin@adguard.com> Date: Thu Feb 20 17:50:44 2020 +0300 * change /filtering/refresh commit 7f8f2ecccb9f7fa65318c1717dc6a7bd61afccf4 Author: Simon Zolin <s.zolin@adguard.com> Date: Thu Feb 20 16:17:07 2020 +0300 * fix race-detector issue commit ac4b64c4a52c5b364a4b154bf18dea0fdf45647f Author: Simon Zolin <s.zolin@adguard.com> Date: Mon Jan 20 20:08:21 2020 +0300 + whitelist filters
This commit is contained in:
@@ -12,7 +12,11 @@ import './index.css';
|
||||
import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Filters from '../../containers/Filters';
|
||||
|
||||
import CustomRules from '../../containers/CustomRules';
|
||||
import DnsBlocklist from '../../containers/DnsBlocklist';
|
||||
import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||
import DnsRewrites from '../../containers/DnsRewrites';
|
||||
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
@@ -108,7 +112,10 @@ class App extends Component {
|
||||
<Route path="/encryption" component={Encryption} />
|
||||
<Route path="/dhcp" component={Dhcp} />
|
||||
<Route path="/clients" component={Clients} />
|
||||
<Route path="/filters" component={Filters} />
|
||||
<Route path="/filters" component={DnsBlocklist} />
|
||||
<Route path="/dns_allowlists" component={DnsAllowlist} />
|
||||
<Route path="/dns_rewrites" component={DnsRewrites} />
|
||||
<Route path="/custom_rules" component={CustomRules} />
|
||||
<Route path="/logs" component={Logs} />
|
||||
<Route path="/guide" component={SetupGuide} />
|
||||
</Fragment>
|
||||
|
||||
39
client/src/components/Filters/Actions.js
Normal file
39
client/src/components/Filters/Actions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
const Actions = ({
|
||||
handleAdd, handleRefresh, processingRefreshFilters, whitelist,
|
||||
}) => (
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standard mr-2 btn-large"
|
||||
type="submit"
|
||||
onClick={handleAdd}
|
||||
>
|
||||
{whitelist ? (
|
||||
<Trans>add_allowlist</Trans>
|
||||
) : (
|
||||
<Trans>add_blocklist</Trans>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-standard"
|
||||
type="submit"
|
||||
onClick={handleRefresh}
|
||||
disabled={processingRefreshFilters}
|
||||
>
|
||||
<Trans>check_updates_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Actions.propTypes = {
|
||||
handleAdd: PropTypes.func.isRequired,
|
||||
handleRefresh: PropTypes.func.isRequired,
|
||||
processingRefreshFilters: PropTypes.bool.isRequired,
|
||||
whitelist: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Actions);
|
||||
|
||||
@@ -14,12 +14,13 @@ import {
|
||||
} from '../../../helpers/helpers';
|
||||
import { FILTERED } from '../../../helpers/constants';
|
||||
|
||||
const getFilterName = (id, filters, t) => {
|
||||
const getFilterName = (id, filters, whitelistFilters, t) => {
|
||||
if (id === 0) {
|
||||
return t('filtered_custom_rules');
|
||||
}
|
||||
|
||||
const filter = filters.find(filter => filter.id === id);
|
||||
const filter = filters.find(filter => filter.id === id)
|
||||
|| whitelistFilters.find(filter => filter.id === id);
|
||||
|
||||
if (filter && filter.name) {
|
||||
return t('query_log_filtered', { filter: filter.name });
|
||||
@@ -43,14 +44,9 @@ const getTitle = (reason, filterName, t, onlyFiltered) => {
|
||||
|
||||
if (checkWhiteList(reason)) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
{t('host_whitelisted')}
|
||||
</div>
|
||||
<div>
|
||||
{filterName}
|
||||
</div>
|
||||
</Fragment>
|
||||
<div>
|
||||
{filterName}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,6 +86,7 @@ const getColor = (reason) => {
|
||||
|
||||
const Info = ({
|
||||
filters,
|
||||
whitelistFilters,
|
||||
hostname,
|
||||
reason,
|
||||
filter_id,
|
||||
@@ -99,7 +96,7 @@ const Info = ({
|
||||
ip_addrs,
|
||||
t,
|
||||
}) => {
|
||||
const filterName = getFilterName(filter_id, filters, t);
|
||||
const filterName = getFilterName(filter_id, filters, whitelistFilters, t);
|
||||
const onlyFiltered = checkSafeSearch(reason)
|
||||
|| checkSafeBrowsing(reason)
|
||||
|| checkParental(reason);
|
||||
@@ -149,6 +146,7 @@ const Info = ({
|
||||
|
||||
Info.propTypes = {
|
||||
filters: PropTypes.array.isRequired,
|
||||
whitelistFilters: PropTypes.array.isRequired,
|
||||
hostname: PropTypes.string.isRequired,
|
||||
reason: PropTypes.string.isRequired,
|
||||
filter_id: PropTypes.number,
|
||||
|
||||
@@ -17,6 +17,7 @@ const Check = (props) => {
|
||||
processing,
|
||||
check,
|
||||
filters,
|
||||
whitelistFilters,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
@@ -62,6 +63,7 @@ const Check = (props) => {
|
||||
<hr/>
|
||||
<Info
|
||||
filters={filters}
|
||||
whitelistFilters={whitelistFilters}
|
||||
hostname={hostname}
|
||||
reason={reason}
|
||||
filter_id={filter_id}
|
||||
@@ -87,6 +89,7 @@ Check.propTypes = {
|
||||
processing: PropTypes.bool.isRequired,
|
||||
check: PropTypes.object.isRequired,
|
||||
filters: PropTypes.array.isRequired,
|
||||
whitelistFilters: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
|
||||
95
client/src/components/Filters/CustomRules.js
Normal file
95
client/src/components/Filters/CustomRules.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Examples from './Examples';
|
||||
import Check from './Check';
|
||||
|
||||
class CustomRules extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.handleRulesChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.handleRulesSubmit();
|
||||
};
|
||||
|
||||
handleRulesChange = (value) => {
|
||||
this.props.handleRulesChange({ userRules: value });
|
||||
};
|
||||
|
||||
handleRulesSubmit = () => {
|
||||
this.props.setRules(this.props.filtering.userRules);
|
||||
};
|
||||
|
||||
handleCheck = (values) => {
|
||||
this.props.checkHost(values);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
filtering: {
|
||||
filters,
|
||||
whitelistFilters,
|
||||
userRules,
|
||||
processingCheck,
|
||||
check,
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('custom_filtering_rules')} />
|
||||
<Card
|
||||
subtitle={t('custom_filter_rules_hint')}
|
||||
>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea
|
||||
className="form-control form-control--textarea-large"
|
||||
value={userRules}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr />
|
||||
<Examples />
|
||||
</Card>
|
||||
<Check
|
||||
filters={filters}
|
||||
whitelistFilters={whitelistFilters}
|
||||
check={check}
|
||||
onSubmit={this.handleCheck}
|
||||
processing={processingCheck}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CustomRules.propTypes = {
|
||||
filtering: PropTypes.object.isRequired,
|
||||
setRules: PropTypes.func.isRequired,
|
||||
checkHost: PropTypes.func.isRequired,
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(CustomRules);
|
||||
130
client/src/components/Filters/DnsAllowlist.js
Normal file
130
client/src/components/Filters/DnsAllowlist.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import Modal from './Modal';
|
||||
import Actions from './Actions';
|
||||
import Table from './Table';
|
||||
|
||||
import { MODAL_TYPE } from '../../helpers/constants';
|
||||
import { getCurrentFilter } from '../../helpers/helpers';
|
||||
|
||||
class DnsAllowlist extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
handleSubmit = (values) => {
|
||||
const { name, url } = values;
|
||||
const { filtering } = this.props;
|
||||
const whitelist = true;
|
||||
|
||||
if (filtering.modalType === MODAL_TYPE.EDIT) {
|
||||
this.props.editFilter(filtering.modalFilterUrl, values, whitelist);
|
||||
} else {
|
||||
this.props.addFilter(url, name, whitelist);
|
||||
}
|
||||
};
|
||||
|
||||
handleDelete = (url) => {
|
||||
if (window.confirm(this.props.t('list_confirm_delete'))) {
|
||||
const whitelist = true;
|
||||
this.props.removeFilter(url, whitelist);
|
||||
}
|
||||
};
|
||||
|
||||
toggleFilter = (url, data) => {
|
||||
const whitelist = true;
|
||||
this.props.toggleFilterStatus(url, data, whitelist);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
toggleFilteringModal,
|
||||
refreshFilters,
|
||||
addFilter,
|
||||
toggleFilterStatus,
|
||||
filtering: {
|
||||
whitelistFilters,
|
||||
isModalOpen,
|
||||
isFilterAdded,
|
||||
processingRefreshFilters,
|
||||
processingRemoveFilter,
|
||||
processingAddFilter,
|
||||
processingConfigFilter,
|
||||
processingFilters,
|
||||
modalType,
|
||||
modalFilterUrl,
|
||||
},
|
||||
} = this.props;
|
||||
const currentFilterData = getCurrentFilter(modalFilterUrl, whitelistFilters);
|
||||
const loading = processingFilters
|
||||
|| processingAddFilter
|
||||
|| processingRemoveFilter
|
||||
|| processingRefreshFilters;
|
||||
const whitelist = true;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle
|
||||
title={t('dns_allowlists')}
|
||||
subtitle={t('dns_allowlists_desc')}
|
||||
/>
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card subtitle={t('filters_and_hosts_hint')}>
|
||||
<Table
|
||||
filters={whitelistFilters}
|
||||
loading={loading}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
toggleFilteringModal={toggleFilteringModal}
|
||||
toggleFilterStatus={toggleFilterStatus}
|
||||
handleDelete={this.handleDelete}
|
||||
toggleFilter={this.toggleFilter}
|
||||
whitelist={whitelist}
|
||||
/>
|
||||
<Actions
|
||||
handleAdd={() => toggleFilteringModal({ type: MODAL_TYPE.ADD })}
|
||||
handleRefresh={refreshFilters}
|
||||
processingRefreshFilters={processingRefreshFilters}
|
||||
whitelist={whitelist}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
toggleModal={toggleFilteringModal}
|
||||
addFilter={addFilter}
|
||||
isFilterAdded={isFilterAdded}
|
||||
processingAddFilter={processingAddFilter}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
handleSubmit={this.handleSubmit}
|
||||
modalType={modalType}
|
||||
currentFilterData={currentFilterData}
|
||||
whitelist={whitelist}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DnsAllowlist.propTypes = {
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
filtering: PropTypes.object.isRequired,
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
refreshFilters: PropTypes.func.isRequired,
|
||||
editFilter: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(DnsAllowlist);
|
||||
121
client/src/components/Filters/DnsBlocklist.js
Normal file
121
client/src/components/Filters/DnsBlocklist.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import Modal from './Modal';
|
||||
import Actions from './Actions';
|
||||
import Table from './Table';
|
||||
|
||||
import { MODAL_TYPE } from '../../helpers/constants';
|
||||
import { getCurrentFilter } from '../../helpers/helpers';
|
||||
|
||||
class DnsBlocklist extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
handleSubmit = (values) => {
|
||||
const { name, url } = values;
|
||||
const { filtering } = this.props;
|
||||
|
||||
if (filtering.modalType === MODAL_TYPE.EDIT) {
|
||||
this.props.editFilter(filtering.modalFilterUrl, values);
|
||||
} else {
|
||||
this.props.addFilter(url, name);
|
||||
}
|
||||
};
|
||||
|
||||
handleDelete = (url) => {
|
||||
if (window.confirm(this.props.t('list_confirm_delete'))) {
|
||||
this.props.removeFilter(url);
|
||||
}
|
||||
};
|
||||
|
||||
toggleFilter = (url, data) => {
|
||||
this.props.toggleFilterStatus(url, data);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
toggleFilteringModal,
|
||||
refreshFilters,
|
||||
addFilter,
|
||||
filtering: {
|
||||
filters,
|
||||
isModalOpen,
|
||||
isFilterAdded,
|
||||
processingRefreshFilters,
|
||||
processingRemoveFilter,
|
||||
processingAddFilter,
|
||||
processingConfigFilter,
|
||||
processingFilters,
|
||||
modalType,
|
||||
modalFilterUrl,
|
||||
},
|
||||
} = this.props;
|
||||
const currentFilterData = getCurrentFilter(modalFilterUrl, filters);
|
||||
const loading = processingFilters
|
||||
|| processingAddFilter
|
||||
|| processingRemoveFilter
|
||||
|| processingRefreshFilters;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle
|
||||
title={t('dns_blocklists')}
|
||||
subtitle={t('dns_blocklists_desc')}
|
||||
/>
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card subtitle={t('filters_and_hosts_hint')}>
|
||||
<Table
|
||||
filters={filters}
|
||||
loading={loading}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
toggleFilteringModal={toggleFilteringModal}
|
||||
handleDelete={this.handleDelete}
|
||||
toggleFilter={this.toggleFilter}
|
||||
/>
|
||||
<Actions
|
||||
handleAdd={() => toggleFilteringModal({ type: MODAL_TYPE.ADD })}
|
||||
handleRefresh={refreshFilters}
|
||||
processingRefreshFilters={processingRefreshFilters}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
toggleModal={toggleFilteringModal}
|
||||
addFilter={addFilter}
|
||||
isFilterAdded={isFilterAdded}
|
||||
processingAddFilter={processingAddFilter}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
handleSubmit={this.handleSubmit}
|
||||
modalType={modalType}
|
||||
currentFilterData={currentFilterData}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DnsBlocklist.propTypes = {
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
filtering: PropTypes.object.isRequired,
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
refreshFilters: PropTypes.func.isRequired,
|
||||
editFilter: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(DnsBlocklist);
|
||||
54
client/src/components/Filters/Examples.js
Normal file
54
client/src/components/Filters/Examples.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
const Examples = () => (
|
||||
<Fragment>
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> –
|
||||
<Trans>example_meaning_filter_block</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> –
|
||||
<Trans>example_meaning_filter_whitelist</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>127.0.0.1 example.org</code> –
|
||||
<Trans>example_meaning_host_block</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code><Trans>example_comment</Trans></code> –
|
||||
<Trans>example_comment_meaning</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code><Trans>example_comment_hash</Trans></code> –
|
||||
<Trans>example_comment_meaning</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>/REGEX/</code> –
|
||||
<Trans>example_regex_meaning</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p className="mt-1">
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
filtering_rules_learn_more
|
||||
</Trans>
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default withNamespaces()(Examples);
|
||||
@@ -13,6 +13,7 @@ const Form = (props) => {
|
||||
handleSubmit,
|
||||
processingAddFilter,
|
||||
processingConfigFilter,
|
||||
whitelist,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -41,7 +42,11 @@ const Form = (props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="form__description">
|
||||
<Trans>enter_valid_filter_url</Trans>
|
||||
{whitelist ? (
|
||||
<Trans>enter_valid_allowlist</Trans>
|
||||
) : (
|
||||
<Trans>enter_valid_blocklist</Trans>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
@@ -70,6 +75,7 @@ Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
processingAddFilter: PropTypes.bool.isRequired,
|
||||
processingConfigFilter: PropTypes.bool.isRequired,
|
||||
whitelist: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
|
||||
@@ -22,8 +22,21 @@ class Modal extends Component {
|
||||
handleSubmit,
|
||||
modalType,
|
||||
currentFilterData,
|
||||
whitelist,
|
||||
} = this.props;
|
||||
|
||||
const newListTitle = whitelist ? (
|
||||
<Trans>new_allowlist</Trans>
|
||||
) : (
|
||||
<Trans>new_blocklist</Trans>
|
||||
);
|
||||
|
||||
const editListTitle = whitelist ? (
|
||||
<Trans>edit_allowlist</Trans>
|
||||
) : (
|
||||
<Trans>edit_blocklist</Trans>
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
@@ -35,9 +48,9 @@ class Modal extends Component {
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{modalType === MODAL_TYPE.EDIT ? (
|
||||
<Trans>edit_filter_title</Trans>
|
||||
editListTitle
|
||||
) : (
|
||||
<Trans>new_filter_btn</Trans>
|
||||
newListTitle
|
||||
)}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={this.closeModal}>
|
||||
@@ -50,6 +63,7 @@ class Modal extends Component {
|
||||
processingAddFilter={processingAddFilter}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
closeModal={this.closeModal}
|
||||
whitelist={whitelist}
|
||||
/>
|
||||
</div>
|
||||
</ReactModal>
|
||||
@@ -68,6 +82,7 @@ Modal.propTypes = {
|
||||
modalType: PropTypes.string.isRequired,
|
||||
currentFilterData: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
whitelist: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Modal);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { renderInputField, required, domain, answer } from '../../../../helpers/form';
|
||||
import { renderInputField, required, domain, answer } from '../../../helpers/form';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
92
client/src/components/Filters/Rewrites/index.js
Normal file
92
client/src/components/Filters/Rewrites/index.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Table from './Table';
|
||||
import Modal from './Modal';
|
||||
import Card from '../../ui/Card';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
|
||||
class Rewrites extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getRewritesList();
|
||||
}
|
||||
|
||||
handleSubmit = (values) => {
|
||||
this.props.addRewrite(values);
|
||||
};
|
||||
|
||||
handleDelete = (values) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
|
||||
this.props.deleteRewrite(values);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
rewrites,
|
||||
toggleRewritesModal,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
list,
|
||||
isModalOpen,
|
||||
processing,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
} = rewrites;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle
|
||||
title={t('dns_rewrites')}
|
||||
subtitle={t('rewrite_desc')}
|
||||
/>
|
||||
<Card
|
||||
id="rewrites"
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Fragment>
|
||||
<Table
|
||||
list={list}
|
||||
processing={processing}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
handleDelete={this.handleDelete}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleRewritesModal()}
|
||||
disabled={processingAdd}
|
||||
>
|
||||
<Trans>rewrite_add</Trans>
|
||||
</button>
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
/>
|
||||
</Fragment>
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Rewrites.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
getRewritesList: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Rewrites);
|
||||
157
client/src/components/Filters/Table.js
Normal file
157
client/src/components/Filters/Table.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
import CellWrap from '../ui/CellWrap';
|
||||
|
||||
import { MODAL_TYPE } from '../../helpers/constants';
|
||||
import { formatDetailedDateTime } from '../../helpers/helpers';
|
||||
|
||||
class Table extends Component {
|
||||
getDateCell = row => CellWrap(row, formatDetailedDateTime);
|
||||
|
||||
renderCheckbox = ({ original }) => {
|
||||
const { processingConfigFilter, toggleFilter } = this.props;
|
||||
const { url, name, enabled } = original;
|
||||
const data = { name, url, enabled: !enabled };
|
||||
|
||||
return (
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
onChange={() => toggleFilter(url, data)}
|
||||
checked={enabled}
|
||||
disabled={processingConfigFilter}
|
||||
/>
|
||||
<span className="checkbox__label" />
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
columns = [
|
||||
{
|
||||
Header: <Trans>enabled_table_header</Trans>,
|
||||
accessor: 'enabled',
|
||||
Cell: this.renderCheckbox,
|
||||
width: 90,
|
||||
className: 'text-center',
|
||||
},
|
||||
{
|
||||
Header: <Trans>name_table_header</Trans>,
|
||||
accessor: 'name',
|
||||
minWidth: 200,
|
||||
Cell: CellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>list_url_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
minWidth: 200,
|
||||
Cell: ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link logs__text"
|
||||
>
|
||||
{value}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: <Trans>rules_count_table_header</Trans>,
|
||||
accessor: 'rulesCount',
|
||||
className: 'text-center',
|
||||
minWidth: 100,
|
||||
Cell: props => props.value.toLocaleString(),
|
||||
},
|
||||
{
|
||||
Header: <Trans>last_time_updated_table_header</Trans>,
|
||||
accessor: 'lastUpdated',
|
||||
className: 'text-center',
|
||||
minWidth: 150,
|
||||
Cell: this.getDateCell,
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
className: 'text-center',
|
||||
width: 100,
|
||||
sortable: false,
|
||||
Cell: (row) => {
|
||||
const { value } = row;
|
||||
const { t, toggleFilteringModal, handleDelete } = this.props;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
title={t('edit_table_action')}
|
||||
onClick={() =>
|
||||
toggleFilteringModal({
|
||||
type: MODAL_TYPE.EDIT,
|
||||
url: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#edit" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => handleDelete(value)}
|
||||
title={t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading, filters, t, whitelist,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
data={filters}
|
||||
columns={this.columns}
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
loading={loading}
|
||||
minRows={6}
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={whitelist ? t('no_whitelist_added') : t('no_blocklist_added')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
filters: PropTypes.array.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
processingConfigFilter: PropTypes.bool.isRequired,
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
toggleFilter: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
whitelist: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Table);
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
class UserRules extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleRulesChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleRulesSubmit();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, userRules } = this.props;
|
||||
return (
|
||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea
|
||||
className="form-control form-control--textarea-large"
|
||||
value={userRules}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr />
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> –
|
||||
<Trans>example_meaning_filter_block</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> –
|
||||
<Trans>example_meaning_filter_whitelist</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>127.0.0.1 example.org</code> –
|
||||
<Trans>example_meaning_host_block</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code><Trans>example_comment</Trans></code> –
|
||||
<Trans>example_comment_meaning</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code><Trans>example_comment_hash</Trans></code> –
|
||||
<Trans>example_comment_meaning</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>/REGEX/</code> –
|
||||
<Trans>example_regex_meaning</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p className="mt-1">
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
filtering_rules_learn_more
|
||||
</Trans>
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserRules.propTypes = {
|
||||
userRules: PropTypes.string.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
handleRulesSubmit: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(UserRules);
|
||||
@@ -1,305 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import CellWrap from '../ui/CellWrap';
|
||||
import UserRules from './UserRules';
|
||||
import Modal from './Modal';
|
||||
import Check from './Check';
|
||||
|
||||
import { formatDetailedDateTime } from '../../helpers/helpers';
|
||||
import { MODAL_TYPE } from '../../helpers/constants';
|
||||
|
||||
class Filters extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
handleRulesChange = (value) => {
|
||||
this.props.handleRulesChange({ userRules: value });
|
||||
};
|
||||
|
||||
handleRulesSubmit = () => {
|
||||
this.props.setRules(this.props.filtering.userRules);
|
||||
};
|
||||
|
||||
handleSubmit = (values) => {
|
||||
const { name, url } = values;
|
||||
const { filtering } = this.props;
|
||||
|
||||
if (filtering.modalType === MODAL_TYPE.EDIT) {
|
||||
const data = { ...values };
|
||||
this.props.editFilter(filtering.modalFilterUrl, data);
|
||||
} else {
|
||||
this.props.addFilter(url, name);
|
||||
}
|
||||
}
|
||||
|
||||
renderCheckbox = ({ original }) => {
|
||||
const { processingConfigFilter } = this.props.filtering;
|
||||
const { url, name, enabled } = original;
|
||||
const data = { name, url, enabled: !enabled };
|
||||
|
||||
return (
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
onChange={() => this.props.toggleFilterStatus(url, data)}
|
||||
checked={enabled}
|
||||
disabled={processingConfigFilter}
|
||||
/>
|
||||
<span className="checkbox__label" />
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
handleDelete = (url) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
||||
this.props.removeFilter({ url });
|
||||
}
|
||||
};
|
||||
|
||||
getDateCell = row => CellWrap(row, formatDetailedDateTime);
|
||||
|
||||
getFilter = (url, filters) => {
|
||||
const filter = filters.find(item => url === item.url);
|
||||
|
||||
if (filter) {
|
||||
const { enabled, name, url } = filter;
|
||||
return { enabled, name, url };
|
||||
}
|
||||
|
||||
return { name: '', url: '' };
|
||||
};
|
||||
|
||||
handleCheck = (values) => {
|
||||
this.props.checkHost(values);
|
||||
}
|
||||
|
||||
columns = [
|
||||
{
|
||||
Header: <Trans>enabled_table_header</Trans>,
|
||||
accessor: 'enabled',
|
||||
Cell: this.renderCheckbox,
|
||||
width: 90,
|
||||
className: 'text-center',
|
||||
},
|
||||
{
|
||||
Header: <Trans>name_table_header</Trans>,
|
||||
accessor: 'name',
|
||||
minWidth: 200,
|
||||
Cell: CellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>filter_url_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
minWidth: 200,
|
||||
Cell: ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link logs__text"
|
||||
>
|
||||
{value}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: <Trans>rules_count_table_header</Trans>,
|
||||
accessor: 'rulesCount',
|
||||
className: 'text-center',
|
||||
minWidth: 100,
|
||||
Cell: props => props.value.toLocaleString(),
|
||||
},
|
||||
{
|
||||
Header: <Trans>last_time_updated_table_header</Trans>,
|
||||
accessor: 'lastUpdated',
|
||||
className: 'text-center',
|
||||
minWidth: 150,
|
||||
Cell: this.getDateCell,
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
className: 'text-center',
|
||||
width: 100,
|
||||
sortable: false,
|
||||
Cell: (row) => {
|
||||
const { value } = row;
|
||||
const { t, toggleFilteringModal } = this.props;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
title={t('edit_table_action')}
|
||||
onClick={() =>
|
||||
toggleFilteringModal({
|
||||
type: MODAL_TYPE.EDIT,
|
||||
url: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#edit" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => this.handleDelete(value)}
|
||||
title={this.props.t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const {
|
||||
filtering, t, toggleFilteringModal, refreshFilters, addFilter,
|
||||
} = this.props;
|
||||
const {
|
||||
filters,
|
||||
userRules,
|
||||
isModalOpen,
|
||||
isFilterAdded,
|
||||
processingRefreshFilters,
|
||||
processingRemoveFilter,
|
||||
processingAddFilter,
|
||||
processingConfigFilter,
|
||||
processingFilters,
|
||||
modalType,
|
||||
modalFilterUrl,
|
||||
processingCheck,
|
||||
check,
|
||||
} = filtering;
|
||||
|
||||
const currentFilterData = this.getFilter(modalFilterUrl, filters);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('filters')} />
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card
|
||||
title={t('filters_and_hosts')}
|
||||
subtitle={t('filters_and_hosts_hint')}
|
||||
>
|
||||
<ReactTable
|
||||
data={filters}
|
||||
columns={this.columns}
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
loading={
|
||||
processingFilters ||
|
||||
processingAddFilter ||
|
||||
processingRemoveFilter ||
|
||||
processingRefreshFilters
|
||||
}
|
||||
minRows={4}
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('no_filters_added')}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standard mr-2 btn-large"
|
||||
type="submit"
|
||||
onClick={() =>
|
||||
toggleFilteringModal({ type: MODAL_TYPE.ADD })
|
||||
}
|
||||
>
|
||||
<Trans>add_filter_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-standard"
|
||||
type="submit"
|
||||
onClick={refreshFilters}
|
||||
disabled={processingRefreshFilters}
|
||||
>
|
||||
<Trans>check_updates_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="col-md-12">
|
||||
<UserRules
|
||||
userRules={userRules}
|
||||
handleRulesChange={this.handleRulesChange}
|
||||
handleRulesSubmit={this.handleRulesSubmit}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-12">
|
||||
<Check
|
||||
filters={filters}
|
||||
check={check}
|
||||
onSubmit={this.handleCheck}
|
||||
processing={processingCheck}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
toggleModal={toggleFilteringModal}
|
||||
addFilter={addFilter}
|
||||
isFilterAdded={isFilterAdded}
|
||||
processingAddFilter={processingAddFilter}
|
||||
processingConfigFilter={processingConfigFilter}
|
||||
handleSubmit={this.handleSubmit}
|
||||
modalType={modalType}
|
||||
currentFilterData={currentFilterData}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Filters.propTypes = {
|
||||
setRules: PropTypes.func,
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
filtering: PropTypes.shape({
|
||||
userRules: PropTypes.string.isRequired,
|
||||
filters: PropTypes.array.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
isFilterAdded: PropTypes.bool.isRequired,
|
||||
processingFilters: PropTypes.bool.isRequired,
|
||||
processingAddFilter: PropTypes.bool.isRequired,
|
||||
processingRefreshFilters: PropTypes.bool.isRequired,
|
||||
processingConfigFilter: PropTypes.bool.isRequired,
|
||||
processingRemoveFilter: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
processingCheck: PropTypes.bool.isRequired,
|
||||
}),
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
refreshFilters: PropTypes.func.isRequired,
|
||||
editFilter: PropTypes.func.isRequired,
|
||||
checkHost: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Filters);
|
||||
@@ -5,33 +5,39 @@ import enhanceWithClickOutside from 'react-click-outside';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { SETTINGS_URLS } from '../../helpers/constants';
|
||||
import { SETTINGS_URLS, FILTERS_URLS, MENU_URLS } from '../../helpers/constants';
|
||||
import Dropdown from '../ui/Dropdown';
|
||||
|
||||
const MENU_ITEMS = [
|
||||
{
|
||||
route: '', exact: true, xlinkHref: 'dashboard', text: 'dashboard', order: 0,
|
||||
route: MENU_URLS.root, exact: true, icon: 'dashboard', text: 'dashboard', order: 0,
|
||||
},
|
||||
|
||||
// Settings dropdown should have visual order 1
|
||||
|
||||
// Filters dropdown should have visual order 2
|
||||
|
||||
{
|
||||
route: 'filters', xlinkHref: 'filters', text: 'filters', order: 2,
|
||||
route: MENU_URLS.logs, icon: 'log', text: 'query_log', order: 3,
|
||||
},
|
||||
{
|
||||
route: 'logs', xlinkHref: 'log', text: 'query_log', order: 3,
|
||||
},
|
||||
{
|
||||
route: 'guide', xlinkHref: 'setup', text: 'setup_guide', order: 4,
|
||||
route: MENU_URLS.guide, icon: 'setup', text: 'setup_guide', order: 4,
|
||||
},
|
||||
];
|
||||
|
||||
const DROPDOWN_ITEMS = [
|
||||
{ route: 'settings', text: 'general_settings' },
|
||||
{ route: 'dns', text: 'dns_settings' },
|
||||
{ route: 'encryption', text: 'encryption_settings' },
|
||||
{ route: 'clients', text: 'client_settings' },
|
||||
{ route: 'dhcp', text: 'dhcp_settings' },
|
||||
const SETTINGS_ITEMS = [
|
||||
{ route: SETTINGS_URLS.settings, text: 'general_settings' },
|
||||
{ route: SETTINGS_URLS.dns, text: 'dns_settings' },
|
||||
{ route: SETTINGS_URLS.encryption, text: 'encryption_settings' },
|
||||
{ route: SETTINGS_URLS.clients, text: 'client_settings' },
|
||||
{ route: SETTINGS_URLS.dhcp, text: 'dhcp_settings' },
|
||||
];
|
||||
|
||||
const FILTERS_ITEMS = [
|
||||
{ route: FILTERS_URLS.dns_blocklists, text: 'dns_blocklists' },
|
||||
{ route: FILTERS_URLS.dns_allowlists, text: 'dns_allowlists' },
|
||||
{ route: FILTERS_URLS.dns_rewrites, text: 'dns_rewrites' },
|
||||
{ route: FILTERS_URLS.custom_rules, text: 'custom_filtering_rules' },
|
||||
];
|
||||
|
||||
class Menu extends Component {
|
||||
@@ -43,50 +49,82 @@ class Menu extends Component {
|
||||
this.props.toggleMenuOpen();
|
||||
};
|
||||
|
||||
getActiveClassForSettings = () => {
|
||||
getActiveClassForDropdown = (URLS) => {
|
||||
const { pathname } = this.props.location;
|
||||
const isSettingsPage = SETTINGS_URLS.some(item => item === pathname);
|
||||
const isActivePage = Object.values(URLS).some(item => item === pathname);
|
||||
|
||||
return isSettingsPage ? 'active' : '';
|
||||
return isActivePage ? 'active' : '';
|
||||
};
|
||||
|
||||
getNavLink = ({
|
||||
route, exact, text, order, className, icon,
|
||||
}) => (
|
||||
<NavLink
|
||||
to={route}
|
||||
key={route}
|
||||
exact={exact || false}
|
||||
className={`order-${order} ${className}`}
|
||||
onClick={this.toggleMenu}
|
||||
>
|
||||
{icon && (
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref={`#${icon}`} />
|
||||
</svg>
|
||||
)}
|
||||
<Trans>{text}</Trans>
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
getDropdown = ({
|
||||
label, order, URLS, icon, ITEMS,
|
||||
}) =>
|
||||
(
|
||||
<Dropdown
|
||||
label={this.props.t(label)}
|
||||
baseClassName={`dropdown nav-item order-${order}`}
|
||||
controlClassName={`nav-link ${this.getActiveClassForDropdown(URLS)}`}
|
||||
icon={icon}>
|
||||
{ITEMS.map(item => (
|
||||
this.getNavLink({
|
||||
...item,
|
||||
order,
|
||||
className: 'dropdown-item',
|
||||
})))}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
render() {
|
||||
const menuClass = classnames({
|
||||
'header__column mobile-menu': true,
|
||||
'mobile-menu--active': this.props.isMenuOpen,
|
||||
});
|
||||
|
||||
const dropdownControlClass = `nav-link ${this.getActiveClassForSettings()}`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={menuClass}>
|
||||
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
|
||||
{MENU_ITEMS.map(({
|
||||
route, text, exact, xlinkHref, order,
|
||||
}) => (
|
||||
<li className={`nav-item order-${order}`} key={text} onClick={this.toggleMenu}>
|
||||
<NavLink to={`/${route}`} exact={exact || false} className="nav-link">
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref={`#${xlinkHref}`} />
|
||||
</svg>
|
||||
<Trans>{text}</Trans>
|
||||
</NavLink>
|
||||
{MENU_ITEMS.map(item => (
|
||||
<li
|
||||
className={`nav-item order-${item.order}`}
|
||||
key={item.text}
|
||||
onClick={this.toggleMenu}
|
||||
>
|
||||
{this.getNavLink({ ...item, className: 'nav-link' })}
|
||||
</li>
|
||||
))}
|
||||
<Dropdown
|
||||
label={this.props.t('settings')}
|
||||
baseClassName="dropdown nav-item order-1"
|
||||
controlClassName={dropdownControlClass}
|
||||
icon="settings"
|
||||
>
|
||||
{DROPDOWN_ITEMS.map(({ route, text }) => (
|
||||
<NavLink to={`/${route}`} className="dropdown-item" key={text}
|
||||
onClick={this.toggleMenu}>
|
||||
<Trans>{text}</Trans>
|
||||
</NavLink>
|
||||
))}
|
||||
</Dropdown>
|
||||
{this.getDropdown({
|
||||
order: 1,
|
||||
label: 'settings',
|
||||
icon: 'settings',
|
||||
URLS: SETTINGS_URLS,
|
||||
ITEMS: SETTINGS_ITEMS,
|
||||
})}
|
||||
{this.getDropdown({
|
||||
order: 2,
|
||||
label: 'filters',
|
||||
icon: 'filters',
|
||||
URLS: FILTERS_URLS,
|
||||
ITEMS: FILTERS_ITEMS,
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
||||
@@ -140,12 +140,13 @@ class Logs extends Component {
|
||||
})
|
||||
);
|
||||
|
||||
getFilterName = (filters, filterId, t) => {
|
||||
getFilterName = (filters, whitelistFilters, filterId, t) => {
|
||||
if (filterId === CUSTOM_FILTERING_RULES_ID) {
|
||||
return t('custom_filter_rules');
|
||||
}
|
||||
|
||||
const filter = filters.find(filter => filter.id === filterId);
|
||||
const filter = filters.find(filter => filter.id === filterId)
|
||||
|| whitelistFilters.find(filter => filter.id === filterId);
|
||||
let filterName = '';
|
||||
|
||||
if (filter) {
|
||||
@@ -164,7 +165,7 @@ class Logs extends Component {
|
||||
reason, filterId, rule, status, originalAnswer,
|
||||
} = original;
|
||||
const { t, filtering } = this.props;
|
||||
const { filters } = filtering;
|
||||
const { filters, whitelistFilters } = filtering;
|
||||
|
||||
const isFiltered = checkFiltered(reason);
|
||||
const isBlackList = checkBlackList(reason);
|
||||
@@ -177,7 +178,7 @@ class Logs extends Component {
|
||||
const parsedFilteredReason = t('query_log_filtered', { filter: filterKey });
|
||||
const currentService = SERVICES.find(service => service.id === original.serviceName);
|
||||
const serviceName = currentService && currentService.name;
|
||||
const filterName = this.getFilterName(filters, filterId, t);
|
||||
const filterName = this.getFilterName(filters, whitelistFilters, filterId, t);
|
||||
|
||||
if (isBlockedCnameIp) {
|
||||
const normalizedAnswer = this.normalizeResponse(originalAnswer);
|
||||
@@ -188,6 +189,7 @@ class Logs extends Component {
|
||||
<span className="logs__text">
|
||||
<Trans>blocked_by_response</Trans>
|
||||
</span>
|
||||
{this.renderTooltip(isFiltered, rule, filterName)}
|
||||
</div>
|
||||
<div className="logs__list-wrap">
|
||||
{this.renderResponseList(normalizedAnswer, status)}
|
||||
@@ -242,7 +244,7 @@ class Logs extends Component {
|
||||
</div>
|
||||
{isRewrite ? (
|
||||
<div className="logs__action">
|
||||
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
|
||||
<Link to="/dns_rewrites" className="btn btn-sm btn-outline-primary">
|
||||
<Trans>configure</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Table from './Table';
|
||||
import Modal from './Modal';
|
||||
import Card from '../../../ui/Card';
|
||||
|
||||
class Rewrites extends Component {
|
||||
handleSubmit = (values) => {
|
||||
this.props.addRewrite(values);
|
||||
};
|
||||
|
||||
handleDelete = (values) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
|
||||
this.props.deleteRewrite(values);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
rewrites,
|
||||
toggleRewritesModal,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
list,
|
||||
isModalOpen,
|
||||
processing,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
} = rewrites;
|
||||
|
||||
return (
|
||||
<Card
|
||||
id="rewrites"
|
||||
title={t('dns_rewrites')}
|
||||
subtitle={t('rewrite_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Fragment>
|
||||
<Table
|
||||
list={list}
|
||||
processing={processing}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
handleDelete={this.handleDelete}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleRewritesModal()}
|
||||
disabled={processingAdd}
|
||||
>
|
||||
<Trans>rewrite_add</Trans>
|
||||
</button>
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
/>
|
||||
</Fragment>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Rewrites.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
getRewritesList: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Rewrites);
|
||||
@@ -4,7 +4,6 @@ import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Upstream from './Upstream';
|
||||
import Access from './Access';
|
||||
import Rewrites from './Rewrites';
|
||||
import Config from './Config';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
@@ -13,7 +12,6 @@ class Dns extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsSettings();
|
||||
this.props.getAccessList();
|
||||
this.props.getRewritesList();
|
||||
this.props.getDnsConfig();
|
||||
}
|
||||
|
||||
@@ -23,25 +21,18 @@ class Dns extends Component {
|
||||
dashboard,
|
||||
settings,
|
||||
access,
|
||||
rewrites,
|
||||
setAccessList,
|
||||
testUpstream,
|
||||
setUpstream,
|
||||
getRewritesList,
|
||||
addRewrite,
|
||||
deleteRewrite,
|
||||
toggleRewritesModal,
|
||||
dnsConfig,
|
||||
setDnsConfig,
|
||||
} = this.props;
|
||||
|
||||
const isDataLoading = dashboard.processingDnsSettings
|
||||
|| access.processing
|
||||
|| rewrites.processing
|
||||
|| dnsConfig.processingGetConfig;
|
||||
const isDataReady = !dashboard.processingDnsSettings
|
||||
&& !access.processing
|
||||
&& !rewrites.processing
|
||||
&& !dnsConfig.processingGetConfig;
|
||||
|
||||
return (
|
||||
@@ -64,13 +55,6 @@ class Dns extends Component {
|
||||
testUpstream={testUpstream}
|
||||
/>
|
||||
<Access access={access} setAccessList={setAccessList} />
|
||||
<Rewrites
|
||||
rewrites={rewrites}
|
||||
getRewritesList={getRewritesList}
|
||||
addRewrite={addRewrite}
|
||||
deleteRewrite={deleteRewrite}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
@@ -86,11 +70,6 @@ Dns.propTypes = {
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
access: PropTypes.object.isRequired,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
getRewritesList: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
getDnsSettings: PropTypes.func.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
|
||||
@@ -3,28 +3,36 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import './Card.css';
|
||||
|
||||
const Card = props => (
|
||||
<div className={props.type ? `card ${props.type}` : 'card'} id={props.id ? props.id : ''}>
|
||||
{props.title &&
|
||||
<div className="card-header with-border">
|
||||
<div className="card-inner">
|
||||
<div className="card-title">
|
||||
{props.title}
|
||||
const Card = ({
|
||||
type, id, title, subtitle, refresh, bodyType, children,
|
||||
}) => (
|
||||
<div className={type ? `card ${type}` : 'card'} id={id || ''}>
|
||||
{(title || subtitle) && (
|
||||
<div className="card-header with-border">
|
||||
<div className="card-inner">
|
||||
{title && (
|
||||
<div className="card-title">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subtitle && (
|
||||
<div
|
||||
className="card-subtitle"
|
||||
dangerouslySetInnerHTML={{ __html: subtitle }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.subtitle &&
|
||||
<div className="card-subtitle" dangerouslySetInnerHTML={{ __html: props.subtitle }} />
|
||||
}
|
||||
{refresh && (
|
||||
<div className="card-options">
|
||||
{refresh}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.refresh &&
|
||||
<div className="card-options">
|
||||
{props.refresh}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
<div className={props.bodyType ? props.bodyType : 'card-body'}>
|
||||
{props.children}
|
||||
)}
|
||||
<div className={bodyType || 'card-body'}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin-left: 0.7rem;
|
||||
margin-left: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
.page-title__actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,13 @@ const PageTitle = props => (
|
||||
<div className="page-header">
|
||||
<h1 className="page-title">
|
||||
{props.title}
|
||||
{props.subtitle && <span className="page-subtitle">{props.subtitle}</span>}
|
||||
{props.children}
|
||||
</h1>
|
||||
{props.subtitle && (
|
||||
<div className="page-subtitle">
|
||||
{props.subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class PopoverFilter extends Component {
|
||||
)}
|
||||
{filter && (
|
||||
<div className="popover__list-item popover__list-item--nowrap">
|
||||
<Trans>filter_label</Trans>: <strong>{filter}</strong>
|
||||
<Trans>list_label</Trans>: <strong>{filter}</strong>
|
||||
</div>
|
||||
)}
|
||||
{service && (
|
||||
|
||||
@@ -10143,6 +10143,7 @@ body.fixed-header .page {
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin: 1.5rem 0 1.5rem;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user