+ 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_retention_desc": "If you decrease the interval value, some data will be lost",
|
||||||
"statistics_clear": " Clear statistics",
|
"statistics_clear": " Clear statistics",
|
||||||
"statistics_clear_confirm": "Are you sure you want to 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": "Настройки клиентов",
|
"client_settings": "Настройки клиентов",
|
||||||
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
|
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
|
||||||
"upstream_parallel": "Использовать одновременные запроссы ко всем серверам для ускорения обработки запроса",
|
"upstream_parallel": "Использовать одновременные запросы ко всем серверам для ускорения обработки запроса",
|
||||||
"bootstrap_dns": "Bootstrap DNS-серверы",
|
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||||
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
||||||
"url_added_successfully": "URL успешно добавлен",
|
|
||||||
"check_dhcp_servers": "Проверить DHCP-серверы",
|
"check_dhcp_servers": "Проверить DHCP-серверы",
|
||||||
"save_config": "Сохранить конфигурацию",
|
"save_config": "Сохранить конфигурацию",
|
||||||
"enabled_dhcp": "DHCP-сервер включен",
|
"enabled_dhcp": "DHCP-сервер включен",
|
||||||
|
@ -67,7 +66,6 @@
|
||||||
"disabled_protection": "Защита выкл.",
|
"disabled_protection": "Защита выкл.",
|
||||||
"refresh_statics": "Обновить статистику",
|
"refresh_statics": "Обновить статистику",
|
||||||
"dns_query": "DNS-запросы",
|
"dns_query": "DNS-запросы",
|
||||||
"blocked_by": "Заблокировано фильтрами",
|
|
||||||
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
|
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
|
||||||
"stats_adult": "Заблокированные \"взрослые\" сайты",
|
"stats_adult": "Заблокированные \"взрослые\" сайты",
|
||||||
"stats_query_domain": "Часто запрашиваемые домены",
|
"stats_query_domain": "Часто запрашиваемые домены",
|
||||||
|
@ -78,7 +76,6 @@
|
||||||
"top_clients": "Частые клиенты",
|
"top_clients": "Частые клиенты",
|
||||||
"no_clients_found": "Клиентов не найдено",
|
"no_clients_found": "Клиентов не найдено",
|
||||||
"general_statistics": "Общая статистика",
|
"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": "Количество DNS-запросов, заблокированных фильтрами и блок-списками",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
|
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Количество заблокированных \"сайтов для взрослых\"",
|
"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_2": "Найдите настройки DHCP или DNS. Найдите буквы \"DNS\" рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
|
||||||
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
||||||
"install_devices_windows_list_1": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
"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_3": "В левой стороне экрана найдите \"Изменение параметров адаптера\" и кликните по нему.",
|
||||||
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
|
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
|
||||||
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
|
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
|
||||||
|
@ -298,7 +295,6 @@
|
||||||
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
||||||
"client_added": "Клиент \"{{key}}\" успешно добавлен",
|
"client_added": "Клиент \"{{key}}\" успешно добавлен",
|
||||||
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
|
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
|
||||||
"table_statistics": "Количество запросов (последние 24 часа)",
|
|
||||||
"clients_not_found": "Клиентов не найдено",
|
"clients_not_found": "Клиентов не найдено",
|
||||||
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
|
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
|
||||||
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
|
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
|
||||||
|
@ -309,7 +305,7 @@
|
||||||
"access_allowed_title": "Разрешенные клиенты",
|
"access_allowed_title": "Разрешенные клиенты",
|
||||||
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
|
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
|
||||||
"access_disallowed_title": "Запрещенные клиенты",
|
"access_disallowed_title": "Запрещенные клиенты",
|
||||||
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроек, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
|
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
|
||||||
"access_blocked_title": "Заблокированные домены",
|
"access_blocked_title": "Заблокированные домены",
|
||||||
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
|
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
|
||||||
"access_settings_saved": "Настройки доступа успешно сохранены",
|
"access_settings_saved": "Настройки доступа успешно сохранены",
|
||||||
|
@ -353,5 +349,13 @@
|
||||||
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
|
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
|
||||||
"blocked_service": "Заблокированный сервис",
|
"blocked_service": "Заблокированный сервис",
|
||||||
"block_all": "Заблокировать все",
|
"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 { createAction } from 'redux-actions';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import versionCompare from '../helpers/versionCompare';
|
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 { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
|
||||||
import { getTlsStatus } from './encryption';
|
import { getTlsStatus } from './encryption';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
|
@ -21,16 +20,6 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||||
let successMessage = '';
|
let successMessage = '';
|
||||||
try {
|
try {
|
||||||
switch (settingKey) {
|
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:
|
case SETTINGS_NAMES.safebrowsing:
|
||||||
if (status) {
|
if (status) {
|
||||||
successMessage = 'disabled_safe_browsing_toast';
|
successMessage = 'disabled_safe_browsing_toast';
|
||||||
|
@ -77,18 +66,15 @@ export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS');
|
||||||
export const initSettings = settingsList => async (dispatch) => {
|
export const initSettings = settingsList => async (dispatch) => {
|
||||||
dispatch(initSettingsRequest());
|
dispatch(initSettingsRequest());
|
||||||
try {
|
try {
|
||||||
const filteringStatus = await apiClient.getFilteringStatus();
|
|
||||||
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
|
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
|
||||||
const parentalStatus = await apiClient.getParentalStatus();
|
const parentalStatus = await apiClient.getParentalStatus();
|
||||||
const safesearchStatus = await apiClient.getSafesearchStatus();
|
const safesearchStatus = await apiClient.getSafesearchStatus();
|
||||||
const {
|
const {
|
||||||
filtering,
|
|
||||||
safebrowsing,
|
safebrowsing,
|
||||||
parental,
|
parental,
|
||||||
safesearch,
|
safesearch,
|
||||||
} = settingsList;
|
} = settingsList;
|
||||||
const newSettingsList = {
|
const newSettingsList = {
|
||||||
filtering: { ...filtering, enabled: filteringStatus.enabled },
|
|
||||||
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
|
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
|
||||||
parental: { ...parental, enabled: parentalStatus.enabled },
|
parental: { ...parental, enabled: parentalStatus.enabled },
|
||||||
safesearch: { ...safesearch, enabled: safesearchStatus.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 toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
|
||||||
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
|
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
|
||||||
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
|
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 handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
|
||||||
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||||
|
|
|
@ -90,32 +90,19 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtering
|
// Filtering
|
||||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
FILTERING_INFO = { path: 'filtering_info', method: 'GET' };
|
||||||
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
|
|
||||||
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
|
|
||||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
||||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
||||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', 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_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
||||||
|
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
|
||||||
|
FILTERING_CONFIG = { path: 'filtering_config', method: 'POST' };
|
||||||
|
|
||||||
getFilteringStatus() {
|
getFilteringStatus() {
|
||||||
const { path, method } = this.FILTERING_STATUS;
|
const { path, method } = this.FILTERING_INFO;
|
||||||
return this.makeRequest(path, method);
|
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() {
|
refreshFilters() {
|
||||||
const { path, method } = this.FILTERING_REFRESH;
|
const { path, method } = this.FILTERING_REFRESH;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
|
@ -151,26 +138,22 @@ class Api {
|
||||||
return this.makeRequest(path, method, parameters);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFilter(url) {
|
setFiltersConfig(config) {
|
||||||
const { path, method } = this.FILTERING_ENABLE_FILTER;
|
const { path, method } = this.FILTERING_CONFIG;
|
||||||
const parameter = 'url';
|
const parameters = {
|
||||||
const requestBody = `${parameter}=${url}`;
|
data: config,
|
||||||
const config = {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
data: requestBody,
|
|
||||||
header: { 'Content-Type': 'text/plain' },
|
|
||||||
};
|
};
|
||||||
return this.makeRequest(path, method, config);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableFilter(url) {
|
setFilterUrl(config) {
|
||||||
const { path, method } = this.FILTERING_DISABLE_FILTER;
|
const { path, method } = this.FILTERING_SET_URL;
|
||||||
const parameter = 'url';
|
const parameters = {
|
||||||
const requestBody = `${parameter}=${url}`;
|
data: config,
|
||||||
const config = {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
data: requestBody,
|
|
||||||
header: { 'Content-Type': 'text/plain' },
|
|
||||||
};
|
};
|
||||||
return this.makeRequest(path, method, config);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parental
|
// Parental
|
||||||
|
|
|
@ -33,27 +33,13 @@ class Modal extends Component {
|
||||||
this.setState({ ...this.state, name });
|
this.setState({ ...this.state, name });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNext = () => {
|
|
||||||
this.props.addFilter(this.state.url, this.state.name);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.props.isFilterAdded) {
|
|
||||||
this.closeModal();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
this.props.toggleModal();
|
this.props.toggleModal();
|
||||||
this.setState({ ...this.state, ...initialState });
|
this.setState({ ...this.state, ...initialState });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isOpen, processingAddFilter } = this.props;
|
||||||
isOpen,
|
|
||||||
title,
|
|
||||||
inputDescription,
|
|
||||||
processingAddFilter,
|
|
||||||
} = this.props;
|
|
||||||
const { isUrlValid, url, name } = this.state;
|
const { isUrlValid, url, name } = this.state;
|
||||||
const inputUrlClass = classnames({
|
const inputUrlClass = classnames({
|
||||||
'form-control mb-2': true,
|
'form-control mb-2': true,
|
||||||
|
@ -64,28 +50,7 @@ class Modal extends Component {
|
||||||
'form-control mb-2': true,
|
'form-control mb-2': true,
|
||||||
'is-valid': name.length > 0,
|
'is-valid': name.length > 0,
|
||||||
});
|
});
|
||||||
|
const isValidForSubmit = url.length > 0 && isUrlValid && 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);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
|
@ -96,35 +61,47 @@ class Modal extends Component {
|
||||||
>
|
>
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h4 className="modal-title">
|
<h4 className="modal-title">
|
||||||
{title}
|
<Trans>new_filter_btn</Trans>
|
||||||
</h4>
|
</h4>
|
||||||
<button type="button" className="close" onClick={this.closeModal}>
|
<button type="button" className="close" onClick={this.closeModal}>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{renderBody()}
|
<input
|
||||||
</div>
|
type="text"
|
||||||
{!this.props.isFilterAdded &&
|
className={inputNameClass}
|
||||||
<div className="modal-footer">
|
placeholder={this.props.t('enter_name_hint')}
|
||||||
<button
|
onChange={this.handleNameChange}
|
||||||
type="button"
|
/>
|
||||||
className="btn btn-secondary"
|
<input
|
||||||
onClick={this.closeModal}
|
type="text"
|
||||||
>
|
className={inputUrlClass}
|
||||||
<Trans>cancel_btn</Trans>
|
placeholder={this.props.t('enter_url_hint')}
|
||||||
</button>
|
onChange={this.handleUrlChange}
|
||||||
<button
|
/>
|
||||||
type="button"
|
<div className="description">
|
||||||
className="btn btn-success"
|
<Trans>enter_valid_filter_url</Trans>
|
||||||
onClick={this.handleNext}
|
|
||||||
disabled={isValidForSubmit || processingAddFilter}
|
|
||||||
>
|
|
||||||
<Trans>add_filter_btn</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={this.closeModal}
|
||||||
|
>
|
||||||
|
<Trans>cancel_btn</Trans>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success"
|
||||||
|
onClick={() => this.props.addFilter(url, name)}
|
||||||
|
disabled={!isValidForSubmit || processingAddFilter}
|
||||||
|
>
|
||||||
|
<Trans>add_filter_btn</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
);
|
);
|
||||||
|
@ -134,12 +111,10 @@ class Modal extends Component {
|
||||||
Modal.propTypes = {
|
Modal.propTypes = {
|
||||||
toggleModal: PropTypes.func.isRequired,
|
toggleModal: PropTypes.func.isRequired,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
inputDescription: PropTypes.string,
|
|
||||||
addFilter: PropTypes.func.isRequired,
|
addFilter: PropTypes.func.isRequired,
|
||||||
isFilterAdded: PropTypes.bool,
|
isFilterAdded: PropTypes.bool.isRequired,
|
||||||
processingAddFilter: PropTypes.bool,
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNamespaces()(Modal);
|
export default withNamespaces()(Modal);
|
||||||
|
|
|
@ -15,13 +15,13 @@ class UserRules extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const { t, userRules } = this.props;
|
||||||
return (
|
return (
|
||||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control form-control--textarea-large"
|
className="form-control form-control--textarea-large"
|
||||||
value={this.props.userRules}
|
value={userRules}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
|
@ -79,10 +79,10 @@ class UserRules extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserRules.propTypes = {
|
UserRules.propTypes = {
|
||||||
userRules: PropTypes.string,
|
userRules: PropTypes.string.isRequired,
|
||||||
handleRulesChange: PropTypes.func,
|
handleRulesChange: PropTypes.func.isRequired,
|
||||||
handleRulesSubmit: PropTypes.func,
|
handleRulesSubmit: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNamespaces()(UserRules);
|
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 ReactTable from 'react-table';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import Modal from './Modal';
|
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
|
import CellWrap from '../ui/CellWrap';
|
||||||
import UserRules from './UserRules';
|
import UserRules from './UserRules';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
class Filters extends Component {
|
class Filters extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -20,14 +22,19 @@ class Filters extends Component {
|
||||||
this.props.setRules(this.props.filtering.userRules);
|
this.props.setRules(this.props.filtering.userRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderCheckbox = (row) => {
|
renderCheckbox = ({ original }) => {
|
||||||
const { url } = row.original;
|
const { processingConfigFilter } = this.props.filtering;
|
||||||
const { filters } = this.props.filtering;
|
const { url, enabled } = original;
|
||||||
const filter = filters.filter(filter => filter.url === url)[0];
|
|
||||||
return (
|
return (
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input type="checkbox" className="checkbox__input" onChange={() => this.props.toggleFilterStatus(filter.url)} checked={filter.enabled}/>
|
<input
|
||||||
<span className="checkbox__label"/>
|
type="checkbox"
|
||||||
|
className="checkbox__input"
|
||||||
|
onChange={() => this.props.toggleFilterStatus(url, enabled)}
|
||||||
|
checked={enabled}
|
||||||
|
disabled={processingConfigFilter}
|
||||||
|
/>
|
||||||
|
<span className="checkbox__label" />
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,92 +44,131 @@ class Filters extends Component {
|
||||||
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
||||||
this.props.removeFilter({ url });
|
this.props.removeFilter({ url });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
columns = [{
|
columns = [
|
||||||
Header: <Trans>enabled_table_header</Trans>,
|
{
|
||||||
accessor: 'enabled',
|
Header: <Trans>enabled_table_header</Trans>,
|
||||||
Cell: this.renderCheckbox,
|
accessor: 'enabled',
|
||||||
width: 90,
|
Cell: this.renderCheckbox,
|
||||||
className: 'text-center',
|
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>name_table_header</Trans>,
|
||||||
}, {
|
accessor: 'name',
|
||||||
Header: <Trans>filter_url_table_header</Trans>,
|
minWidth: 200,
|
||||||
accessor: 'url',
|
Cell: CellWrap,
|
||||||
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>,
|
Header: <Trans>filter_url_table_header</Trans>,
|
||||||
accessor: 'rulesCount',
|
accessor: 'url',
|
||||||
className: 'text-center',
|
minWidth: 200,
|
||||||
Cell: props => props.value.toLocaleString(),
|
Cell: ({ value }) => (
|
||||||
}, {
|
<div className="logs__row logs__row--overflow">
|
||||||
Header: <Trans>last_time_updated_table_header</Trans>,
|
<a
|
||||||
accessor: 'lastUpdated',
|
href={value}
|
||||||
className: 'text-center',
|
target="_blank"
|
||||||
}, {
|
rel="noopener noreferrer"
|
||||||
Header: <Trans>actions_table_header</Trans>,
|
className="link logs__text"
|
||||||
accessor: 'url',
|
>
|
||||||
Cell: ({ value }) => (
|
{value}
|
||||||
<button
|
</a>
|
||||||
type="button"
|
</div>
|
||||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
),
|
||||||
onClick={() => this.handleDelete(value)}
|
},
|
||||||
title={this.props.t('delete_table_action')}
|
{
|
||||||
>
|
Header: <Trans>rules_count_table_header</Trans>,
|
||||||
<svg className="icons">
|
accessor: 'rulesCount',
|
||||||
<use xlinkHref="#delete" />
|
className: 'text-center',
|
||||||
</svg>
|
minWidth: 100,
|
||||||
</button>
|
Cell: props => props.value.toLocaleString(),
|
||||||
),
|
},
|
||||||
className: 'text-center',
|
{
|
||||||
width: 80,
|
Header: <Trans>last_time_updated_table_header</Trans>,
|
||||||
sortable: false,
|
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() {
|
render() {
|
||||||
const { t } = this.props;
|
const {
|
||||||
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
|
filtering, t, toggleFilteringModal, refreshFilters, addFilter,
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
filters,
|
||||||
|
userRules,
|
||||||
|
isModalOpen,
|
||||||
|
isFilterAdded,
|
||||||
|
processingRefreshFilters,
|
||||||
|
processingRemoveFilter,
|
||||||
|
processingAddFilter,
|
||||||
|
processingFilters,
|
||||||
|
} = filtering;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
<PageTitle title={ t('filters') } />
|
<PageTitle title={t('filters')} />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<Card
|
<Card
|
||||||
title={ t('filters_and_hosts') }
|
title={t('filters_and_hosts')}
|
||||||
subtitle={ t('filters_and_hosts_hint') }
|
subtitle={t('filters_and_hosts_hint')}
|
||||||
>
|
>
|
||||||
<ReactTable
|
<ReactTable
|
||||||
data={filters}
|
data={filters}
|
||||||
columns={this.columns}
|
columns={this.columns}
|
||||||
showPagination={true}
|
showPagination={true}
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
|
loading={
|
||||||
|
processingFilters ||
|
||||||
|
processingAddFilter ||
|
||||||
|
processingRemoveFilter ||
|
||||||
|
processingRefreshFilters
|
||||||
|
}
|
||||||
minRows={4}
|
minRows={4}
|
||||||
// Text
|
previousText={t('previous_btn')}
|
||||||
previousText={ t('previous_btn') }
|
nextText={t('next_btn')}
|
||||||
nextText={ t('next_btn') }
|
loadingText={t('loading_table_status')}
|
||||||
loadingText={ t('loading_table_status') }
|
pageText={t('page_table_footer_text')}
|
||||||
pageText={ t('page_table_footer_text') }
|
ofText={t('of_table_footer_text')}
|
||||||
ofText={ t('of_table_footer_text') }
|
rowsText={t('rows_table_footer_text')}
|
||||||
rowsText={ t('rows_table_footer_text') }
|
noDataText={t('no_filters_added')}
|
||||||
noDataText={ t('no_filters_added') }
|
|
||||||
/>
|
/>
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
<button
|
<button
|
||||||
className="btn btn-success btn-standard mr-2"
|
className="btn btn-success btn-standard mr-2"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={this.props.toggleFilteringModal}
|
onClick={toggleFilteringModal}
|
||||||
>
|
>
|
||||||
<Trans>add_filter_btn</Trans>
|
<Trans>add_filter_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-standard"
|
className="btn btn-primary btn-standard"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={this.props.refreshFilters}
|
onClick={refreshFilters}
|
||||||
disabled={processingRefreshFilters}
|
disabled={processingRefreshFilters}
|
||||||
>
|
>
|
||||||
<Trans>check_updates_btn</Trans>
|
<Trans>check_updates_btn</Trans>
|
||||||
|
@ -140,15 +186,13 @@ class Filters extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={this.props.filtering.isFilteringModalOpen}
|
isOpen={isModalOpen}
|
||||||
toggleModal={this.props.toggleFilteringModal}
|
toggleModal={toggleFilteringModal}
|
||||||
addFilter={this.props.addFilter}
|
addFilter={addFilter}
|
||||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
isFilterAdded={isFilterAdded}
|
||||||
processingAddFilter={this.props.filtering.processingAddFilter}
|
processingAddFilter={processingAddFilter}
|
||||||
title={ t('new_filter_btn') }
|
|
||||||
inputDescription={ t('enter_valid_filter_url') }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,12 +201,15 @@ Filters.propTypes = {
|
||||||
setRules: PropTypes.func,
|
setRules: PropTypes.func,
|
||||||
getFilteringStatus: PropTypes.func.isRequired,
|
getFilteringStatus: PropTypes.func.isRequired,
|
||||||
filtering: PropTypes.shape({
|
filtering: PropTypes.shape({
|
||||||
userRules: PropTypes.string,
|
userRules: PropTypes.string.isRequired,
|
||||||
filters: PropTypes.array,
|
filters: PropTypes.array.isRequired,
|
||||||
isFilteringModalOpen: PropTypes.bool.isRequired,
|
isModalOpen: PropTypes.bool.isRequired,
|
||||||
isFilterAdded: PropTypes.bool,
|
isFilterAdded: PropTypes.bool.isRequired,
|
||||||
processingAddFilter: PropTypes.bool,
|
processingFilters: PropTypes.bool.isRequired,
|
||||||
processingRefreshFilters: PropTypes.bool,
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
|
processingRefreshFilters: PropTypes.bool.isRequired,
|
||||||
|
processingConfigFilter: PropTypes.bool.isRequired,
|
||||||
|
processingRemoveFilter: PropTypes.bool.isRequired,
|
||||||
}),
|
}),
|
||||||
removeFilter: PropTypes.func.isRequired,
|
removeFilter: PropTypes.func.isRequired,
|
||||||
toggleFilterStatus: PropTypes.func.isRequired,
|
toggleFilterStatus: PropTypes.func.isRequired,
|
||||||
|
@ -170,8 +217,7 @@ Filters.propTypes = {
|
||||||
toggleFilteringModal: PropTypes.func.isRequired,
|
toggleFilteringModal: PropTypes.func.isRequired,
|
||||||
handleRulesChange: PropTypes.func.isRequired,
|
handleRulesChange: PropTypes.func.isRequired,
|
||||||
refreshFilters: PropTypes.func.isRequired,
|
refreshFilters: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default withNamespaces()(Filters);
|
export default withNamespaces()(Filters);
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__text--full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.logs__row .tooltip-custom {
|
.logs__row .tooltip-custom {
|
||||||
top: 0;
|
top: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import endsWith from 'lodash/endsWith';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import { HashLink as Link } from 'react-router-hash-link';
|
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 { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
|
||||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
|
@ -114,7 +114,7 @@ class Logs extends Component {
|
||||||
|
|
||||||
getTimeCell = ({ value }) => (
|
getTimeCell = ({ value }) => (
|
||||||
<div className="logs__row">
|
<div className="logs__row">
|
||||||
<span className="logs__text" title={value}>
|
<span className="logs__text" title={formatDateTime(value)}>
|
||||||
{formatTime(value)}
|
{formatTime(value)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -227,7 +227,7 @@ class Logs extends Component {
|
||||||
{
|
{
|
||||||
Header: t('time_table_header'),
|
Header: t('time_table_header'),
|
||||||
accessor: 'time',
|
accessor: 'time',
|
||||||
maxWidth: 90,
|
maxWidth: 100,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: this.getTimeCell,
|
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;
|
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 {
|
.form__inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -109,3 +120,11 @@
|
||||||
.custom-control-label:before {
|
.custom-control-label:before {
|
||||||
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
|
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 Services from './Services';
|
||||||
import StatsConfig from './StatsConfig';
|
import StatsConfig from './StatsConfig';
|
||||||
import LogsConfig from './LogsConfig';
|
import LogsConfig from './LogsConfig';
|
||||||
|
import FiltersConfig from './FiltersConfig';
|
||||||
import Checkbox from '../ui/Checkbox';
|
import Checkbox from '../ui/Checkbox';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
|
@ -14,11 +15,6 @@ import './Settings.css';
|
||||||
|
|
||||||
class Settings extends Component {
|
class Settings extends Component {
|
||||||
settings = {
|
settings = {
|
||||||
filtering: {
|
|
||||||
enabled: false,
|
|
||||||
title: 'block_domain_use_filters_and_hosts',
|
|
||||||
subtitle: 'filters_block_toggle_hint',
|
|
||||||
},
|
|
||||||
safebrowsing: {
|
safebrowsing: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
title: 'use_adguard_browsing_sec',
|
title: 'use_adguard_browsing_sec',
|
||||||
|
@ -41,17 +37,20 @@ class Settings extends Component {
|
||||||
this.props.getBlockedServices();
|
this.props.getBlockedServices();
|
||||||
this.props.getStatsConfig();
|
this.props.getStatsConfig();
|
||||||
this.props.getLogsConfig();
|
this.props.getLogsConfig();
|
||||||
|
this.props.getFilteringStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSettings = (settings) => {
|
renderSettings = (settings) => {
|
||||||
if (Object.keys(settings).length > 0) {
|
const settingsKeys = Object.keys(settings);
|
||||||
return Object.keys(settings).map((key) => {
|
|
||||||
|
if (settingsKeys.length > 0) {
|
||||||
|
return settingsKeys.map((key) => {
|
||||||
const setting = settings[key];
|
const setting = settings[key];
|
||||||
const { enabled } = setting;
|
const { enabled } = setting;
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={key}
|
|
||||||
{...settings[key]}
|
{...settings[key]}
|
||||||
|
key={key}
|
||||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -71,6 +70,8 @@ class Settings extends Component {
|
||||||
queryLogs,
|
queryLogs,
|
||||||
setLogsConfig,
|
setLogsConfig,
|
||||||
clearLogs,
|
clearLogs,
|
||||||
|
filtering,
|
||||||
|
setFiltersConfig,
|
||||||
t,
|
t,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -90,6 +91,12 @@ class Settings extends Component {
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<Card bodyType="card-body box-body--settings">
|
<Card bodyType="card-body box-body--settings">
|
||||||
<div className="form">
|
<div className="form">
|
||||||
|
<FiltersConfig
|
||||||
|
interval={filtering.interval}
|
||||||
|
enabled={filtering.enabled}
|
||||||
|
processing={filtering.processingSetConfig}
|
||||||
|
setFiltersConfig={setFiltersConfig}
|
||||||
|
/>
|
||||||
{this.renderSettings(settings.settingsList)}
|
{this.renderSettings(settings.settingsList)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -134,6 +141,8 @@ Settings.propTypes = {
|
||||||
getStatsConfig: PropTypes.func.isRequired,
|
getStatsConfig: PropTypes.func.isRequired,
|
||||||
setStatsConfig: PropTypes.func.isRequired,
|
setStatsConfig: PropTypes.func.isRequired,
|
||||||
resetStats: PropTypes.func.isRequired,
|
resetStats: PropTypes.func.isRequired,
|
||||||
|
setFiltersConfig: PropTypes.func.isRequired,
|
||||||
|
getFilteringStatus: PropTypes.func.isRequired,
|
||||||
t: 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 {
|
.checkbox--settings .checkbox__label-title {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 2px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: 12px 10px;
|
background-size: 12px 10px;
|
||||||
border-radius: 3px;
|
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 {
|
.checkbox__label .checkbox__label-text {
|
||||||
|
@ -82,10 +82,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label {
|
.checkbox__input:disabled + .checkbox__label {
|
||||||
opacity: 0.6;
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox__input:disabled + .checkbox__label:before {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox__label-text {
|
.checkbox__label-text {
|
||||||
max-width: 515px;
|
max-width: 515px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Checkbox extends Component {
|
||||||
t,
|
t,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="form__group">
|
<div className="form__group form__group--checkbox">
|
||||||
<label className="checkbox checkbox--settings">
|
<label className="checkbox checkbox--settings">
|
||||||
<span className="checkbox__marker"/>
|
<span className="checkbox__marker"/>
|
||||||
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import { connect } from 'react-redux';
|
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';
|
import Filters from '../components/Filters';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
|
@ -8,7 +17,18 @@ const mapStateToProps = (state) => {
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setRules,
|
||||||
|
getFilteringStatus,
|
||||||
|
addFilter,
|
||||||
|
removeFilter,
|
||||||
|
toggleFilterStatus,
|
||||||
|
toggleFilteringModal,
|
||||||
|
refreshFilters,
|
||||||
|
handleRulesChange,
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
actionCreators,
|
mapDispatchToProps,
|
||||||
)(Filters);
|
)(Filters);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
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 { getLogs, getLogsConfig } from '../actions/queryLogs';
|
||||||
import Logs from '../components/Logs';
|
import Logs from '../components/Logs';
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,19 @@ import { initSettings, toggleSetting } from '../actions';
|
||||||
import { getBlockedServices, setBlockedServices } from '../actions/services';
|
import { getBlockedServices, setBlockedServices } from '../actions/services';
|
||||||
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
|
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
|
||||||
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
|
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
|
||||||
|
import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
|
||||||
import Settings from '../components/Settings';
|
import Settings from '../components/Settings';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const {
|
const {
|
||||||
settings, services, stats, queryLogs,
|
settings, services, stats, queryLogs, filtering,
|
||||||
} = state;
|
} = state;
|
||||||
const props = {
|
const props = {
|
||||||
settings,
|
settings,
|
||||||
services,
|
services,
|
||||||
stats,
|
stats,
|
||||||
queryLogs,
|
queryLogs,
|
||||||
|
filtering,
|
||||||
};
|
};
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
@ -29,6 +31,8 @@ const mapDispatchToProps = {
|
||||||
clearLogs,
|
clearLogs,
|
||||||
getLogsConfig,
|
getLogsConfig,
|
||||||
setLogsConfig,
|
setLogsConfig,
|
||||||
|
getFilteringStatus,
|
||||||
|
setFiltersConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -265,3 +265,5 @@ export const FILTERED_STATUS = {
|
||||||
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||||
|
|
||||||
export const QUERY_LOG_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>
|
<Fragment>
|
||||||
<label className="custom-control custom-radio custom-control-inline">
|
<label className="custom-control custom-radio custom-control-inline">
|
||||||
<input
|
<input {...input} type="radio" className="custom-control-input" disabled={disabled} />
|
||||||
{...input}
|
|
||||||
type="radio"
|
|
||||||
className="custom-control-input"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<span className="custom-control-label">{placeholder}</span>
|
<span className="custom-control-label">{placeholder}</span>
|
||||||
</label>
|
</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>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const renderSelectField = ({
|
export const renderSelectField = ({
|
||||||
input, placeholder, disabled, meta: { touched, error },
|
input,
|
||||||
|
placeholder,
|
||||||
|
subtitle,
|
||||||
|
disabled,
|
||||||
|
modifier = 'checkbox--form',
|
||||||
|
meta: { touched, error },
|
||||||
}) => (
|
}) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className="checkbox checkbox--form">
|
<label className={`checkbox ${modifier}`}>
|
||||||
<span className="checkbox__marker" />
|
<span className="checkbox__marker" />
|
||||||
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
||||||
<span className="checkbox__label">
|
<span className="checkbox__label">
|
||||||
<span className="checkbox__label-text checkbox__label-text--long">
|
<span className="checkbox__label-text checkbox__label-text--long">
|
||||||
<span className="checkbox__label-title">{placeholder}</span>
|
<span className="checkbox__label-title">{placeholder}</span>
|
||||||
|
{subtitle && (
|
||||||
|
<span
|
||||||
|
className="checkbox__label-subtitle"
|
||||||
|
dangerouslySetInnerHTML={{ __html: subtitle }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -64,7 +72,12 @@ export const renderSelectField = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
export const renderServiceField = ({
|
export const renderServiceField = ({
|
||||||
input, placeholder, disabled, modifier, icon, meta: { touched, error },
|
input,
|
||||||
|
placeholder,
|
||||||
|
disabled,
|
||||||
|
modifier,
|
||||||
|
icon,
|
||||||
|
meta: { touched, error },
|
||||||
}) => (
|
}) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className={`service custom-switch ${modifier}`}>
|
<label className={`service custom-switch ${modifier}`}>
|
||||||
|
@ -81,7 +94,9 @@ export const renderServiceField = ({
|
||||||
<use xlinkHref={`#${icon}`} />
|
<use xlinkHref={`#${icon}`} />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</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>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import addDays from 'date-fns/add_days';
|
||||||
import subDays from 'date-fns/sub_days';
|
import subDays from 'date-fns/sub_days';
|
||||||
import round from 'lodash/round';
|
import round from 'lodash/round';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
STANDARD_DNS_PORT,
|
STANDARD_DNS_PORT,
|
||||||
|
@ -19,6 +20,21 @@ export const formatTime = (time) => {
|
||||||
return dateFormat(parsedTime, 'HH:mm:ss');
|
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) => {
|
export const normalizeLogs = logs => logs.map((log) => {
|
||||||
const {
|
const {
|
||||||
time,
|
time,
|
||||||
|
@ -74,18 +90,38 @@ export const normalizeTopStats = stats => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const normalizeFilteringStatus = (filteringStatus) => {
|
export const normalizeFilteringStatus = (filteringStatus) => {
|
||||||
const { enabled, filters, user_rules: userRules } = filteringStatus;
|
const {
|
||||||
const newFilters = filters ? filters.map((filter) => {
|
enabled, filters, user_rules: userRules, interval,
|
||||||
const {
|
} = filteringStatus;
|
||||||
id, url, enabled, lastUpdated: lastUpdated = Date.now(), name = 'Default name', rulesCount: rulesCount = 0,
|
const newFilters = filters
|
||||||
} = filter;
|
? filters.map((filter) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
enabled,
|
||||||
|
last_updated,
|
||||||
|
name = 'Default name',
|
||||||
|
rules_count: rules_count = 0,
|
||||||
|
} = filter;
|
||||||
|
|
||||||
return {
|
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') : '';
|
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) => {
|
export const getPercent = (amount, number) => {
|
||||||
|
@ -241,3 +277,5 @@ export const secondsToMilliseconds = (seconds) => {
|
||||||
|
|
||||||
return 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 services from './services';
|
||||||
import stats from './stats';
|
import stats from './stats';
|
||||||
import queryLogs from './queryLogs';
|
import queryLogs from './queryLogs';
|
||||||
|
import filtering from './filtering';
|
||||||
|
|
||||||
const settings = handleActions({
|
const settings = handleActions({
|
||||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||||
|
@ -130,13 +131,6 @@ const dashboard = handleActions({
|
||||||
return newState;
|
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.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
|
||||||
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
|
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
|
||||||
[actions.toggleProtectionSuccess]: (state) => {
|
[actions.toggleProtectionSuccess]: (state) => {
|
||||||
|
@ -189,62 +183,6 @@ const dashboard = handleActions({
|
||||||
autoClients: [],
|
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({
|
const dhcp = handleActions({
|
||||||
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
||||||
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
||||||
|
|
Loading…
Reference in New Issue