+ client: handle filters configuration

This commit is contained in:
Ildar Kamalov 2019-09-12 16:19:35 +03:00 committed by Simon Zolin
parent 57bb04685f
commit d0fc1dc54d
25 changed files with 745 additions and 460 deletions

View File

@ -378,5 +378,11 @@
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
"statistics_clear": " Clear statistics",
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
"statistics_cleared": "Statistics successfully cleared"
"statistics_cleared": "Statistics successfully cleared",
"interval_hours": "{{count}} hour",
"interval_hours_plural": "{{count}} hours",
"filters_configuration": "Filters configuration",
"filters_enable": "Enable filters",
"filters_interval": "Filters update interval",
"disabled": "Disabled"
}

View File

@ -1,10 +1,9 @@
{
"client_settings": "Настройки клиентов",
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
"upstream_parallel": "Использовать одновременные запроссы ко всем серверам для ускорения обработки запроса",
"upstream_parallel": "Использовать одновременные запросы ко всем серверам для ускорения обработки запроса",
"bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
"url_added_successfully": "URL успешно добавлен",
"check_dhcp_servers": "Проверить DHCP-серверы",
"save_config": "Сохранить конфигурацию",
"enabled_dhcp": "DHCP-сервер включен",
@ -67,7 +66,6 @@
"disabled_protection": "Защита выкл.",
"refresh_statics": "Обновить статистику",
"dns_query": "DNS-запросы",
"blocked_by": "Заблокировано фильтрами",
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
"stats_adult": "Заблокированные \"взрослые\" сайты",
"stats_query_domain": "Часто запрашиваемые домены",
@ -78,7 +76,6 @@
"top_clients": "Частые клиенты",
"no_clients_found": "Клиентов не найдено",
"general_statistics": "Общая статистика",
"number_of_dns_query_24_hours": "Количество DNS-запросов за 24 часа",
"number_of_dns_query_blocked_24_hours": "Количество DNS-запросов, заблокированных фильтрами и блок-списками",
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "Количество заблокированных \"сайтов для взрослых\"",
@ -211,7 +208,7 @@
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы \"DNS\" рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
"install_devices_windows_list_1": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
"install_devices_windows_list_2": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
"install_devices_windows_list_2": "Перейдите в \"Сеть и интернет\", а затем в \"Центр управления сетями и общим доступом\"",
"install_devices_windows_list_3": "В левой стороне экрана найдите \"Изменение параметров адаптера\" и кликните по нему.",
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
@ -298,7 +295,6 @@
"client_deleted": "Клиент \"{{key}}\" успешно удален",
"client_added": "Клиент \"{{key}}\" успешно добавлен",
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
"table_statistics": "Количество запросов (последние 24 часа)",
"clients_not_found": "Клиентов не найдено",
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
@ -309,7 +305,7 @@
"access_allowed_title": "Разрешенные клиенты",
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
"access_disallowed_title": "Запрещенные клиенты",
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроек, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
"access_blocked_title": "Заблокированные домены",
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
"access_settings_saved": "Настройки доступа успешно сохранены",
@ -353,5 +349,13 @@
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
"blocked_service": "Заблокированный сервис",
"block_all": "Заблокировать все",
"unblock_all": "Разблокировать все"
"unblock_all": "Разблокировать все",
"domain": "Домен",
"answer": "Ответ",
"interval_hours_0": "{{count}} час",
"interval_hours_1": "{{count}} часа",
"interval_hours_2": "{{count}} часов",
"interval_days_0": "{{count}} день",
"interval_days_1": "{{count}} дня",
"interval_days_2": "{{count}} дней"
}

View File

