From d0fc1dc54dfbc017f28c6c0afa4623c6259af557 Mon Sep 17 00:00:00 2001
From: Ildar Kamalov <i.kamalov@adguard.com>
Date: Thu, 12 Sep 2019 16:19:35 +0300
Subject: [PATCH] + client: handle filters configuration

---
 client/src/__locales/en.json                  |   8 +-
 client/src/__locales/ru.json                  |  20 +-
 client/src/actions/filtering.js               | 145 ++++++++++++
 client/src/actions/index.js                   | 158 +------------
 client/src/api/Api.js                         |  49 ++--
 client/src/components/Filters/Modal.js        | 113 ++++------
 client/src/components/Filters/UserRules.js    |  12 +-
 client/src/components/Filters/index.js        | 212 +++++++++++-------
 client/src/components/Logs/Logs.css           |   4 +
 client/src/components/Logs/index.js           |   6 +-
 .../components/Settings/FiltersConfig/Form.js |  88 ++++++++
 .../Settings/FiltersConfig/index.js           |  36 +++
 client/src/components/Settings/Settings.css   |  19 ++
 client/src/components/Settings/index.js       |  25 ++-
 client/src/components/ui/CellWrap.js          |  19 ++
 client/src/components/ui/Checkbox.css         |   9 +-
 client/src/components/ui/Checkbox.js          |   2 +-
 client/src/containers/Filters.js              |  24 +-
 client/src/containers/Logs.js                 |   3 +-
 client/src/containers/Settings.js             |   6 +-
 client/src/helpers/constants.js               |   2 +
 client/src/helpers/form.js                    |  37 ++-
 client/src/helpers/helpers.js                 |  58 ++++-
 client/src/reducers/filtering.js              |  86 +++++++
 client/src/reducers/index.js                  |  64 +-----
 25 files changed, 745 insertions(+), 460 deletions(-)
 create mode 100644 client/src/actions/filtering.js
 create mode 100644 client/src/components/Settings/FiltersConfig/Form.js
 create mode 100644 client/src/components/Settings/FiltersConfig/index.js
 create mode 100644 client/src/components/ui/CellWrap.js
 create mode 100644 client/src/reducers/filtering.js

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 (
-                    <React.Fragment>
-                        <input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
-                        <input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
-                        {inputDescription &&
-                            <div className="description">
-                                {inputDescription}
-                            </div>}
-                    </React.Fragment>
-                );
-            }
-            return (
-                <div className="description">
-                    <Trans>filter_added_successfully</Trans>
-                </div>
-            );
-        };
-
-        const isValidForSubmit = !(url.length > 0 && isUrlValid && name.length > 0);
+        const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0;
 
         return (
             <ReactModal
@@ -96,35 +61,47 @@ class Modal extends Component {
             >
                 <div className="modal-content">
                     <div className="modal-header">
-                    <h4 className="modal-title">
-                        {title}
-                    </h4>
-                    <button type="button" className="close" onClick={this.closeModal}>
-                        <span className="sr-only">Close</span>
-                    </button>
+                        <h4 className="modal-title">
+                            <Trans>new_filter_btn</Trans>
+                        </h4>
+                        <button type="button" className="close" onClick={this.closeModal}>
+                            <span className="sr-only">Close</span>
+                        </button>
                     </div>
                     <div className="modal-body">
-                        {renderBody()}
-                    </div>
-                    {!this.props.isFilterAdded &&
-                        <div className="modal-footer">
-                            <button
-                                type="button"
-                                className="btn btn-secondary"
-                                onClick={this.closeModal}
-                            >
-                                <Trans>cancel_btn</Trans>
-                            </button>
-                            <button
-                                type="button"
-                                className="btn btn-success"
-                                onClick={this.handleNext}
-                                disabled={isValidForSubmit || processingAddFilter}
-                            >
-                                <Trans>add_filter_btn</Trans>
-                            </button>
+                        <input
+                            type="text"
+                            className={inputNameClass}
+                            placeholder={this.props.t('enter_name_hint')}
+                            onChange={this.handleNameChange}
+                        />
+                        <input
+                            type="text"
+                            className={inputUrlClass}
+                            placeholder={this.props.t('enter_url_hint')}
+                            onChange={this.handleUrlChange}
+                        />
+                        <div className="description">
+                            <Trans>enter_valid_filter_url</Trans>
                         </div>
-                    }
+                    </div>
+                    <div className="modal-footer">
+                        <button
+                            type="button"
+                            className="btn btn-secondary"
+                            onClick={this.closeModal}
+                        >
+                            <Trans>cancel_btn</Trans>
+                        </button>
+                        <button
+                            type="button"
+                            className="btn btn-success"
+                            onClick={() => this.props.addFilter(url, name)}
+                            disabled={!isValidForSubmit || processingAddFilter}
+                        >
+                            <Trans>add_filter_btn</Trans>
+                        </button>
+                    </div>
                 </div>
             </ReactModal>
         );
@@ -134,12 +111,10 @@ class Modal extends Component {
 Modal.propTypes = {
     toggleModal: PropTypes.func.isRequired,
     isOpen: PropTypes.bool.isRequired,
-    title: PropTypes.string.isRequired,
-    inputDescription: PropTypes.string,
     addFilter: PropTypes.func.isRequired,
-    isFilterAdded: PropTypes.bool,
-    processingAddFilter: PropTypes.bool,
-    t: PropTypes.func,
+    isFilterAdded: PropTypes.bool.isRequired,
+    processingAddFilter: PropTypes.bool.isRequired,
+    t: PropTypes.func.isRequired,
 };
 
 export default withNamespaces()(Modal);
diff --git a/client/src/components/Filters/UserRules.js b/client/src/components/Filters/UserRules.js
index 39d39351..d2d78e91 100644
--- a/client/src/components/Filters/UserRules.js
+++ b/client/src/components/Filters/UserRules.js
@@ -15,13 +15,13 @@ class UserRules extends Component {
     };
 
     render() {
-        const { t } = this.props;
+        const { t, userRules } = this.props;
         return (
             <Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
                 <form onSubmit={this.handleSubmit}>
                     <textarea
                         className="form-control form-control--textarea-large"
-                        value={this.props.userRules}
+                        value={userRules}
                         onChange={this.handleChange}
                     />
                     <div className="card-actions">
@@ -79,10 +79,10 @@ class UserRules extends Component {
 }
 
 UserRules.propTypes = {
-    userRules: PropTypes.string,
-    handleRulesChange: PropTypes.func,
-    handleRulesSubmit: PropTypes.func,
-    t: PropTypes.func,
+    userRules: PropTypes.string.isRequired,
+    handleRulesChange: PropTypes.func.isRequired,
+    handleRulesSubmit: PropTypes.func.isRequired,
+    t: PropTypes.func.isRequired,
 };
 
 export default withNamespaces()(UserRules);
diff --git a/client/src/components/Filters/index.js b/client/src/components/Filters/index.js
index b95745ab..ba8213e5 100644
--- a/client/src/components/Filters/index.js
+++ b/client/src/components/Filters/index.js
@@ -1,11 +1,13 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
 import ReactTable from 'react-table';
 import PropTypes from 'prop-types';
 import { Trans, withNamespaces } from 'react-i18next';
-import Modal from './Modal';
+
 import PageTitle from '../ui/PageTitle';
 import Card from '../ui/Card';
+import CellWrap from '../ui/CellWrap';
 import UserRules from './UserRules';
+import Modal from './Modal';
 
 class Filters extends Component {
     componentDidMount() {
@@ -20,14 +22,19 @@ class Filters extends Component {
         this.props.setRules(this.props.filtering.userRules);
     };
 
-    renderCheckbox = (row) => {
-        const { url } = row.original;
-        const { filters } = this.props.filtering;
-        const filter = filters.filter(filter => filter.url === url)[0];
+    renderCheckbox = ({ original }) => {
+        const { processingConfigFilter } = this.props.filtering;
+        const { url, enabled } = original;
         return (
             <label className="checkbox">
-                <input type="checkbox" className="checkbox__input" onChange={() => this.props.toggleFilterStatus(filter.url)} checked={filter.enabled}/>
-                <span className="checkbox__label"/>
+                <input
+                    type="checkbox"
+                    className="checkbox__input"
+                    onChange={() => this.props.toggleFilterStatus(url, enabled)}
+                    checked={enabled}
+                    disabled={processingConfigFilter}
+                />
+                <span className="checkbox__label" />
             </label>
         );
     };
@@ -37,92 +44,131 @@ class Filters extends Component {
         if (window.confirm(this.props.t('filter_confirm_delete'))) {
             this.props.removeFilter({ url });
         }
-    }
+    };
 
-    columns = [{
-        Header: <Trans>enabled_table_header</Trans>,
-        accessor: 'enabled',
-        Cell: this.renderCheckbox,
-        width: 90,
-        className: 'text-center',
-    }, {
-        Header: <Trans>name_table_header</Trans>,
-        accessor: 'name',
-        Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
-    }, {
-        Header: <Trans>filter_url_table_header</Trans>,
-        accessor: 'url',
-        Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>),
-    }, {
-        Header: <Trans>rules_count_table_header</Trans>,
-        accessor: 'rulesCount',
-        className: 'text-center',
-        Cell: props => props.value.toLocaleString(),
-    }, {
-        Header: <Trans>last_time_updated_table_header</Trans>,
-        accessor: 'lastUpdated',
-        className: 'text-center',
-    }, {
-        Header: <Trans>actions_table_header</Trans>,
-        accessor: 'url',
-        Cell: ({ value }) => (
-            <button
-                type="button"
-                className="btn btn-icon btn-outline-secondary btn-sm"
-                onClick={() => this.handleDelete(value)}
-                title={this.props.t('delete_table_action')}
-            >
-                <svg className="icons">
-                    <use xlinkHref="#delete" />
-                </svg>
-            </button>
-        ),
-        className: 'text-center',
-        width: 80,
-        sortable: false,
-    },
+    columns = [
+        {
+            Header: <Trans>enabled_table_header</Trans>,
+            accessor: 'enabled',
+            Cell: this.renderCheckbox,
+            width: 90,
+            className: 'text-center',
+        },
+        {
+            Header: <Trans>name_table_header</Trans>,
+            accessor: 'name',
+            minWidth: 200,
+            Cell: CellWrap,
+        },
+        {
+            Header: <Trans>filter_url_table_header</Trans>,
+            accessor: 'url',
+            minWidth: 200,
+            Cell: ({ value }) => (
+                <div className="logs__row logs__row--overflow">
+                    <a
+                        href={value}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="link logs__text"
+                    >
+                        {value}
+                    </a>
+                </div>
+            ),
+        },
+        {
+            Header: <Trans>rules_count_table_header</Trans>,
+            accessor: 'rulesCount',
+            className: 'text-center',
+            minWidth: 100,
+            Cell: props => props.value.toLocaleString(),
+        },
+        {
+            Header: <Trans>last_time_updated_table_header</Trans>,
+            accessor: 'lastUpdated',
+            className: 'text-center',
+            minWidth: 150,
+            Cell: CellWrap,
+        },
+        {
+            Header: <Trans>actions_table_header</Trans>,
+            accessor: 'url',
+            Cell: ({ value }) => (
+                <button
+                    type="button"
+                    className="btn btn-icon btn-outline-secondary btn-sm"
+                    onClick={() => this.handleDelete(value)}
+                    title={this.props.t('delete_table_action')}
+                >
+                    <svg className="icons">
+                        <use xlinkHref="#delete" />
+                    </svg>
+                </button>
+            ),
+            className: 'text-center',
+            width: 80,
+            sortable: false,
+        },
     ];
 
     render() {
-        const { t } = this.props;
-        const { filters, userRules, processingRefreshFilters } = this.props.filtering;
+        const {
+            filtering, t, toggleFilteringModal, refreshFilters, addFilter,
+        } = this.props;
+        const {
+            filters,
+            userRules,
+            isModalOpen,
+            isFilterAdded,
+            processingRefreshFilters,
+            processingRemoveFilter,
+            processingAddFilter,
+            processingFilters,
+        } = filtering;
+
         return (
-            <div>
-                <PageTitle title={ t('filters') } />
+            <Fragment>
+                <PageTitle title={t('filters')} />
                 <div className="content">
                     <div className="row">
                         <div className="col-md-12">
                             <Card
-                                title={ t('filters_and_hosts') }
-                                subtitle={ t('filters_and_hosts_hint') }
+                                title={t('filters_and_hosts')}
+                                subtitle={t('filters_and_hosts_hint')}
                             >
                                 <ReactTable
                                     data={filters}
                                     columns={this.columns}
                                     showPagination={true}
                                     defaultPageSize={10}
+                                    loading={
+                                        processingFilters ||
+                                        processingAddFilter ||
+                                        processingRemoveFilter ||
+                                        processingRefreshFilters
+                                    }
                                     minRows={4}
-                                    // Text
-                                    previousText={ t('previous_btn') }
-                                    nextText={ t('next_btn') }
-                                    loadingText={ t('loading_table_status') }
-                                    pageText={ t('page_table_footer_text') }
-                                    ofText={ t('of_table_footer_text') }
-                                    rowsText={ t('rows_table_footer_text') }
-                                    noDataText={ t('no_filters_added') }
+                                    previousText={t('previous_btn')}
+                                    nextText={t('next_btn')}
+                                    loadingText={t('loading_table_status')}
+                                    pageText={t('page_table_footer_text')}
+                                    ofText={t('of_table_footer_text')}
+                                    rowsText={t('rows_table_footer_text')}
+                                    noDataText={t('no_filters_added')}
                                 />
                                 <div className="card-actions">
                                     <button
                                         className="btn btn-success btn-standard mr-2"
                                         type="submit"
-                                        onClick={this.props.toggleFilteringModal}
+                                        onClick={toggleFilteringModal}
                                     >
                                         <Trans>add_filter_btn</Trans>
                                     </button>
                                     <button
                                         className="btn btn-primary btn-standard"
                                         type="submit"
-                                        onClick={this.props.refreshFilters}
+                                        onClick={refreshFilters}
                                         disabled={processingRefreshFilters}
                                     >
                                         <Trans>check_updates_btn</Trans>
@@ -140,15 +186,13 @@ class Filters extends Component {
                     </div>
                 </div>
                 <Modal
-                    isOpen={this.props.filtering.isFilteringModalOpen}
-                    toggleModal={this.props.toggleFilteringModal}
-                    addFilter={this.props.addFilter}
-                    isFilterAdded={this.props.filtering.isFilterAdded}
-                    processingAddFilter={this.props.filtering.processingAddFilter}
-                    title={ t('new_filter_btn') }
-                    inputDescription={ t('enter_valid_filter_url') }
+                    isOpen={isModalOpen}
+                    toggleModal={toggleFilteringModal}
+                    addFilter={addFilter}
+                    isFilterAdded={isFilterAdded}
+                    processingAddFilter={processingAddFilter}
                 />
-            </div>
+            </Fragment>
         );
     }
 }
@@ -157,12 +201,15 @@ Filters.propTypes = {
     setRules: PropTypes.func,
     getFilteringStatus: PropTypes.func.isRequired,
     filtering: PropTypes.shape({
-        userRules: PropTypes.string,
-        filters: PropTypes.array,
-        isFilteringModalOpen: PropTypes.bool.isRequired,
-        isFilterAdded: PropTypes.bool,
-        processingAddFilter: PropTypes.bool,
-        processingRefreshFilters: PropTypes.bool,
+        userRules: PropTypes.string.isRequired,
+        filters: PropTypes.array.isRequired,
+        isModalOpen: PropTypes.bool.isRequired,
+        isFilterAdded: PropTypes.bool.isRequired,
+        processingFilters: PropTypes.bool.isRequired,
+        processingAddFilter: PropTypes.bool.isRequired,
+        processingRefreshFilters: PropTypes.bool.isRequired,
+        processingConfigFilter: PropTypes.bool.isRequired,
+        processingRemoveFilter: PropTypes.bool.isRequired,
     }),
     removeFilter: PropTypes.func.isRequired,
     toggleFilterStatus: PropTypes.func.isRequired,
@@ -170,8 +217,7 @@ Filters.propTypes = {
     toggleFilteringModal: PropTypes.func.isRequired,
     handleRulesChange: PropTypes.func.isRequired,
     refreshFilters: PropTypes.func.isRequired,
-    t: PropTypes.func,
+    t: PropTypes.func.isRequired,
 };
 
-
 export default withNamespaces()(Filters);
diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css
index 313879b6..a5c073af 100644
--- a/client/src/components/Logs/Logs.css
+++ b/client/src/components/Logs/Logs.css
@@ -36,6 +36,10 @@
     overflow: hidden;
 }
 
+.logs__text--full {
+    width: 100%;
+}
+
 .logs__row .tooltip-custom {
     top: 0;
     margin-left: 0;
diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js
index e4f880b0..3f1493af 100644
--- a/client/src/components/Logs/index.js
+++ b/client/src/components/Logs/index.js
@@ -6,7 +6,7 @@ import endsWith from 'lodash/endsWith';
 import { Trans, withNamespaces } from 'react-i18next';
 import { HashLink as Link } from 'react-router-hash-link';
 
-import { formatTime, getClientName } from '../../helpers/helpers';
+import { formatTime, formatDateTime, getClientName } from '../../helpers/helpers';
 import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
 import { getTrackerData } from '../../helpers/trackers/trackers';
 import PageTitle from '../ui/PageTitle';
@@ -114,7 +114,7 @@ class Logs extends Component {
 
     getTimeCell = ({ value }) => (
         <div className="logs__row">
-            <span className="logs__text" title={value}>
+            <span className="logs__text" title={formatDateTime(value)}>
                 {formatTime(value)}
             </span>
         </div>
@@ -227,7 +227,7 @@ class Logs extends Component {
             {
                 Header: t('time_table_header'),
                 accessor: 'time',
-                maxWidth: 90,
+                maxWidth: 100,
                 filterable: false,
                 Cell: this.getTimeCell,
             },
diff --git a/client/src/components/Settings/FiltersConfig/Form.js b/client/src/components/Settings/FiltersConfig/Form.js
new file mode 100644
index 00000000..1a5add1e
--- /dev/null
+++ b/client/src/components/Settings/FiltersConfig/Form.js
@@ -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);
diff --git a/client/src/components/Settings/FiltersConfig/index.js b/client/src/components/Settings/FiltersConfig/index.js
new file mode 100644
index 00000000..c1ae5798
--- /dev/null
+++ b/client/src/components/Settings/FiltersConfig/index.js
@@ -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);
diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css
index 0cc18f6e..284d70e2 100644
--- a/client/src/components/Settings/Settings.css
+++ b/client/src/components/Settings/Settings.css
@@ -11,6 +11,17 @@
     margin-bottom: 20px;
 }
 
+.form__group--inner {
+    max-width: 300px;
+    margin-top: -10px;
+    margin-left: 40px;
+    font-size: 14px;
+}
+
+.form__group--checkbox {
+    margin-bottom: 25px;
+}
+
 .form__inline {
     display: flex;
     justify-content: flex-start;
@@ -109,3 +120,11 @@
 .custom-control-label:before {
     transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
 }
+
+.custom-select:disabled {
+    background-color: #f9f9f9;
+}
+
+.custom-select {
+    transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
+}
diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js
index 2ca90782..936e05c9 100644
--- a/client/src/components/Settings/index.js
+++ b/client/src/components/Settings/index.js
@@ -5,6 +5,7 @@ import { withNamespaces } from 'react-i18next';
 import Services from './Services';
 import StatsConfig from './StatsConfig';
 import LogsConfig from './LogsConfig';
+import FiltersConfig from './FiltersConfig';
 import Checkbox from '../ui/Checkbox';
 import Loading from '../ui/Loading';
 import PageTitle from '../ui/PageTitle';
@@ -14,11 +15,6 @@ import './Settings.css';
 
 class Settings extends Component {
     settings = {
-        filtering: {
-            enabled: false,
-            title: 'block_domain_use_filters_and_hosts',
-            subtitle: 'filters_block_toggle_hint',
-        },
         safebrowsing: {
             enabled: false,
             title: 'use_adguard_browsing_sec',
@@ -41,17 +37,20 @@ class Settings extends Component {
         this.props.getBlockedServices();
         this.props.getStatsConfig();
         this.props.getLogsConfig();
+        this.props.getFilteringStatus();
     }
 
     renderSettings = (settings) => {
-        if (Object.keys(settings).length > 0) {
-            return Object.keys(settings).map((key) => {
+        const settingsKeys = Object.keys(settings);
+
+        if (settingsKeys.length > 0) {
+            return settingsKeys.map((key) => {
                 const setting = settings[key];
                 const { enabled } = setting;
                 return (
                     <Checkbox
-                        key={key}
                         {...settings[key]}
+                        key={key}
                         handleChange={() => this.props.toggleSetting(key, enabled)}
                     />
                 );
@@ -71,6 +70,8 @@ class Settings extends Component {
             queryLogs,
             setLogsConfig,
             clearLogs,
+            filtering,
+            setFiltersConfig,
             t,
         } = this.props;
 
@@ -90,6 +91,12 @@ class Settings extends Component {
                             <div className="col-md-12">
                                 <Card bodyType="card-body box-body--settings">
                                     <div className="form">
+                                        <FiltersConfig
+                                            interval={filtering.interval}
+                                            enabled={filtering.enabled}
+                                            processing={filtering.processingSetConfig}
+                                            setFiltersConfig={setFiltersConfig}
+                                        />
                                         {this.renderSettings(settings.settingsList)}
                                     </div>
                                 </Card>
@@ -134,6 +141,8 @@ Settings.propTypes = {
     getStatsConfig: PropTypes.func.isRequired,
     setStatsConfig: PropTypes.func.isRequired,
     resetStats: PropTypes.func.isRequired,
+    setFiltersConfig: PropTypes.func.isRequired,
+    getFilteringStatus: PropTypes.func.isRequired,
     t: PropTypes.func.isRequired,
 };
 
diff --git a/client/src/components/ui/CellWrap.js b/client/src/components/ui/CellWrap.js
new file mode 100644
index 00000000..93904fa5
--- /dev/null
+++ b/client/src/components/ui/CellWrap.js
@@ -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;
diff --git a/client/src/components/ui/Checkbox.css b/client/src/components/ui/Checkbox.css
index a39f70ec..ff657898 100644
--- a/client/src/components/ui/Checkbox.css
+++ b/client/src/components/ui/Checkbox.css
@@ -18,7 +18,7 @@
 }
 
 .checkbox--settings .checkbox__label-title {
-    margin-bottom: 5px;
+    margin-bottom: 2px;
     font-weight: 600;
 }
 
@@ -53,7 +53,7 @@
     background-position: center center;
     background-size: 12px 10px;
     border-radius: 3px;
-    transition: 0.3s ease box-shadow;
+    transition: 0.3s ease-in-out box-shadow, 0.3s ease-in-out opacity;
 }
 
 .checkbox__label .checkbox__label-text {
@@ -82,10 +82,13 @@
 }
 
 .checkbox__input:disabled + .checkbox__label {
-    opacity: 0.6;
     cursor: default;
 }
 
+.checkbox__input:disabled + .checkbox__label:before {
+    opacity: 0.6;
+}
+
 .checkbox__label-text {
     max-width: 515px;
     line-height: 1.5;
diff --git a/client/src/components/ui/Checkbox.js b/client/src/components/ui/Checkbox.js
index 79051457..3cb8aab6 100644
--- a/client/src/components/ui/Checkbox.js
+++ b/client/src/components/ui/Checkbox.js
@@ -14,7 +14,7 @@ class Checkbox extends Component {
             t,
         } = this.props;
         return (
-            <div className="form__group">
+            <div className="form__group form__group--checkbox">
                 <label className="checkbox checkbox--settings">
                 <span className="checkbox__marker"/>
                 <input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
diff --git a/client/src/containers/Filters.js b/client/src/containers/Filters.js
index 21e56f20..134c036c 100644
--- a/client/src/containers/Filters.js
+++ b/client/src/containers/Filters.js
@@ -1,5 +1,14 @@
 import { connect } from 'react-redux';
-import * as actionCreators from '../actions';
+import {
+    setRules,
+    getFilteringStatus,
+    addFilter,
+    removeFilter,
+    toggleFilterStatus,
+    toggleFilteringModal,
+    refreshFilters,
+    handleRulesChange,
+} from '../actions/filtering';
 import Filters from '../components/Filters';
 
 const mapStateToProps = (state) => {
@@ -8,7 +17,18 @@ const mapStateToProps = (state) => {
     return props;
 };
 
+const mapDispatchToProps = {
+    setRules,
+    getFilteringStatus,
+    addFilter,
+    removeFilter,
+    toggleFilterStatus,
+    toggleFilteringModal,
+    refreshFilters,
+    handleRulesChange,
+};
+
 export default connect(
     mapStateToProps,
-    actionCreators,
+    mapDispatchToProps,
 )(Filters);
diff --git a/client/src/containers/Logs.js b/client/src/containers/Logs.js
index b2e4c56d..c512f495 100644
--- a/client/src/containers/Logs.js
+++ b/client/src/containers/Logs.js
@@ -1,5 +1,6 @@
 import { connect } from 'react-redux';
-import { getFilteringStatus, setRules, addSuccessToast, getClients } from '../actions';
+import { addSuccessToast, getClients } from '../actions';
+import { getFilteringStatus, setRules } from '../actions/filtering';
 import { getLogs, getLogsConfig } from '../actions/queryLogs';
 import Logs from '../components/Logs';
 
diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js
index b78e140d..866765de 100644
--- a/client/src/containers/Settings.js
+++ b/client/src/containers/Settings.js
@@ -3,17 +3,19 @@ import { initSettings, toggleSetting } from '../actions';
 import { getBlockedServices, setBlockedServices } from '../actions/services';
 import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
 import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
+import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
 import Settings from '../components/Settings';
 
 const mapStateToProps = (state) => {
     const {
-        settings, services, stats, queryLogs,
+        settings, services, stats, queryLogs, filtering,
     } = state;
     const props = {
         settings,
         services,
         stats,
         queryLogs,
+        filtering,
     };
     return props;
 };
@@ -29,6 +31,8 @@ const mapDispatchToProps = {
     clearLogs,
     getLogsConfig,
     setLogsConfig,
+    getFilteringStatus,
+    setFiltersConfig,
 };
 
 export default connect(
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index 6001293a..997002fd 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -265,3 +265,5 @@ export const FILTERED_STATUS = {
 export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
 
 export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
+
+export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js
index e3b6e46c..cac9882e 100644
--- a/client/src/helpers/form.js
+++ b/client/src/helpers/form.js
@@ -32,28 +32,36 @@ export const renderRadioField = ({
 }) => (
     <Fragment>
         <label className="custom-control custom-radio custom-control-inline">
-            <input
-                {...input}
-                type="radio"
-                className="custom-control-input"
-                disabled={disabled}
-            />
+            <input {...input} type="radio" className="custom-control-input" disabled={disabled} />
             <span className="custom-control-label">{placeholder}</span>
         </label>
-        {!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
+        {!disabled &&
+            touched &&
+            (error && <span className="form__message form__message--error">{error}</span>)}
     </Fragment>
 );
 
 export const renderSelectField = ({
-    input, placeholder, disabled, meta: { touched, error },
+    input,
+    placeholder,
+    subtitle,
+    disabled,
+    modifier = 'checkbox--form',
+    meta: { touched, error },
 }) => (
     <Fragment>
-        <label className="checkbox checkbox--form">
+        <label className={`checkbox ${modifier}`}>
             <span className="checkbox__marker" />
             <input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
             <span className="checkbox__label">
                 <span className="checkbox__label-text checkbox__label-text--long">
                     <span className="checkbox__label-title">{placeholder}</span>
+                    {subtitle && (
+                        <span
+                            className="checkbox__label-subtitle"
+                            dangerouslySetInnerHTML={{ __html: subtitle }}
+                        />
+                    )}
                 </span>
             </span>
         </label>
@@ -64,7 +72,12 @@ export const renderSelectField = ({
 );
 
 export const renderServiceField = ({
-    input, placeholder, disabled, modifier, icon, meta: { touched, error },
+    input,
+    placeholder,
+    disabled,
+    modifier,
+    icon,
+    meta: { touched, error },
 }) => (
     <Fragment>
         <label className={`service custom-switch ${modifier}`}>
@@ -81,7 +94,9 @@ export const renderServiceField = ({
                 <use xlinkHref={`#${icon}`} />
             </svg>
         </label>
-        {!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
+        {!disabled &&
+            touched &&
+            (error && <span className="form__message form__message--error">{error}</span>)}
     </Fragment>
 );
 
diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js
index ad0e94dd..7f2ab78d 100644
--- a/client/src/helpers/helpers.js
+++ b/client/src/helpers/helpers.js
@@ -6,6 +6,7 @@ import addDays from 'date-fns/add_days';
 import subDays from 'date-fns/sub_days';
 import round from 'lodash/round';
 import axios from 'axios';
+import i18n from 'i18next';
 
 import {
     STANDARD_DNS_PORT,
@@ -19,6 +20,21 @@ export const formatTime = (time) => {
     return dateFormat(parsedTime, 'HH:mm:ss');
 };
 
+export const formatDateTime = (dateTime) => {
+    const currentLanguage = i18n.languages[0] || 'en';
+    const parsedTime = dateParse(dateTime);
+    const options = {
+        year: 'numeric',
+        month: 'numeric',
+        day: 'numeric',
+        hour: 'numeric',
+        minute: 'numeric',
+        hour12: false,
+    };
+
+    return parsedTime.toLocaleString(currentLanguage, options);
+};
+
 export const normalizeLogs = logs => logs.map((log) => {
     const {
         time,
@@ -74,18 +90,38 @@ export const normalizeTopStats = stats => (
 );
 
 export const normalizeFilteringStatus = (filteringStatus) => {
-    const { enabled, filters, user_rules: userRules } = filteringStatus;
-    const newFilters = filters ? filters.map((filter) => {
-        const {
-            id, url, enabled, lastUpdated: lastUpdated = Date.now(), name = 'Default name', rulesCount: rulesCount = 0,
-        } = filter;
+    const {
+        enabled, filters, user_rules: userRules, interval,
+    } = filteringStatus;
+    const newFilters = filters
+        ? filters.map((filter) => {
+            const {
+                id,
+                url,
+                enabled,
+                last_updated,
+                name = 'Default name',
+                rules_count: rules_count = 0,
+            } = filter;
 
-        return {
-            id, url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
-        };
-    }) : [];
+            return {
+                id,
+                url,
+                enabled,
+                lastUpdated: last_updated ? formatDateTime(last_updated) : '–',
+                name,
+                rulesCount: rules_count,
+            };
+        })
+        : [];
     const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
-    return { enabled, userRules: newUserRules, filters: newFilters };
+
+    return {
+        enabled,
+        userRules: newUserRules,
+        filters: newFilters,
+        interval,
+    };
 };
 
 export const getPercent = (amount, number) => {
@@ -241,3 +277,5 @@ export const secondsToMilliseconds = (seconds) => {
 
     return seconds;
 };
+
+export const normalizeRulesTextarea = text => text && text.replace(/^\n/g, '').replace(/\n\s*\n/g, '\n');
diff --git a/client/src/reducers/filtering.js b/client/src/reducers/filtering.js
new file mode 100644
index 00000000..4ed0acd8
--- /dev/null
+++ b/client/src/reducers/filtering.js
@@ -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;
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index 49e7729f..9339f042 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -13,6 +13,7 @@ import rewrites from './rewrites';
 import services from './services';
 import stats from './stats';
 import queryLogs from './queryLogs';
+import filtering from './filtering';
 
 const settings = handleActions({
     [actions.initSettingsRequest]: state => ({ ...state, processing: true }),
@@ -130,13 +131,6 @@ const dashboard = handleActions({
         return newState;
     },
 
-    [actions.getFilteringRequest]: state => ({ ...state, processingFiltering: true }),
-    [actions.getFilteringFailure]: state => ({ ...state, processingFiltering: false }),
-    [actions.getFilteringSuccess]: (state, { payload }) => {
-        const newState = { ...state, isFilteringEnabled: payload, processingFiltering: false };
-        return newState;
-    },
-
     [actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
     [actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
     [actions.toggleProtectionSuccess]: (state) => {
@@ -189,62 +183,6 @@ const dashboard = handleActions({
     autoClients: [],
 });
 
-const filtering = handleActions({
-    [actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
-    [actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
-    [actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
-
-    [actions.handleRulesChange]: (state, { payload }) => {
-        const { userRules } = payload;
-        return { ...state, userRules };
-    },
-
-    [actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
-    [actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
-    [actions.getFilteringStatusSuccess]: (state, { payload }) => {
-        const { status } = payload;
-        const { filters, userRules } = status;
-        const newState = {
-            ...state, filters, userRules, processingFilters: false,
-        };
-        return newState;
-    },
-
-    [actions.addFilterRequest]: state =>
-        ({ ...state, processingAddFilter: true, isFilterAdded: false }),
-    [actions.addFilterFailure]: (state) => {
-        const newState = { ...state, processingAddFilter: false, isFilterAdded: false };
-        return newState;
-    },
-    [actions.addFilterSuccess]: state =>
-        ({ ...state, processingAddFilter: false, isFilterAdded: true }),
-
-    [actions.toggleFilteringModal]: (state) => {
-        const newState = {
-            ...state,
-            isFilteringModalOpen: !state.isFilteringModalOpen,
-            isFilterAdded: false,
-        };
-        return newState;
-    },
-
-    [actions.toggleFilterRequest]: state => ({ ...state, processingFilters: true }),
-    [actions.toggleFilterFailure]: state => ({ ...state, processingFilters: false }),
-    [actions.toggleFilterSuccess]: state => ({ ...state, processingFilters: false }),
-
-    [actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
-    [actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
-    [actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
-}, {
-    isFilteringModalOpen: false,
-    processingFilters: false,
-    processingRules: false,
-    processingAddFilter: false,
-    processingRefreshFilters: false,
-    filters: [],
-    userRules: '',
-});
-
 const dhcp = handleActions({
     [actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
     [actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),