diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 570d28e1..9c59c654 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -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"
}
diff --git a/client/src/__locales/ru.json b/client/src/__locales/ru.json
index 4bca94d4..88799f70 100644
--- a/client/src/__locales/ru.json
+++ b/client/src/__locales/ru.json
@@ -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}} дней"
}
\ No newline at end of file
diff --git a/client/src/actions/filtering.js b/client/src/actions/filtering.js
new file mode 100644
index 00000000..3f0c59d9
--- /dev/null
+++ b/client/src/actions/filtering.js
@@ -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());
+ }
+};
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 8a132060..88e14ef9 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -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');
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index f39b28dc..187b7312 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -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
diff --git a/client/src/components/Filters/Modal.js b/client/src/components/Filters/Modal.js
index 884117b7..c4b89437 100644
--- a/client/src/components/Filters/Modal.js
+++ b/client/src/components/Filters/Modal.js
@@ -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 (
-