@ -0,0 +1,145 @@
import { createAction } from 'redux-actions';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import { normalizeFilteringStatus, normalizeRulesTextarea } from '../helpers/helpers';
import { addErrorToast, addSuccessToast } from './index';
import apiClient from '../api/Api';
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
export const handleRulesChange = createAction('HANDLE_RULES_CHANGE');
export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQUEST');
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
export const getFilteringStatus = () => async (dispatch) => {
dispatch(getFilteringStatusRequest());
try {
const status = await apiClient.getFilteringStatus();
dispatch(getFilteringStatusSuccess({ ...normalizeFilteringStatus(status) }));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getFilteringStatusFailure());
}
};
export const setRulesRequest = createAction('SET_RULES_REQUEST');
export const setRulesFailure = createAction('SET_RULES_FAILURE');
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
export const setRules = rules => async (dispatch) => {
dispatch(setRulesRequest());
try {
const normalizedRules = normalizeRulesTextarea(rules);
await apiClient.setRules(normalizedRules);
dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setRulesFailure());
}
};
export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
export const addFilter = (url, name) => async (dispatch) => {
dispatch(addFilterRequest());
try {
await apiClient.addFilter(url, name);
dispatch(addFilterSuccess(url));
dispatch(toggleFilteringModal());
dispatch(addSuccessToast('filter_added_successfully'));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(addFilterFailure());
}
};
export const removeFilterRequest = createAction('REMOVE_FILTER_REQUEST');
export const removeFilterFailure = createAction('REMOVE_FILTER_FAILURE');
export const removeFilterSuccess = createAction('REMOVE_FILTER_SUCCESS');
export const removeFilter = url => async (dispatch) => {
dispatch(removeFilterRequest());
try {
await apiClient.removeFilter(url);
dispatch(removeFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(removeFilterFailure());
}
};
export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST');
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
dispatch(toggleFilterRequest());
try {
await apiClient.setFilterUrl({ url, enabled: !enabled });
dispatch(toggleFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleFilterFailure());
}
};
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
export const refreshFilters = () => async (dispatch) => {
dispatch(refreshFiltersRequest());
dispatch(showLoading());
try {
const refreshText = await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess());
if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) {
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
} else {
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
}
} else {
dispatch(addErrorToast({ error: refreshText }));
}
dispatch(getFilteringStatus());
dispatch(hideLoading());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(refreshFiltersFailure());
dispatch(hideLoading());
}
};
export const setFiltersConfigRequest = createAction('SET_FILTERS_CONFIG_REQUEST');
export const setFiltersConfigFailure = createAction('SET_FILTERS_CONFIG_FAILURE');
export const setFiltersConfigSuccess = createAction('SET_FILTERS_CONFIG_SUCCESS');
export const setFiltersConfig = config => async (dispatch, getState) => {
dispatch(setFiltersConfigRequest());
try {
const { enabled } = config;
const prevEnabled = getState().filtering.enabled;
let successToastMessage = 'config_successfully_saved';
if (prevEnabled !== enabled) {
successToastMessage = enabled ? 'enabled_filtering_toast' : 'disabled_filtering_toast';
}
await apiClient.setFiltersConfig(config);
dispatch(addSuccessToast(successToastMessage));
dispatch(setFiltersConfigSuccess(config));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setFiltersConfigFailure());
}
};

View File

