Merge: + Filters: Allow changing Filter Name and URL parameters
Close #971 * commit 'b3bca39de4bebb716606c35f034f84cb6aa12de0': + client: modify added filters * filtering/set_url: allow changing Name and URL parameters
This commit is contained in:
commit
b5f95fefc8
|
@ -1339,8 +1339,12 @@ Request:
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "..."
|
"url": "..."
|
||||||
|
"data": {
|
||||||
|
"name": "..."
|
||||||
|
"url": "..."
|
||||||
"enabled": true | false
|
"enabled": true | false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,9 @@
|
||||||
"enter_url_hint": "Enter URL",
|
"enter_url_hint": "Enter URL",
|
||||||
"check_updates_btn": "Check updates",
|
"check_updates_btn": "Check updates",
|
||||||
"new_filter_btn": "New filter subscription",
|
"new_filter_btn": "New filter subscription",
|
||||||
|
"edit_filter_title": "Edit filter",
|
||||||
"enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.",
|
"enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.",
|
||||||
|
"form_error_url_format": "Invalid url format",
|
||||||
"custom_filter_rules": "Custom filtering rules",
|
"custom_filter_rules": "Custom filtering rules",
|
||||||
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
|
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
|
||||||
"examples_title": "Examples",
|
"examples_title": "Examples",
|
||||||
|
@ -404,6 +406,7 @@
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"answer": "Answer",
|
"answer": "Answer",
|
||||||
"filter_added_successfully": "The filter has been successfully added",
|
"filter_added_successfully": "The filter has been successfully added",
|
||||||
|
"filter_updated": "The filter successfully updated",
|
||||||
"statistics_configuration": "Statistics configuration",
|
"statistics_configuration": "Statistics configuration",
|
||||||
"statistics_retention": "Statistics retention",
|
"statistics_retention": "Statistics retention",
|
||||||
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
|
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
|
||||||
|
|
|
@ -78,10 +78,10 @@ export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST');
|
||||||
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
|
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
|
||||||
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
|
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
|
||||||
|
|
||||||
export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
|
export const toggleFilterStatus = (url, data) => async (dispatch) => {
|
||||||
dispatch(toggleFilterRequest());
|
dispatch(toggleFilterRequest());
|
||||||
try {
|
try {
|
||||||
await apiClient.setFilterUrl({ url, enabled: !enabled });
|
await apiClient.setFilterUrl({ url, data });
|
||||||
dispatch(toggleFilterSuccess(url));
|
dispatch(toggleFilterSuccess(url));
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -90,6 +90,24 @@ export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editFilterRequest = createAction('EDIT_FILTER_REQUEST');
|
||||||
|
export const editFilterFailure = createAction('EDIT_FILTER_FAILURE');
|
||||||
|
export const editFilterSuccess = createAction('EDIT_FILTER_SUCCESS');
|
||||||
|
|
||||||
|
export const editFilter = (url, data) => async (dispatch) => {
|
||||||
|
dispatch(editFilterRequest());
|
||||||
|
try {
|
||||||
|
await apiClient.setFilterUrl({ url, data });
|
||||||
|
dispatch(editFilterSuccess(url));
|
||||||
|
dispatch(toggleFilteringModal());
|
||||||
|
dispatch(addSuccessToast('filter_updated'));
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(editFilterFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
|
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
|
||||||
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
||||||
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Field, reduxForm } from 'redux-form';
|
||||||
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
|
import flow from 'lodash/flow';
|
||||||
|
|
||||||
|
import { renderInputField, required, isValidUrl } from '../../helpers/form';
|
||||||
|
|
||||||
|
const Form = (props) => {
|
||||||
|
const {
|
||||||
|
t,
|
||||||
|
closeModal,
|
||||||
|
handleSubmit,
|
||||||
|
processingAddFilter,
|
||||||
|
processingConfigFilter,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="form__group">
|
||||||
|
<Field
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
component={renderInputField}
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('enter_name_hint')}
|
||||||
|
validate={[required]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form__group">
|
||||||
|
<Field
|
||||||
|
id="url"
|
||||||
|
name="url"
|
||||||
|
type="text"
|
||||||
|
component={renderInputField}
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('enter_url_hint')}
|
||||||
|
validate={[required, isValidUrl]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form__description">
|
||||||
|
<Trans>enter_valid_filter_url</Trans>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={closeModal}
|
||||||
|
>
|
||||||
|
<Trans>cancel_btn</Trans>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success"
|
||||||
|
disabled={processingAddFilter || processingConfigFilter}
|
||||||
|
>
|
||||||
|
<Trans>save_btn</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.propTypes = {
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
closeModal: PropTypes.func.isRequired,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
|
processingConfigFilter: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default flow([
|
||||||
|
withNamespaces(),
|
||||||
|
reduxForm({
|
||||||
|
form: 'filterForm',
|
||||||
|
}),
|
||||||
|
])(Form);
|
|
@ -1,52 +1,28 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from 'react-modal';
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
|
|
||||||
|
import { MODAL_TYPE } from '../../helpers/constants';
|
||||||
|
import Form from './Form';
|
||||||
import '../ui/Modal.css';
|
import '../ui/Modal.css';
|
||||||
|
|
||||||
ReactModal.setAppElement('#root');
|
ReactModal.setAppElement('#root');
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
url: '',
|
|
||||||
name: '',
|
|
||||||
isUrlValid: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Modal extends Component {
|
class Modal extends Component {
|
||||||
state = initialState;
|
|
||||||
|
|
||||||
isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
|
|
||||||
|
|
||||||
handleUrlChange = async (e) => {
|
|
||||||
const { value: url } = e.currentTarget;
|
|
||||||
this.setState(...this.state, { url, isUrlValid: this.isUrlValid(url) });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleNameChange = (e) => {
|
|
||||||
const { value: name } = e.currentTarget;
|
|
||||||
this.setState({ ...this.state, name });
|
|
||||||
};
|
|
||||||
|
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
this.props.toggleModal();
|
this.props.toggleModal();
|
||||||
this.setState({ ...this.state, ...initialState });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isOpen, processingAddFilter } = this.props;
|
const {
|
||||||
const { isUrlValid, url, name } = this.state;
|
isOpen,
|
||||||
const inputUrlClass = classnames({
|
processingAddFilter,
|
||||||
'form-control mb-2': true,
|
processingConfigFilter,
|
||||||
'is-invalid': url.length > 0 && !isUrlValid,
|
handleSubmit,
|
||||||
'is-valid': url.length > 0 && isUrlValid,
|
modalType,
|
||||||
});
|
currentFilterData,
|
||||||
const inputNameClass = classnames({
|
} = this.props;
|
||||||
'form-control mb-2': true,
|
|
||||||
'is-valid': name.length > 0,
|
|
||||||
});
|
|
||||||
const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
|
@ -58,46 +34,23 @@ class Modal extends Component {
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h4 className="modal-title">
|
<h4 className="modal-title">
|
||||||
|
{modalType === MODAL_TYPE.EDIT ? (
|
||||||
|
<Trans>edit_filter_title</Trans>
|
||||||
|
) : (
|
||||||
<Trans>new_filter_btn</Trans>
|
<Trans>new_filter_btn</Trans>
|
||||||
|
)}
|
||||||
</h4>
|
</h4>
|
||||||
<button type="button" className="close" onClick={this.closeModal}>
|
<button type="button" className="close" onClick={this.closeModal}>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<Form
|
||||||
<input
|
initialValues={{ ...currentFilterData }}
|
||||||
type="text"
|
onSubmit={handleSubmit}
|
||||||
className={inputNameClass}
|
processingAddFilter={processingAddFilter}
|
||||||
placeholder={this.props.t('enter_name_hint')}
|
processingConfigFilter={processingConfigFilter}
|
||||||
onChange={this.handleNameChange}
|
closeModal={this.closeModal}
|
||||||
/>
|
/>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className={inputUrlClass}
|
|
||||||
placeholder={this.props.t('enter_url_hint')}
|
|
||||||
onChange={this.handleUrlChange}
|
|
||||||
/>
|
|
||||||
<div className="description">
|
|
||||||
<Trans>enter_valid_filter_url</Trans>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="modal-footer">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary"
|
|
||||||
onClick={this.closeModal}
|
|
||||||
>
|
|
||||||
<Trans>cancel_btn</Trans>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-success"
|
|
||||||
onClick={() => this.props.addFilter(url, name)}
|
|
||||||
disabled={!isValidForSubmit || processingAddFilter}
|
|
||||||
>
|
|
||||||
<Trans>add_filter_btn</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
);
|
);
|
||||||
|
@ -110,6 +63,10 @@ Modal.propTypes = {
|
||||||
addFilter: PropTypes.func.isRequired,
|
addFilter: PropTypes.func.isRequired,
|
||||||
isFilterAdded: PropTypes.bool.isRequired,
|
isFilterAdded: PropTypes.bool.isRequired,
|
||||||
processingAddFilter: PropTypes.bool.isRequired,
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
|
processingConfigFilter: PropTypes.bool.isRequired,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
modalType: PropTypes.string.isRequired,
|
||||||
|
currentFilterData: PropTypes.object.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import CellWrap from '../ui/CellWrap';
|
||||||
import UserRules from './UserRules';
|
import UserRules from './UserRules';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
import { MODAL_TYPE } from '../../helpers/constants';
|
||||||
|
|
||||||
class Filters extends Component {
|
class Filters extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.getFilteringStatus();
|
this.props.getFilteringStatus();
|
||||||
|
@ -22,15 +24,29 @@ class Filters extends Component {
|
||||||
this.props.setRules(this.props.filtering.userRules);
|
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 }) => {
|
renderCheckbox = ({ original }) => {
|
||||||
const { processingConfigFilter } = this.props.filtering;
|
const { processingConfigFilter } = this.props.filtering;
|
||||||
const { url, enabled } = original;
|
const { url, name, enabled } = original;
|
||||||
|
const data = { name, url, enabled: !enabled };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="checkbox__input"
|
className="checkbox__input"
|
||||||
onChange={() => this.props.toggleFilterStatus(url, enabled)}
|
onChange={() => this.props.toggleFilterStatus(url, data)}
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
disabled={processingConfigFilter}
|
disabled={processingConfigFilter}
|
||||||
/>
|
/>
|
||||||
|
@ -46,6 +62,17 @@ class Filters extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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: '' };
|
||||||
|
};
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
Header: <Trans>enabled_table_header</Trans>,
|
Header: <Trans>enabled_table_header</Trans>,
|
||||||
|
@ -94,7 +121,30 @@ class Filters extends Component {
|
||||||
{
|
{
|
||||||
Header: <Trans>actions_table_header</Trans>,
|
Header: <Trans>actions_table_header</Trans>,
|
||||||
accessor: 'url',
|
accessor: 'url',
|
||||||
Cell: ({ value }) => (
|
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||||
|
@ -105,10 +155,9 @@ class Filters extends Component {
|
||||||
<use xlinkHref="#delete" />
|
<use xlinkHref="#delete" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
),
|
</div>
|
||||||
className: 'text-center',
|
);
|
||||||
width: 80,
|
},
|
||||||
sortable: false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -124,9 +173,14 @@ class Filters extends Component {
|
||||||
processingRefreshFilters,
|
processingRefreshFilters,
|
||||||
processingRemoveFilter,
|
processingRemoveFilter,
|
||||||
processingAddFilter,
|
processingAddFilter,
|
||||||
|
processingConfigFilter,
|
||||||
processingFilters,
|
processingFilters,
|
||||||
|
modalType,
|
||||||
|
modalFilterUrl,
|
||||||
} = filtering;
|
} = filtering;
|
||||||
|
|
||||||
|
const currentFilterData = this.getFilter(modalFilterUrl, filters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<PageTitle title={t('filters')} />
|
<PageTitle title={t('filters')} />
|
||||||
|
@ -161,7 +215,9 @@ class Filters extends Component {
|
||||||
<button
|
<button
|
||||||
className="btn btn-success btn-standard mr-2"
|
className="btn btn-success btn-standard mr-2"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={toggleFilteringModal}
|
onClick={() =>
|
||||||
|
toggleFilteringModal({ type: MODAL_TYPE.ADD })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Trans>add_filter_btn</Trans>
|
<Trans>add_filter_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
@ -191,6 +247,10 @@ class Filters extends Component {
|
||||||
addFilter={addFilter}
|
addFilter={addFilter}
|
||||||
isFilterAdded={isFilterAdded}
|
isFilterAdded={isFilterAdded}
|
||||||
processingAddFilter={processingAddFilter}
|
processingAddFilter={processingAddFilter}
|
||||||
|
processingConfigFilter={processingConfigFilter}
|
||||||
|
handleSubmit={this.handleSubmit}
|
||||||
|
modalType={modalType}
|
||||||
|
currentFilterData={currentFilterData}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -210,6 +270,7 @@ Filters.propTypes = {
|
||||||
processingRefreshFilters: PropTypes.bool.isRequired,
|
processingRefreshFilters: PropTypes.bool.isRequired,
|
||||||
processingConfigFilter: PropTypes.bool.isRequired,
|
processingConfigFilter: PropTypes.bool.isRequired,
|
||||||
processingRemoveFilter: PropTypes.bool.isRequired,
|
processingRemoveFilter: PropTypes.bool.isRequired,
|
||||||
|
modalType: PropTypes.string.isRequired,
|
||||||
}),
|
}),
|
||||||
removeFilter: PropTypes.func.isRequired,
|
removeFilter: PropTypes.func.isRequired,
|
||||||
toggleFilterStatus: PropTypes.func.isRequired,
|
toggleFilterStatus: PropTypes.func.isRequired,
|
||||||
|
@ -217,6 +278,7 @@ Filters.propTypes = {
|
||||||
toggleFilteringModal: PropTypes.func.isRequired,
|
toggleFilteringModal: PropTypes.func.isRequired,
|
||||||
handleRulesChange: PropTypes.func.isRequired,
|
handleRulesChange: PropTypes.func.isRequired,
|
||||||
refreshFilters: PropTypes.func.isRequired,
|
refreshFilters: PropTypes.func.isRequired,
|
||||||
|
editFilter: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
toggleFilteringModal,
|
toggleFilteringModal,
|
||||||
refreshFilters,
|
refreshFilters,
|
||||||
handleRulesChange,
|
handleRulesChange,
|
||||||
|
editFilter,
|
||||||
} from '../actions/filtering';
|
} from '../actions/filtering';
|
||||||
import Filters from '../components/Filters';
|
import Filters from '../components/Filters';
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ const mapDispatchToProps = {
|
||||||
toggleFilteringModal,
|
toggleFilteringModal,
|
||||||
refreshFilters,
|
refreshFilters,
|
||||||
handleRulesChange,
|
handleRulesChange,
|
||||||
|
editFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { R_IPV4, R_MAC, R_HOST, R_IPV6, R_CIDR, UNSAFE_PORTS } from '../helpers/constants';
|
import { R_IPV4, R_MAC, R_HOST, R_IPV6, R_CIDR, UNSAFE_PORTS, R_URL_REQUIRES_PROTOCOL } from '../helpers/constants';
|
||||||
import { createOnBlurHandler } from './helpers';
|
import { createOnBlurHandler } from './helpers';
|
||||||
|
|
||||||
export const renderField = (props, elementType) => {
|
export const renderField = (props, elementType) => {
|
||||||
|
@ -270,4 +270,11 @@ export const answer = (value) => {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isValidUrl = (value) => {
|
||||||
|
if (value && !R_URL_REQUIRES_PROTOCOL.test(value)) {
|
||||||
|
return <Trans>form_error_url_format</Trans>;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export const toNumber = value => value && parseInt(value, 10);
|
export const toNumber = value => value && parseInt(value, 10);
|
||||||
|
|
|
@ -37,7 +37,17 @@ const filtering = handleActions(
|
||||||
isFilterAdded: true,
|
isFilterAdded: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[actions.toggleFilteringModal]: (state) => {
|
[actions.toggleFilteringModal]: (state, { payload }) => {
|
||||||
|
if (payload) {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
isModalOpen: !state.isModalOpen,
|
||||||
|
isFilterAdded: false,
|
||||||
|
modalType: payload.type || '',
|
||||||
|
modalFilterUrl: payload.url || '',
|
||||||
|
};
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
const newState = {
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
isModalOpen: !state.isModalOpen,
|
isModalOpen: !state.isModalOpen,
|
||||||
|
@ -50,6 +60,10 @@ const filtering = handleActions(
|
||||||
[actions.toggleFilterFailure]: state => ({ ...state, processingConfigFilter: false }),
|
[actions.toggleFilterFailure]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
[actions.toggleFilterSuccess]: state => ({ ...state, processingConfigFilter: false }),
|
[actions.toggleFilterSuccess]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
|
|
||||||
|
[actions.editFilterRequest]: state => ({ ...state, processingConfigFilter: true }),
|
||||||
|
[actions.editFilterFailure]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
|
[actions.editFilterSuccess]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
|
|
||||||
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
|
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
|
||||||
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
|
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
|
||||||
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
|
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
|
||||||
|
@ -80,6 +94,8 @@ const filtering = handleActions(
|
||||||
userRules: '',
|
userRules: '',
|
||||||
interval: 24,
|
interval: 24,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
modalType: '',
|
||||||
|
modalFilterUrl: '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -137,12 +137,18 @@ func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type filterURLJSON struct {
|
type filterURLJSON struct {
|
||||||
|
Name string `json:"name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filterURLReq struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Data filterURLJSON `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
|
func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
|
||||||
fj := filterURLJSON{}
|
fj := filterURLReq{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&fj)
|
err := json.NewDecoder(r.Body).Decode(&fj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
||||||
|
@ -154,14 +160,34 @@ func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
found := filterEnable(fj.URL, fj.Enabled)
|
f := filter{
|
||||||
if !found {
|
Enabled: fj.Data.Enabled,
|
||||||
|
Name: fj.Data.Name,
|
||||||
|
URL: fj.Data.URL,
|
||||||
|
}
|
||||||
|
status := filterSetProperties(fj.URL, f)
|
||||||
|
if (status & statusFound) == 0 {
|
||||||
http.Error(w, "URL doesn't exist", http.StatusBadRequest)
|
http.Error(w, "URL doesn't exist", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (status & statusURLExists) != 0 {
|
||||||
|
http.Error(w, "URL already exists", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
onConfigModified()
|
onConfigModified()
|
||||||
|
if (status & statusURLChanged) != 0 {
|
||||||
|
if fj.Data.Enabled {
|
||||||
|
// download new filter and apply its rules
|
||||||
|
refreshStatus = 1
|
||||||
|
refreshLock.Lock()
|
||||||
|
_, _ = refreshFiltersIfNecessary(true)
|
||||||
|
refreshLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (status & statusEnabledChanged) != 0 {
|
||||||
enableFilters(true)
|
enableFilters(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
func handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -68,45 +68,80 @@ func userFilter() filter {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable or disable a filter
|
const (
|
||||||
func filterEnable(url string, enable bool) bool {
|
statusFound = 1
|
||||||
r := false
|
statusEnabledChanged = 2
|
||||||
|
statusURLChanged = 4
|
||||||
|
statusURLExists = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update properties for a filter specified by its URL
|
||||||
|
// Return status* flags.
|
||||||
|
func filterSetProperties(url string, newf filter) int {
|
||||||
|
r := 0
|
||||||
config.Lock()
|
config.Lock()
|
||||||
|
defer config.Unlock()
|
||||||
|
|
||||||
for i := range config.Filters {
|
for i := range config.Filters {
|
||||||
filter := &config.Filters[i] // otherwise we will be operating on a copy
|
f := &config.Filters[i]
|
||||||
if filter.URL == url {
|
if f.URL != url {
|
||||||
filter.Enabled = enable
|
continue
|
||||||
if enable {
|
}
|
||||||
e := filter.load()
|
|
||||||
|
log.Debug("filter: set properties: %s: {%s %s %v}",
|
||||||
|
f.URL, newf.Name, newf.URL, newf.Enabled)
|
||||||
|
f.Name = newf.Name
|
||||||
|
|
||||||
|
if f.URL != newf.URL {
|
||||||
|
r |= statusURLChanged
|
||||||
|
if filterExistsNoLock(newf.URL) {
|
||||||
|
return statusURLExists
|
||||||
|
}
|
||||||
|
f.URL = newf.URL
|
||||||
|
f.unload()
|
||||||
|
f.LastUpdated = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Enabled != newf.Enabled {
|
||||||
|
r |= statusEnabledChanged
|
||||||
|
f.Enabled = newf.Enabled
|
||||||
|
if f.Enabled {
|
||||||
|
if (r & statusURLChanged) == 0 {
|
||||||
|
e := f.load()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
// This isn't a fatal error,
|
// This isn't a fatal error,
|
||||||
// because it may occur when someone removes the file from disk.
|
// because it may occur when someone removes the file from disk.
|
||||||
// In this case the periodic update task will try to download the file.
|
// In this case the periodic update task will try to download the file.
|
||||||
filter.LastUpdated = time.Time{}
|
f.LastUpdated = time.Time{}
|
||||||
log.Tracef("%s filter load: %v", url, e)
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
filter.unload()
|
f.unload()
|
||||||
}
|
|
||||||
r = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.Unlock()
|
|
||||||
return r
|
return r | statusFound
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return TRUE if a filter with this URL exists
|
// Return TRUE if a filter with this URL exists
|
||||||
func filterExists(url string) bool {
|
func filterExists(url string) bool {
|
||||||
r := false
|
|
||||||
config.RLock()
|
config.RLock()
|
||||||
|
r := filterExistsNoLock(url)
|
||||||
|
config.RUnlock()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return TRUE if a filter with this URL exists
|
||||||
|
func filterExistsNoLock(url string) bool {
|
||||||
|
r := false
|
||||||
for i := range config.Filters {
|
for i := range config.Filters {
|
||||||
if config.Filters[i].URL == url {
|
if config.Filters[i].URL == url {
|
||||||
r = true
|
r = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.RUnlock()
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue