+ client: handle filters configuration
This commit is contained in:
parent
57bb04685f
commit
d0fc1dc54d
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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}} дней"
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -97,16 +62,29 @@ class Modal extends Component {
|
|||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{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()}
|
||||
<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>
|
||||
{!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
|
@ -118,13 +96,12 @@ class Modal extends Component {
|
|||
<button
|
||||
type="button"
|
||||
className="btn btn-success"
|
||||
onClick={this.handleNext}
|
||||
disabled={isValidForSubmit || processingAddFilter}
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,13 +22,18 @@ 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}/>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
onChange={() => this.props.toggleFilterStatus(url, enabled)}
|
||||
checked={enabled}
|
||||
disabled={processingConfigFilter}
|
||||
/>
|
||||
<span className="checkbox__label" />
|
||||
</label>
|
||||
);
|
||||
|
@ -37,32 +44,54 @@ class Filters extends Component {
|
|||
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
||||
this.props.removeFilter({ url });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
columns = [{
|
||||
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>),
|
||||
}, {
|
||||
minWidth: 200,
|
||||
Cell: CellWrap,
|
||||
},
|
||||
{
|
||||
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>),
|
||||
}, {
|
||||
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 }) => (
|
||||
|
@ -84,10 +113,22 @@ class Filters extends Component {
|
|||
];
|
||||
|
||||
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>
|
||||
<Fragment>
|
||||
<PageTitle title={t('filters')} />
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
|
@ -101,8 +142,13 @@ class Filters extends Component {
|
|||
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')}
|
||||
|
@ -115,14 +161,14 @@ class Filters extends Component {
|
|||
<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);
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__text--full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logs__row .tooltip-custom {
|
||||
top: 0;
|
||||
margin-left: 0;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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}/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
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');
|
||||
|
|
|
@ -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;
|
|
@ -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 }),
|
||||
|
|
Loading…
Reference in New Issue