@ -1,10 +1,9 @@
import { createAction } from 'redux-actions';
import { t } from 'i18next';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import axios from 'axios';
import versionCompare from '../helpers/versionCompare';
import { normalizeFilteringStatus, normalizeTextarea, sortClients } from '../helpers/helpers';
import { normalizeTextarea, sortClients } from '../helpers/helpers';
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
import { getTlsStatus } from './encryption';
import apiClient from '../api/Api';
@ -21,16 +20,6 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
let successMessage = '';
try {
switch (settingKey) {
case SETTINGS_NAMES.filtering:
if (status) {
successMessage = 'disabled_filtering_toast';
await apiClient.disableFiltering();
} else {
successMessage = 'enabled_filtering_toast';
await apiClient.enableFiltering();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case SETTINGS_NAMES.safebrowsing:
if (status) {
successMessage = 'disabled_safe_browsing_toast';
@ -77,18 +66,15 @@ export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS');
export const initSettings = settingsList => async (dispatch) => {
dispatch(initSettingsRequest());
try {
const filteringStatus = await apiClient.getFilteringStatus();
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
const parentalStatus = await apiClient.getParentalStatus();
const safesearchStatus = await apiClient.getSafesearchStatus();
const {
filtering,
safebrowsing,
parental,
safesearch,
} = settingsList;
const newSettingsList = {
filtering: { ...filtering, enabled: filteringStatus.enabled },
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
parental: { ...parental, enabled: parentalStatus.enabled },
safesearch: { ...safesearch, enabled: safesearchStatus.enabled },
@ -100,21 +86,6 @@ export const initSettings = settingsList => async (dispatch) => {
}
};
export const getFilteringRequest = createAction('GET_FILTERING_REQUEST');
export const getFilteringFailure = createAction('GET_FILTERING_FAILURE');
export const getFilteringSuccess = createAction('GET_FILTERING_SUCCESS');
export const getFiltering = () => async (dispatch) => {
dispatch(getFilteringRequest());
try {
const filteringStatus = await apiClient.getFilteringStatus();
dispatch(getFilteringSuccess(filteringStatus.enabled));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getFilteringFailure());
}
};
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
@ -290,133 +261,6 @@ export const disableDns = () => async (dispatch) => {
}
};
export const setRulesRequest = createAction('SET_RULES_REQUEST');
export const setRulesFailure = createAction('SET_RULES_FAILURE');
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
export const setRules = rules => async (dispatch) => {
dispatch(setRulesRequest());
try {
const replacedLineEndings = rules
.replace(/^\n/g, '')
.replace(/\n\s*\n/g, '\n');
await apiClient.setRules(replacedLineEndings);
dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setRulesFailure());
}
};
export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQUEST');
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
export const getFilteringStatus = () => async (dispatch) => {
dispatch(getFilteringStatusRequest());
try {
const status = await apiClient.getFilteringStatus();
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getFilteringStatusFailure());
}
};
export const toggleFilterRequest = createAction('FILTER_ENABLE_REQUEST');
export const toggleFilterFailure = createAction('FILTER_ENABLE_FAILURE');
export const toggleFilterSuccess = createAction('FILTER_ENABLE_SUCCESS');
export const toggleFilterStatus = url => async (dispatch, getState) => {
dispatch(toggleFilterRequest());
const state = getState();
const { filters } = state.filtering;
const filter = filters.filter(filter => filter.url === url)[0];
const { enabled } = filter;
let toggleStatusMethod;
if (enabled) {
toggleStatusMethod = apiClient.disableFilter.bind(apiClient);
} else {
toggleStatusMethod = apiClient.enableFilter.bind(apiClient);
}
try {
await toggleStatusMethod(url);
dispatch(toggleFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleFilterFailure());
}
};
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
export const refreshFilters = () => async (dispatch) => {
dispatch(refreshFiltersRequest());
dispatch(showLoading());
try {
const refreshText = await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess());
if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) {
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
} else {
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
}
} else {
dispatch(addErrorToast({ error: refreshText }));
}
dispatch(getFilteringStatus());
dispatch(hideLoading());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(refreshFiltersFailure());
dispatch(hideLoading());
}
};
export const handleRulesChange = createAction('HANDLE_RULES_CHANGE');
export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
export const addFilter = (url, name) => async (dispatch) => {
dispatch(addFilterRequest());
try {
await apiClient.addFilter(url, name);
dispatch(addFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(addFilterFailure());
}
};
export const removeFilterRequest = createAction('ADD_FILTER_REQUEST');
export const removeFilterFailure = createAction('ADD_FILTER_FAILURE');
export const removeFilterSuccess = createAction('ADD_FILTER_SUCCESS');
export const removeFilter = url => async (dispatch) => {
dispatch(removeFilterRequest());
try {
await apiClient.removeFilter(url);
dispatch(removeFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(removeFilterFailure());
}
};
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');

View File

@ -90,32 +90,19 @@ class Api {
}
// Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
FILTERING_INFO = { path: 'filtering_info', method: 'GET' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
FILTERING_CONFIG = { path: 'filtering_config', method: 'POST' };
getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS;
const { path, method } = this.FILTERING_INFO;
return this.makeRequest(path, method);
}
enableFiltering() {
const { path, method } = this.FILTERING_ENABLE;
return this.makeRequest(path, method);
}
disableFiltering() {
const { path, method } = this.FILTERING_DISABLE;
return this.makeRequest(path, method);
}
// TODO find out when to use force parameter
refreshFilters() {
const { path, method } = this.FILTERING_REFRESH;
return this.makeRequest(path, method);
@ -151,26 +138,22 @@ class Api {
return this.makeRequest(path, method, parameters);
}
enableFilter(url) {
const { path, method } = this.FILTERING_ENABLE_FILTER;
const parameter = 'url';
const requestBody = `${parameter}=${url}`;
const config = {
data: requestBody,
header: { 'Content-Type': 'text/plain' },
setFiltersConfig(config) {
const { path, method } = this.FILTERING_CONFIG;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
return this.makeRequest(path, method, parameters);
}
disableFilter(url) {
const { path, method } = this.FILTERING_DISABLE_FILTER;
const parameter = 'url';
const requestBody = `${parameter}=${url}`;
const config = {
data: requestBody,
header: { 'Content-Type': 'text/plain' },
setFilterUrl(config) {
const { path, method } = this.FILTERING_SET_URL;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
return this.makeRequest(path, method, parameters);
}
// Parental

View File

@ -33,27 +33,13 @@ class Modal extends Component {
this.setState({ ...this.state, name });
};
handleNext = () => {
this.props.addFilter(this.state.url, this.state.name);
setTimeout(() => {
if (this.props.isFilterAdded) {
this.closeModal();
}
}, 2000);
};
closeModal = () => {
this.props.toggleModal();
this.setState({ ...this.state, ...initialState });
}
};
render() {
const {
isOpen,
title,
inputDescription,
processingAddFilter,
} = this.props;
const { isOpen, processingAddFilter } = this.props;
const { isUrlValid, url, name } = this.state;
const inputUrlClass = classnames({
'form-control mb-2': true,
@ -64,28 +50,7 @@ class Modal extends Component {
'form-control mb-2': true,
'is-valid': name.length > 0,
});
const renderBody = () => {
if (!this.props.isFilterAdded) {
return (
<React.Fragment>
<input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
<input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
{inputDescription &&
<div className="description">
{inputDescription}
</div>}
</React.Fragment>
);
}
return (
<div className="description">
<Trans>filter_added_successfully</Trans>
</div>
);
};
const isValidForSubmit = !(url.length > 0 && isUrlValid && name.length > 0);
const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0;
return (
<ReactModal
@ -96,35 +61,47 @@ class Modal extends Component {
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{title}
</h4>
<button type="button" className="close" onClick={this.closeModal}>
<span className="sr-only">Close</span>
</button>
<h4 className="modal-title">
<Trans>new_filter_btn</Trans>
</h4>
<button type="button" className="close" onClick={this.closeModal}>
<span className="sr-only">Close</span>
</button>
</div>
<div className="modal-body">
{renderBody()}
</div>
{!this.props.isFilterAdded &&
<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.handleNext}
disabled={isValidForSubmit || processingAddFilter}
>
<Trans>add_filter_btn</Trans>
</button>
<input
type="text"
className={inputNameClass}
placeholder={this.props.t('enter_name_hint')}
onChange={this.handleNameChange}
/>
<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>
</ReactModal>
);
@ -134,12 +111,10 @@ class Modal extends Component {
Modal.propTypes = {
toggleModal: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
inputDescription: PropTypes.string,
addFilter: PropTypes.func.isRequired,
isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
t: PropTypes.func,
isFilterAdded: PropTypes.bool.isRequired,
processingAddFilter: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Modal);

View File

@ -15,13 +15,13 @@ class UserRules extends Component {
};
render() {
const { t } = this.props;
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={this.props.userRules}
value={userRules}
onChange={this.handleChange}
/>
<div className="card-actions">
@ -79,10 +79,10 @@ class UserRules extends Component {
}
UserRules.propTypes = {
userRules: PropTypes.string,
handleRulesChange: PropTypes.func,
handleRulesSubmit: PropTypes.func,
t: PropTypes.func,
userRules: PropTypes.string.isRequired,
handleRulesChange: PropTypes.func.isRequired,
handleRulesSubmit: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(UserRules);

View File

@ -1,11 +1,13 @@
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Modal from './Modal';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import CellWrap from '../ui/CellWrap';
import UserRules from './UserRules';
import Modal from './Modal';
class Filters extends Component {
componentDidMount() {
@ -20,14 +22,19 @@ class Filters extends Component {
this.props.setRules(this.props.filtering.userRules);
};
renderCheckbox = (row) => {
const { url } = row.original;
const { filters } = this.props.filtering;
const filter = filters.filter(filter => filter.url === url)[0];
renderCheckbox = ({ original }) => {
const { processingConfigFilter } = this.props.filtering;
const { url, enabled } = original;
return (
<label className="checkbox">
<input type="checkbox" className="checkbox__input" onChange={() => this.props.toggleFilterStatus(filter.url)} checked={filter.enabled}/>
<span className="checkbox__label"/>
<input
type="checkbox"
className="checkbox__input"
onChange={() => this.props.toggleFilterStatus(url, enabled)}
checked={enabled}
disabled={processingConfigFilter}
/>
<span className="checkbox__label" />
</label>
);
};
@ -37,92 +44,131 @@ class Filters extends Component {
if (window.confirm(this.props.t('filter_confirm_delete'))) {
this.props.removeFilter({ url });
}
}
};
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',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
}, {
Header: <Trans>filter_url_table_header</Trans>,
accessor: 'url',
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',
Cell: props => props.value.toLocaleString(),
}, {
Header: <Trans>last_time_updated_table_header</Trans>,
accessor: 'lastUpdated',
className: 'text-center',
}, {
Header: <Trans>actions_table_header</Trans>,
accessor: 'url',
Cell: ({ value }) => (
<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>
),
className: 'text-center',
width: 80,
sortable: false,
},
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: CellWrap,
},
{
Header: <Trans>actions_table_header</Trans>,
accessor: 'url',
Cell: ({ value }) => (
<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>
),
className: 'text-center',
width: 80,
sortable: false,
},
];
render() {
const { t } = this.props;
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
const {
filtering, t, toggleFilteringModal, refreshFilters, addFilter,
} = this.props;
const {
filters,
userRules,
isModalOpen,
isFilterAdded,
processingRefreshFilters,
processingRemoveFilter,
processingAddFilter,
processingFilters,
} = filtering;
return (
<div>
<PageTitle title={ t('filters') } />
<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') }
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}
// Text
previousText={ t('previous_btn') }
nextText={ t('next_btn') }
loadingText={ t('loading_table_status') }
pageText={ t('page_table_footer_text') }
ofText={ t('of_table_footer_text') }
rowsText={ t('rows_table_footer_text') }
noDataText={ t('no_filters_added') }
previousText={t('previous_btn')}
nextText={t('next_btn')}
loadingText={t('loading_table_status')}
pageText={t('page_table_footer_text')}
ofText={t('of_table_footer_text')}
rowsText={t('rows_table_footer_text')}
noDataText={t('no_filters_added')}
/>
<div className="card-actions">
<button
className="btn btn-success btn-standard mr-2"
type="submit"
onClick={this.props.toggleFilteringModal}
onClick={toggleFilteringModal}
>
<Trans>add_filter_btn</Trans>
</button>
<button
className="btn btn-primary btn-standard"
type="submit"
onClick={this.props.refreshFilters}
onClick={refreshFilters}
disabled={processingRefreshFilters}
>
<Trans>check_updates_btn</Trans>
@ -140,15 +186,13 @@ class Filters extends Component {
</div>
</div>
<Modal
isOpen={this.props.filtering.isFilteringModalOpen}
toggleModal={this.props.toggleFilteringModal}
addFilter={this.props.addFilter}
isFilterAdded={this.props.filtering.isFilterAdded}
processingAddFilter={this.props.filtering.processingAddFilter}
title={ t('new_filter_btn') }
inputDescription={ t('enter_valid_filter_url') }
isOpen={isModalOpen}
toggleModal={toggleFilteringModal}
addFilter={addFilter}
isFilterAdded={isFilterAdded}
processingAddFilter={processingAddFilter}
/>
</div>
</Fragment>
);
}
}
@ -157,12 +201,15 @@ Filters.propTypes = {
setRules: PropTypes.func,
getFilteringStatus: PropTypes.func.isRequired,
filtering: PropTypes.shape({
userRules: PropTypes.string,
filters: PropTypes.array,
isFilteringModalOpen: PropTypes.bool.isRequired,
isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
processingRefreshFilters: PropTypes.bool,
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,
}),
removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired,
@ -170,8 +217,7 @@ Filters.propTypes = {
toggleFilteringModal: PropTypes.func.isRequired,
handleRulesChange: PropTypes.func.isRequired,
refreshFilters: PropTypes.func.isRequired,
t: PropTypes.func,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Filters);

View File

@ -36,6 +36,10 @@
overflow: hidden;
}
.logs__text--full {
width: 100%;
}
.logs__row .tooltip-custom {
top: 0;
margin-left: 0;

View File

@ -6,7 +6,7 @@ import endsWith from 'lodash/endsWith';
import { Trans, withNamespaces } from 'react-i18next';
import { HashLink as Link } from 'react-router-hash-link';
import { formatTime, getClientName } from '../../helpers/helpers';
import { formatTime, formatDateTime, getClientName } from '../../helpers/helpers';
import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
import { getTrackerData } from '../../helpers/trackers/trackers';
import PageTitle from '../ui/PageTitle';
@ -114,7 +114,7 @@ class Logs extends Component {
getTimeCell = ({ value }) => (
<div className="logs__row">
<span className="logs__text" title={value}>
<span className="logs__text" title={formatDateTime(value)}>
{formatTime(value)}
</span>
</div>
@ -227,7 +227,7 @@ class Logs extends Component {
{
Header: t('time_table_header'),
accessor: 'time',
maxWidth: 90,
maxWidth: 100,
filterable: false,
Cell: this.getTimeCell,
},

View File

@ -0,0 +1,88 @@
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 { renderSelectField, toNumber } from '../../../helpers/form';
import { FILTERS_INTERVALS_HOURS } from '../../../helpers/constants';
const getTitleForInterval = (interval, t) => {
if (interval === 0) {
return t('disabled');
} else if (interval === 72 || interval === 168) {
return t('interval_days', { count: interval / 24 });
}
return t('interval_hours', { count: interval });
};
const getIntervalSelect = (processing, t, handleChange, toNumber) => (
<Field
name="interval"
className="custom-select"
component="select"
onChange={handleChange}
normalize={toNumber}
disabled={processing}
>
{FILTERS_INTERVALS_HOURS.map(interval => (
<option value={interval} key={interval}>
{getTitleForInterval(interval, t)}
</option>
))}
</Field>
);
const Form = (props) => {
const {
handleSubmit, handleChange, processing, t,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="enabled"
type="checkbox"
modifier="checkbox--settings"
component={renderSelectField}
placeholder={t('block_domain_use_filters_and_hosts')}
subtitle={t('filters_block_toggle_hint')}
onChange={handleChange}
disabled={processing}
/>
</div>
</div>
<div className="col-12 col-md-5">
<div className="form__group form__group--inner mb-5">
<label className="form__label">
<Trans>filters_interval</Trans>
</label>
{getIntervalSelect(processing, t, handleChange, toNumber)}
</div>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({
form: 'filterConfigForm',
}),
])(Form);

View File

@ -0,0 +1,36 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import debounce from 'lodash/debounce';
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
import Form from './Form';
class FiltersConfig extends Component {
handleFormChange = debounce((values) => {
this.props.setFiltersConfig(values);
}, DEBOUNCE_TIMEOUT);
render() {
const { interval, enabled, processing } = this.props;
return (
<Form
initialValues={{ interval, enabled }}
onSubmit={this.handleFormChange}
onChange={this.handleFormChange}
processing={processing}
/>
);
}
}
FiltersConfig.propTypes = {
interval: PropTypes.number.isRequired,
enabled: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
setFiltersConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(FiltersConfig);

View File

@ -11,6 +11,17 @@
margin-bottom: 20px;
}
.form__group--inner {
max-width: 300px;
margin-top: -10px;
margin-left: 40px;
font-size: 14px;
}
.form__group--checkbox {
margin-bottom: 25px;
}
.form__inline {
display: flex;
justify-content: flex-start;
@ -109,3 +120,11 @@
.custom-control-label:before {
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
}
.custom-select:disabled {
background-color: #f9f9f9;
}
.custom-select {
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
}

View File

@ -5,6 +5,7 @@ import { withNamespaces } from 'react-i18next';
import Services from './Services';
import StatsConfig from './StatsConfig';
import LogsConfig from './LogsConfig';
import FiltersConfig from './FiltersConfig';
import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle';
@ -14,11 +15,6 @@ import './Settings.css';
class Settings extends Component {
settings = {
filtering: {
enabled: false,
title: 'block_domain_use_filters_and_hosts',
subtitle: 'filters_block_toggle_hint',
},
safebrowsing: {
enabled: false,
title: 'use_adguard_browsing_sec',
@ -41,17 +37,20 @@ class Settings extends Component {
this.props.getBlockedServices();
this.props.getStatsConfig();
this.props.getLogsConfig();
this.props.getFilteringStatus();
}
renderSettings = (settings) => {
if (Object.keys(settings).length > 0) {
return Object.keys(settings).map((key) => {
const settingsKeys = Object.keys(settings);
if (settingsKeys.length > 0) {
return settingsKeys.map((key) => {
const setting = settings[key];
const { enabled } = setting;
return (
<Checkbox
key={key}
{...settings[key]}
key={key}
handleChange={() => this.props.toggleSetting(key, enabled)}
/>
);
@ -71,6 +70,8 @@ class Settings extends Component {
queryLogs,
setLogsConfig,
clearLogs,
filtering,
setFiltersConfig,
t,
} = this.props;
@ -90,6 +91,12 @@ class Settings extends Component {
<div className="col-md-12">
<Card bodyType="card-body box-body--settings">
<div className="form">
<FiltersConfig
interval={filtering.interval}
enabled={filtering.enabled}
processing={filtering.processingSetConfig}
setFiltersConfig={setFiltersConfig}
/>
{this.renderSettings(settings.settingsList)}
</div>
</Card>
@ -134,6 +141,8 @@ Settings.propTypes = {
getStatsConfig: PropTypes.func.isRequired,
setStatsConfig: PropTypes.func.isRequired,
resetStats: PropTypes.func.isRequired,
setFiltersConfig: PropTypes.func.isRequired,
getFilteringStatus: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
const CellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text logs__text--full" title={value}>
{value}
</span>
</div>
);
CellWrap.propTypes = {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
};
export default CellWrap;

View File

@ -18,7 +18,7 @@
}
.checkbox--settings .checkbox__label-title {
margin-bottom: 5px;
margin-bottom: 2px;
font-weight: 600;
}
@ -53,7 +53,7 @@
background-position: center center;
background-size: 12px 10px;
border-radius: 3px;
transition: 0.3s ease box-shadow;
transition: 0.3s ease-in-out box-shadow, 0.3s ease-in-out opacity;
}
.checkbox__label .checkbox__label-text {
@ -82,10 +82,13 @@
}
.checkbox__input:disabled + .checkbox__label {
opacity: 0.6;
cursor: default;
}
.checkbox__input:disabled + .checkbox__label:before {
opacity: 0.6;
}
.checkbox__label-text {
max-width: 515px;
line-height: 1.5;

View File

@ -14,7 +14,7 @@ class Checkbox extends Component {
t,
} = this.props;
return (
<div className="form__group">
<div className="form__group form__group--checkbox">
<label className="checkbox checkbox--settings">
<span className="checkbox__marker"/>
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>

View File

@ -1,5 +1,14 @@
import { connect } from 'react-redux';
import * as actionCreators from '../actions';
import {
setRules,
getFilteringStatus,
addFilter,
removeFilter,
toggleFilterStatus,
toggleFilteringModal,
refreshFilters,
handleRulesChange,
} from '../actions/filtering';
import Filters from '../components/Filters';
const mapStateToProps = (state) => {
@ -8,7 +17,18 @@ const mapStateToProps = (state) => {
return props;
};
const mapDispatchToProps = {
setRules,
getFilteringStatus,
addFilter,
removeFilter,
toggleFilterStatus,
toggleFilteringModal,
refreshFilters,
handleRulesChange,
};
export default connect(
mapStateToProps,
actionCreators,
mapDispatchToProps,
)(Filters);

View File

@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { getFilteringStatus, setRules, addSuccessToast, getClients } from '../actions';
import { addSuccessToast, getClients } from '../actions';
import { getFilteringStatus, setRules } from '../actions/filtering';
import { getLogs, getLogsConfig } from '../actions/queryLogs';
import Logs from '../components/Logs';

View File

@ -3,17 +3,19 @@ import { initSettings, toggleSetting } from '../actions';
import { getBlockedServices, setBlockedServices } from '../actions/services';
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
import Settings from '../components/Settings';
const mapStateToProps = (state) => {
const {
settings, services, stats, queryLogs,
settings, services, stats, queryLogs, filtering,
} = state;
const props = {
settings,
services,
stats,
queryLogs,
filtering,
};
return props;
};
@ -29,6 +31,8 @@ const mapDispatchToProps = {
clearLogs,
getLogsConfig,
setLogsConfig,
getFilteringStatus,
setFiltersConfig,
};
export default connect(

View File

@ -265,3 +265,5 @@ export const FILTERED_STATUS = {
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];

View File

@ -32,28 +32,36 @@ export const renderRadioField = ({
}) => (
<Fragment>
<label className="custom-control custom-radio custom-control-inline">
<input
{...input}
type="radio"
className="custom-control-input"
disabled={disabled}
/>
<input {...input} type="radio" className="custom-control-input" disabled={disabled} />
<span className="custom-control-label">{placeholder}</span>
</label>
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
{!disabled &&
touched &&
(error && <span className="form__message form__message--error">{error}</span>)}
</Fragment>
);
export const renderSelectField = ({
input, placeholder, disabled, meta: { touched, error },
input,
placeholder,
subtitle,
disabled,
modifier = 'checkbox--form',
meta: { touched, error },
}) => (
<Fragment>
<label className="checkbox checkbox--form">
<label className={`checkbox ${modifier}`}>
<span className="checkbox__marker" />
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{placeholder}</span>
{subtitle && (
<span
className="checkbox__label-subtitle"
dangerouslySetInnerHTML={{ __html: subtitle }}
/>
)}
</span>
</span>
</label>
@ -64,7 +72,12 @@ export const renderSelectField = ({
);
export const renderServiceField = ({
input, placeholder, disabled, modifier, icon, meta: { touched, error },
input,
placeholder,
disabled,
modifier,
icon,
meta: { touched, error },
}) => (
<Fragment>
<label className={`service custom-switch ${modifier}`}>
@ -81,7 +94,9 @@ export const renderServiceField = ({
<use xlinkHref={`#${icon}`} />
</svg>
</label>
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
{!disabled &&
touched &&
(error && <span className="form__message form__message--error">{error}</span>)}
</Fragment>
);

View File

@ -6,6 +6,7 @@ import addDays from 'date-fns/add_days';
import subDays from 'date-fns/sub_days';
import round from 'lodash/round';
import axios from 'axios';
import i18n from 'i18next';
import {
STANDARD_DNS_PORT,
@ -19,6 +20,21 @@ export const formatTime = (time) => {
return dateFormat(parsedTime, 'HH:mm:ss');
};
export const formatDateTime = (dateTime) => {
const currentLanguage = i18n.languages[0] || 'en';
const parsedTime = dateParse(dateTime);
const options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false,
};
return parsedTime.toLocaleString(currentLanguage, options);
};
export const normalizeLogs = logs => logs.map((log) => {
const {
time,
@ -74,18 +90,38 @@ export const normalizeTopStats = stats => (
);
export const normalizeFilteringStatus = (filteringStatus) => {
const { enabled, filters, user_rules: userRules } = filteringStatus;
const newFilters = filters ? filters.map((filter) => {
const {
id, url, enabled, lastUpdated: lastUpdated = Date.now(), name = 'Default name', rulesCount: rulesCount = 0,
} = filter;
const {
enabled, filters, user_rules: userRules, interval,
} = filteringStatus;
const newFilters = filters
? filters.map((filter) => {
const {
id,
url,
enabled,
last_updated,
name = 'Default name',
rules_count: rules_count = 0,
} = filter;
return {
id, url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
};
}) : [];
return {
id,
url,
enabled,
lastUpdated: last_updated ? formatDateTime(last_updated) : '',
name,
rulesCount: rules_count,
};
})
: [];
const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
return { enabled, userRules: newUserRules, filters: newFilters };
return {
enabled,
userRules: newUserRules,
filters: newFilters,
interval,
};
};
export const getPercent = (amount, number) => {
@ -241,3 +277,5 @@ export const secondsToMilliseconds = (seconds) => {
return seconds;
};
export const normalizeRulesTextarea = text => text && text.replace(/^\n/g, '').replace(/\n\s*\n/g, '\n');

View File

@ -0,0 +1,86 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/filtering';
const filtering = handleActions(
{
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
[actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
[actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
[actions.handleRulesChange]: (state, { payload }) => {
const { userRules } = payload;
return { ...state, userRules };
},
[actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
[actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
[actions.getFilteringStatusSuccess]: (state, { payload }) => ({
...state,
...payload,
processingFilters: false,
}),
[actions.addFilterRequest]: state => ({
...state,
processingAddFilter: true,
isFilterAdded: false,
}),
[actions.addFilterFailure]: state => ({
...state,
processingAddFilter: false,
isFilterAdded: false,
}),
[actions.addFilterSuccess]: state => ({
...state,
processingAddFilter: false,
isFilterAdded: true,
}),
[actions.toggleFilteringModal]: (state) => {
const newState = {
...state,
isModalOpen: !state.isModalOpen,
isFilterAdded: false,
};
return newState;
},
[actions.toggleFilterRequest]: state => ({ ...state, processingConfigFilter: true }),
[actions.toggleFilterFailure]: state => ({ ...state, processingConfigFilter: false }),
[actions.toggleFilterSuccess]: state => ({ ...state, processingConfigFilter: false }),
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
[actions.removeFilterRequest]: state => ({ ...state, processingRemoveFilter: true }),
[actions.removeFilterFailure]: state => ({ ...state, processingRemoveFilter: false }),
[actions.removeFilterSuccess]: state => ({ ...state, processingRemoveFilter: false }),
[actions.setFiltersConfigRequest]: state => ({ ...state, processingSetConfig: true }),
[actions.setFiltersConfigFailure]: state => ({ ...state, processingSetConfig: false }),
[actions.setFiltersConfigSuccess]: (state, { payload }) => ({
...state,
...payload,
processingSetConfig: false,
}),
},
{
isModalOpen: false,
processingFilters: false,
processingRules: false,
processingAddFilter: false,
processingRefreshFilters: false,
processingConfigFilter: false,
processingRemoveFilter: false,
processingSetConfig: false,
isFilterAdded: false,
filters: [],
userRules: '',
interval: 24,
enabled: true,
},
);
export default filtering;

View File

@ -13,6 +13,7 @@ import rewrites from './rewrites';
import services from './services';
import stats from './stats';
import queryLogs from './queryLogs';
import filtering from './filtering';
const settings = handleActions({
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
@ -130,13 +131,6 @@ const dashboard = handleActions({
return newState;
},
[actions.getFilteringRequest]: state => ({ ...state, processingFiltering: true }),
[actions.getFilteringFailure]: state => ({ ...state, processingFiltering: false }),
[actions.getFilteringSuccess]: (state, { payload }) => {
const newState = { ...state, isFilteringEnabled: payload, processingFiltering: false };
return newState;
},
[actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
[actions.toggleProtectionSuccess]: (state) => {
@ -189,62 +183,6 @@ const dashboard = handleActions({
autoClients: [],
});
const filtering = handleActions({
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
[actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
[actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
[actions.handleRulesChange]: (state, { payload }) => {
const { userRules } = payload;
return { ...state, userRules };
},
[actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
[actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
[actions.getFilteringStatusSuccess]: (state, { payload }) => {
const { status } = payload;
const { filters, userRules } = status;
const newState = {
...state, filters, userRules, processingFilters: false,
};
return newState;
},
[actions.addFilterRequest]: state =>
({ ...state, processingAddFilter: true, isFilterAdded: false }),
[actions.addFilterFailure]: (state) => {
const newState = { ...state, processingAddFilter: false, isFilterAdded: false };
return newState;
},
[actions.addFilterSuccess]: state =>
({ ...state, processingAddFilter: false, isFilterAdded: true }),
[actions.toggleFilteringModal]: (state) => {
const newState = {
...state,
isFilteringModalOpen: !state.isFilteringModalOpen,
isFilterAdded: false,
};
return newState;
},
[actions.toggleFilterRequest]: state => ({ ...state, processingFilters: true }),
[actions.toggleFilterFailure]: state => ({ ...state, processingFilters: false }),
[actions.toggleFilterSuccess]: state => ({ ...state, processingFilters: false }),
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
}, {
isFilteringModalOpen: false,
processingFilters: false,
processingRules: false,
processingAddFilter: false,
processingRefreshFilters: false,
filters: [],
userRules: '',
});
const dhcp = handleActions({
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),