Merge: Add Filters Update Interval setting; refactor
Close #641 * commit 'd0fc1dc54dfbc017f28c6c0afa4623c6259af557': + client: handle filters configuration * openapi: update /filtering filtering: refactor; change API; add "filters_update_interval" setting
This commit is contained in:
commit
30ca77303b
|
@ -45,6 +45,11 @@ Contents:
|
||||||
* Query logs
|
* Query logs
|
||||||
* API: Set querylog parameters
|
* API: Set querylog parameters
|
||||||
* API: Get querylog parameters
|
* API: Get querylog parameters
|
||||||
|
* Filtering
|
||||||
|
* Filters update mechanism
|
||||||
|
* API: Get filtering parameters
|
||||||
|
* API: Set filtering parameters
|
||||||
|
* API: Set URL parameters
|
||||||
|
|
||||||
|
|
||||||
## Relations between subsystems
|
## Relations between subsystems
|
||||||
|
@ -1019,3 +1024,76 @@ Response:
|
||||||
"enabled": true | false
|
"enabled": true | false
|
||||||
"interval": 1 | 7 | 30 | 90
|
"interval": 1 | 7 | 30 | 90
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Filtering
|
||||||
|
|
||||||
|
### Filters update mechanism
|
||||||
|
|
||||||
|
Filters can be updated either manually by request from UI or automatically.
|
||||||
|
Auto-update interval can be configured in UI. If it is 0, auto-update is disabled.
|
||||||
|
When the last modification date of filter files is older than auto-update interval, auto-update procedure is started.
|
||||||
|
If an enabled filter file doesn't exist, it's downloaded on application startup. This includes the case when installation wizard is completed and there are no filter files yet.
|
||||||
|
When auto-update time comes, server starts the update procedure by downloading filter files. After new filter files are in place, it restarts DNS filtering module with new rules.
|
||||||
|
Only filters that are enabled by configuration can be updated.
|
||||||
|
As a result of the update procedure, all enabled filter files are written to disk, refreshed (their last modification date is equal to the current time) and loaded.
|
||||||
|
|
||||||
|
|
||||||
|
### API: Get filtering parameters
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
GET /control/filtering_info
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
{
|
||||||
|
"enabled": true | false
|
||||||
|
"interval": 0 | 1 | 12 | 1*24 || 3*24 || 7*24
|
||||||
|
"filters":[
|
||||||
|
{
|
||||||
|
"id":1
|
||||||
|
"enabled":true,
|
||||||
|
"url":"https://...",
|
||||||
|
"name":"...",
|
||||||
|
"rules_count":1234,
|
||||||
|
"last_updated":"2019-09-04T18:29:30+00:00",
|
||||||
|
}
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"user_rules":["...", ...]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### API: Set filtering parameters
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
POST /control/filtering_config
|
||||||
|
|
||||||
|
{
|
||||||
|
"enabled": true | false
|
||||||
|
"interval": 0 | 1 | 12 | 1*24 || 3*24 || 7*24
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
|
||||||
|
### API: Set URL parameters
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
POST /control/filtering/set_url
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "..."
|
||||||
|
"enabled": true | false
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
|
@ -378,5 +378,11 @@
|
||||||
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
|
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
|
||||||
"statistics_clear": " Clear statistics",
|
"statistics_clear": " Clear statistics",
|
||||||
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
|
"statistics_clear_confirm": "Are you sure you want to clear statistics?",
|
||||||
"statistics_cleared": "Statistics successfully cleared"
|
"statistics_cleared": "Statistics successfully cleared",
|
||||||
|
"interval_hours": "{{count}} hour",
|
||||||
|
"interval_hours_plural": "{{count}} hours",
|
||||||
|
"filters_configuration": "Filters configuration",
|
||||||
|
"filters_enable": "Enable filters",
|
||||||
|
"filters_interval": "Filters update interval",
|
||||||
|
"disabled": "Disabled"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
{
|
{
|
||||||
"client_settings": "Настройки клиентов",
|
"client_settings": "Настройки клиентов",
|
||||||
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
|
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
|
||||||
"upstream_parallel": "Использовать одновременные запроссы ко всем серверам для ускорения обработки запроса",
|
"upstream_parallel": "Использовать одновременные запросы ко всем серверам для ускорения обработки запроса",
|
||||||
"bootstrap_dns": "Bootstrap DNS-серверы",
|
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||||
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
||||||
"url_added_successfully": "URL успешно добавлен",
|
|
||||||
"check_dhcp_servers": "Проверить DHCP-серверы",
|
"check_dhcp_servers": "Проверить DHCP-серверы",
|
||||||
"save_config": "Сохранить конфигурацию",
|
"save_config": "Сохранить конфигурацию",
|
||||||
"enabled_dhcp": "DHCP-сервер включен",
|
"enabled_dhcp": "DHCP-сервер включен",
|
||||||
|
@ -67,7 +66,6 @@
|
||||||
"disabled_protection": "Защита выкл.",
|
"disabled_protection": "Защита выкл.",
|
||||||
"refresh_statics": "Обновить статистику",
|
"refresh_statics": "Обновить статистику",
|
||||||
"dns_query": "DNS-запросы",
|
"dns_query": "DNS-запросы",
|
||||||
"blocked_by": "Заблокировано фильтрами",
|
|
||||||
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
|
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
|
||||||
"stats_adult": "Заблокированные \"взрослые\" сайты",
|
"stats_adult": "Заблокированные \"взрослые\" сайты",
|
||||||
"stats_query_domain": "Часто запрашиваемые домены",
|
"stats_query_domain": "Часто запрашиваемые домены",
|
||||||
|
@ -78,7 +76,6 @@
|
||||||
"top_clients": "Частые клиенты",
|
"top_clients": "Частые клиенты",
|
||||||
"no_clients_found": "Клиентов не найдено",
|
"no_clients_found": "Клиентов не найдено",
|
||||||
"general_statistics": "Общая статистика",
|
"general_statistics": "Общая статистика",
|
||||||
"number_of_dns_query_24_hours": "Количество DNS-запросов за 24 часа",
|
|
||||||
"number_of_dns_query_blocked_24_hours": "Количество DNS-запросов, заблокированных фильтрами и блок-списками",
|
"number_of_dns_query_blocked_24_hours": "Количество DNS-запросов, заблокированных фильтрами и блок-списками",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
|
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Количество заблокированных \"сайтов для взрослых\"",
|
"number_of_dns_query_blocked_24_hours_adult": "Количество заблокированных \"сайтов для взрослых\"",
|
||||||
|
@ -211,7 +208,7 @@
|
||||||
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы \"DNS\" рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
|
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы \"DNS\" рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
|
||||||
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
||||||
"install_devices_windows_list_1": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
"install_devices_windows_list_1": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
||||||
"install_devices_windows_list_2": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
"install_devices_windows_list_2": "Перейдите в \"Сеть и интернет\", а затем в \"Центр управления сетями и общим доступом\"",
|
||||||
"install_devices_windows_list_3": "В левой стороне экрана найдите \"Изменение параметров адаптера\" и кликните по нему.",
|
"install_devices_windows_list_3": "В левой стороне экрана найдите \"Изменение параметров адаптера\" и кликните по нему.",
|
||||||
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
|
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
|
||||||
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
|
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
|
||||||
|
@ -298,7 +295,6 @@
|
||||||
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
||||||
"client_added": "Клиент \"{{key}}\" успешно добавлен",
|
"client_added": "Клиент \"{{key}}\" успешно добавлен",
|
||||||
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
|
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
|
||||||
"table_statistics": "Количество запросов (последние 24 часа)",
|
|
||||||
"clients_not_found": "Клиентов не найдено",
|
"clients_not_found": "Клиентов не найдено",
|
||||||
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
|
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
|
||||||
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
|
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
|
||||||
|
@ -309,7 +305,7 @@
|
||||||
"access_allowed_title": "Разрешенные клиенты",
|
"access_allowed_title": "Разрешенные клиенты",
|
||||||
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
|
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
|
||||||
"access_disallowed_title": "Запрещенные клиенты",
|
"access_disallowed_title": "Запрещенные клиенты",
|
||||||
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроек, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
|
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
|
||||||
"access_blocked_title": "Заблокированные домены",
|
"access_blocked_title": "Заблокированные домены",
|
||||||
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
|
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
|
||||||
"access_settings_saved": "Настройки доступа успешно сохранены",
|
"access_settings_saved": "Настройки доступа успешно сохранены",
|
||||||
|
@ -353,5 +349,13 @@
|
||||||
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
|
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
|
||||||
"blocked_service": "Заблокированный сервис",
|
"blocked_service": "Заблокированный сервис",
|
||||||
"block_all": "Заблокировать все",
|
"block_all": "Заблокировать все",
|
||||||
"unblock_all": "Разблокировать все"
|
"unblock_all": "Разблокировать все",
|
||||||
|
"domain": "Домен",
|
||||||
|
"answer": "Ответ",
|
||||||
|
"interval_hours_0": "{{count}} час",
|
||||||
|
"interval_hours_1": "{{count}} часа",
|
||||||
|
"interval_hours_2": "{{count}} часов",
|
||||||
|
"interval_days_0": "{{count}} день",
|
||||||
|
"interval_days_1": "{{count}} дня",
|
||||||
|
"interval_days_2": "{{count}} дней"
|
||||||
}
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||||
|
|
||||||
|
import { normalizeFilteringStatus, normalizeRulesTextarea } from '../helpers/helpers';
|
||||||
|
import { addErrorToast, addSuccessToast } from './index';
|
||||||
|
import apiClient from '../api/Api';
|
||||||
|
|
||||||
|
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
|
||||||
|
export const handleRulesChange = createAction('HANDLE_RULES_CHANGE');
|
||||||
|
|
||||||
|
export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQUEST');
|
||||||
|
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
|
||||||
|
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
|
||||||
|
|
||||||
|
export const getFilteringStatus = () => async (dispatch) => {
|
||||||
|
dispatch(getFilteringStatusRequest());
|
||||||
|
try {
|
||||||
|
const status = await apiClient.getFilteringStatus();
|
||||||
|
dispatch(getFilteringStatusSuccess({ ...normalizeFilteringStatus(status) }));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(getFilteringStatusFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setRulesRequest = createAction('SET_RULES_REQUEST');
|
||||||
|
export const setRulesFailure = createAction('SET_RULES_FAILURE');
|
||||||
|
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
|
||||||
|
|
||||||
|
export const setRules = rules => async (dispatch) => {
|
||||||
|
dispatch(setRulesRequest());
|
||||||
|
try {
|
||||||
|
const normalizedRules = normalizeRulesTextarea(rules);
|
||||||
|
await apiClient.setRules(normalizedRules);
|
||||||
|
dispatch(addSuccessToast('updated_custom_filtering_toast'));
|
||||||
|
dispatch(setRulesSuccess());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(setRulesFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
|
||||||
|
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
|
||||||
|
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
||||||
|
|
||||||
|
export const addFilter = (url, name) => async (dispatch) => {
|
||||||
|
dispatch(addFilterRequest());
|
||||||
|
try {
|
||||||
|
await apiClient.addFilter(url, name);
|
||||||
|
dispatch(addFilterSuccess(url));
|
||||||
|
dispatch(toggleFilteringModal());
|
||||||
|
dispatch(addSuccessToast('filter_added_successfully'));
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(addFilterFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeFilterRequest = createAction('REMOVE_FILTER_REQUEST');
|
||||||
|
export const removeFilterFailure = createAction('REMOVE_FILTER_FAILURE');
|
||||||
|
export const removeFilterSuccess = createAction('REMOVE_FILTER_SUCCESS');
|
||||||
|
|
||||||
|
export const removeFilter = url => async (dispatch) => {
|
||||||
|
dispatch(removeFilterRequest());
|
||||||
|
try {
|
||||||
|
await apiClient.removeFilter(url);
|
||||||
|
dispatch(removeFilterSuccess(url));
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(removeFilterFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST');
|
||||||
|
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
|
||||||
|
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
|
||||||
|
|
||||||
|
export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
|
||||||
|
dispatch(toggleFilterRequest());
|
||||||
|
try {
|
||||||
|
await apiClient.setFilterUrl({ url, enabled: !enabled });
|
||||||
|
dispatch(toggleFilterSuccess(url));
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(toggleFilterFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
|
||||||
|
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
||||||
|
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
||||||
|
|
||||||
|
export const refreshFilters = () => async (dispatch) => {
|
||||||
|
dispatch(refreshFiltersRequest());
|
||||||
|
dispatch(showLoading());
|
||||||
|
try {
|
||||||
|
const refreshText = await apiClient.refreshFilters();
|
||||||
|
dispatch(refreshFiltersSuccess());
|
||||||
|
|
||||||
|
if (refreshText.includes('OK')) {
|
||||||
|
if (refreshText.includes('OK 0')) {
|
||||||
|
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
|
||||||
|
} else {
|
||||||
|
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(addErrorToast({ error: refreshText }));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(getFilteringStatus());
|
||||||
|
dispatch(hideLoading());
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(refreshFiltersFailure());
|
||||||
|
dispatch(hideLoading());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFiltersConfigRequest = createAction('SET_FILTERS_CONFIG_REQUEST');
|
||||||
|
export const setFiltersConfigFailure = createAction('SET_FILTERS_CONFIG_FAILURE');
|
||||||
|
export const setFiltersConfigSuccess = createAction('SET_FILTERS_CONFIG_SUCCESS');
|
||||||
|
|
||||||
|
export const setFiltersConfig = config => async (dispatch, getState) => {
|
||||||
|
dispatch(setFiltersConfigRequest());
|
||||||
|
try {
|
||||||
|
const { enabled } = config;
|
||||||
|
const prevEnabled = getState().filtering.enabled;
|
||||||
|
let successToastMessage = 'config_successfully_saved';
|
||||||
|
|
||||||
|
if (prevEnabled !== enabled) {
|
||||||
|
successToastMessage = enabled ? 'enabled_filtering_toast' : 'disabled_filtering_toast';
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiClient.setFiltersConfig(config);
|
||||||
|
dispatch(addSuccessToast(successToastMessage));
|
||||||
|
dispatch(setFiltersConfigSuccess(config));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(setFiltersConfigFailure());
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,10 +1,9 @@
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import versionCompare from '../helpers/versionCompare';
|
import versionCompare from '../helpers/versionCompare';
|
||||||
import { normalizeFilteringStatus, normalizeTextarea, sortClients } from '../helpers/helpers';
|
import { normalizeTextarea, sortClients } from '../helpers/helpers';
|
||||||
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
|
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
|
||||||
import { getTlsStatus } from './encryption';
|
import { getTlsStatus } from './encryption';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
|
@ -21,16 +20,6 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||||
let successMessage = '';
|
let successMessage = '';
|
||||||
try {
|
try {
|
||||||
switch (settingKey) {
|
switch (settingKey) {
|
||||||
case SETTINGS_NAMES.filtering:
|
|
||||||
if (status) {
|
|
||||||
successMessage = 'disabled_filtering_toast';
|
|
||||||
await apiClient.disableFiltering();
|
|
||||||
} else {
|
|
||||||
successMessage = 'enabled_filtering_toast';
|
|
||||||
await apiClient.enableFiltering();
|
|
||||||
}
|
|
||||||
dispatch(toggleSettingStatus({ settingKey }));
|
|
||||||
break;
|
|
||||||
case SETTINGS_NAMES.safebrowsing:
|
case SETTINGS_NAMES.safebrowsing:
|
||||||
if (status) {
|
if (status) {
|
||||||
successMessage = 'disabled_safe_browsing_toast';
|
successMessage = 'disabled_safe_browsing_toast';
|
||||||
|
@ -77,18 +66,15 @@ export const initSettingsSuccess = createAction('SETTINGS_INIT_SUCCESS');
|
||||||
export const initSettings = settingsList => async (dispatch) => {
|
export const initSettings = settingsList => async (dispatch) => {
|
||||||
dispatch(initSettingsRequest());
|
dispatch(initSettingsRequest());
|
||||||
try {
|
try {
|
||||||
const filteringStatus = await apiClient.getFilteringStatus();
|
|
||||||
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
|
const safebrowsingStatus = await apiClient.getSafebrowsingStatus();
|
||||||
const parentalStatus = await apiClient.getParentalStatus();
|
const parentalStatus = await apiClient.getParentalStatus();
|
||||||
const safesearchStatus = await apiClient.getSafesearchStatus();
|
const safesearchStatus = await apiClient.getSafesearchStatus();
|
||||||
const {
|
const {
|
||||||
filtering,
|
|
||||||
safebrowsing,
|
safebrowsing,
|
||||||
parental,
|
parental,
|
||||||
safesearch,
|
safesearch,
|
||||||
} = settingsList;
|
} = settingsList;
|
||||||
const newSettingsList = {
|
const newSettingsList = {
|
||||||
filtering: { ...filtering, enabled: filteringStatus.enabled },
|
|
||||||
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
|
safebrowsing: { ...safebrowsing, enabled: safebrowsingStatus.enabled },
|
||||||
parental: { ...parental, enabled: parentalStatus.enabled },
|
parental: { ...parental, enabled: parentalStatus.enabled },
|
||||||
safesearch: { ...safesearch, enabled: safesearchStatus.enabled },
|
safesearch: { ...safesearch, enabled: safesearchStatus.enabled },
|
||||||
|
@ -100,21 +86,6 @@ export const initSettings = settingsList => async (dispatch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFilteringRequest = createAction('GET_FILTERING_REQUEST');
|
|
||||||
export const getFilteringFailure = createAction('GET_FILTERING_FAILURE');
|
|
||||||
export const getFilteringSuccess = createAction('GET_FILTERING_SUCCESS');
|
|
||||||
|
|
||||||
export const getFiltering = () => async (dispatch) => {
|
|
||||||
dispatch(getFilteringRequest());
|
|
||||||
try {
|
|
||||||
const filteringStatus = await apiClient.getFilteringStatus();
|
|
||||||
dispatch(getFilteringSuccess(filteringStatus.enabled));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(getFilteringFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
|
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
|
||||||
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
|
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
|
||||||
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
|
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
|
||||||
|
@ -290,133 +261,6 @@ export const disableDns = () => async (dispatch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setRulesRequest = createAction('SET_RULES_REQUEST');
|
|
||||||
export const setRulesFailure = createAction('SET_RULES_FAILURE');
|
|
||||||
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
|
|
||||||
|
|
||||||
export const setRules = rules => async (dispatch) => {
|
|
||||||
dispatch(setRulesRequest());
|
|
||||||
try {
|
|
||||||
const replacedLineEndings = rules
|
|
||||||
.replace(/^\n/g, '')
|
|
||||||
.replace(/\n\s*\n/g, '\n');
|
|
||||||
await apiClient.setRules(replacedLineEndings);
|
|
||||||
dispatch(addSuccessToast('updated_custom_filtering_toast'));
|
|
||||||
dispatch(setRulesSuccess());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(setRulesFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFilteringStatusRequest = createAction('GET_FILTERING_STATUS_REQUEST');
|
|
||||||
export const getFilteringStatusFailure = createAction('GET_FILTERING_STATUS_FAILURE');
|
|
||||||
export const getFilteringStatusSuccess = createAction('GET_FILTERING_STATUS_SUCCESS');
|
|
||||||
|
|
||||||
export const getFilteringStatus = () => async (dispatch) => {
|
|
||||||
dispatch(getFilteringStatusRequest());
|
|
||||||
try {
|
|
||||||
const status = await apiClient.getFilteringStatus();
|
|
||||||
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(getFilteringStatusFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toggleFilterRequest = createAction('FILTER_ENABLE_REQUEST');
|
|
||||||
export const toggleFilterFailure = createAction('FILTER_ENABLE_FAILURE');
|
|
||||||
export const toggleFilterSuccess = createAction('FILTER_ENABLE_SUCCESS');
|
|
||||||
|
|
||||||
export const toggleFilterStatus = url => async (dispatch, getState) => {
|
|
||||||
dispatch(toggleFilterRequest());
|
|
||||||
const state = getState();
|
|
||||||
const { filters } = state.filtering;
|
|
||||||
const filter = filters.filter(filter => filter.url === url)[0];
|
|
||||||
const { enabled } = filter;
|
|
||||||
let toggleStatusMethod;
|
|
||||||
if (enabled) {
|
|
||||||
toggleStatusMethod = apiClient.disableFilter.bind(apiClient);
|
|
||||||
} else {
|
|
||||||
toggleStatusMethod = apiClient.enableFilter.bind(apiClient);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await toggleStatusMethod(url);
|
|
||||||
dispatch(toggleFilterSuccess(url));
|
|
||||||
dispatch(getFilteringStatus());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(toggleFilterFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
|
|
||||||
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
|
||||||
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
|
||||||
|
|
||||||
export const refreshFilters = () => async (dispatch) => {
|
|
||||||
dispatch(refreshFiltersRequest());
|
|
||||||
dispatch(showLoading());
|
|
||||||
try {
|
|
||||||
const refreshText = await apiClient.refreshFilters();
|
|
||||||
dispatch(refreshFiltersSuccess());
|
|
||||||
|
|
||||||
if (refreshText.includes('OK')) {
|
|
||||||
if (refreshText.includes('OK 0')) {
|
|
||||||
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
|
|
||||||
} else {
|
|
||||||
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatch(addErrorToast({ error: refreshText }));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(getFilteringStatus());
|
|
||||||
dispatch(hideLoading());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(refreshFiltersFailure());
|
|
||||||
dispatch(hideLoading());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleRulesChange = createAction('HANDLE_RULES_CHANGE');
|
|
||||||
|
|
||||||
export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
|
|
||||||
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
|
|
||||||
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
|
||||||
|
|
||||||
export const addFilter = (url, name) => async (dispatch) => {
|
|
||||||
dispatch(addFilterRequest());
|
|
||||||
try {
|
|
||||||
await apiClient.addFilter(url, name);
|
|
||||||
dispatch(addFilterSuccess(url));
|
|
||||||
dispatch(getFilteringStatus());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(addFilterFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const removeFilterRequest = createAction('ADD_FILTER_REQUEST');
|
|
||||||
export const removeFilterFailure = createAction('ADD_FILTER_FAILURE');
|
|
||||||
export const removeFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
|
||||||
|
|
||||||
export const removeFilter = url => async (dispatch) => {
|
|
||||||
dispatch(removeFilterRequest());
|
|
||||||
try {
|
|
||||||
await apiClient.removeFilter(url);
|
|
||||||
dispatch(removeFilterSuccess(url));
|
|
||||||
dispatch(getFilteringStatus());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(addErrorToast({ error }));
|
|
||||||
dispatch(removeFilterFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
|
|
||||||
|
|
||||||
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
|
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
|
||||||
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||||
|
|
|
@ -90,32 +90,19 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtering
|
// Filtering
|
||||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
FILTERING_INFO = { path: 'filtering_info', method: 'GET' };
|
||||||
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
|
|
||||||
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
|
|
||||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
||||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
||||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
|
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
|
||||||
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
|
|
||||||
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
|
|
||||||
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
||||||
|
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
|
||||||
|
FILTERING_CONFIG = { path: 'filtering_config', method: 'POST' };
|
||||||
|
|
||||||
getFilteringStatus() {
|
getFilteringStatus() {
|
||||||
const { path, method } = this.FILTERING_STATUS;
|
const { path, method } = this.FILTERING_INFO;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFiltering() {
|
|
||||||
const { path, method } = this.FILTERING_ENABLE;
|
|
||||||
return this.makeRequest(path, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
disableFiltering() {
|
|
||||||
const { path, method } = this.FILTERING_DISABLE;
|
|
||||||
return this.makeRequest(path, method);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO find out when to use force parameter
|
|
||||||
refreshFilters() {
|
refreshFilters() {
|
||||||
const { path, method } = this.FILTERING_REFRESH;
|
const { path, method } = this.FILTERING_REFRESH;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
|
@ -151,26 +138,22 @@ class Api {
|
||||||
return this.makeRequest(path, method, parameters);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFilter(url) {
|
setFiltersConfig(config) {
|
||||||
const { path, method } = this.FILTERING_ENABLE_FILTER;
|
const { path, method } = this.FILTERING_CONFIG;
|
||||||
const parameter = 'url';
|
const parameters = {
|
||||||
const requestBody = `${parameter}=${url}`;
|
data: config,
|
||||||
const config = {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
data: requestBody,
|
|
||||||
header: { 'Content-Type': 'text/plain' },
|
|
||||||
};
|
};
|
||||||
return this.makeRequest(path, method, config);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableFilter(url) {
|
setFilterUrl(config) {
|
||||||
const { path, method } = this.FILTERING_DISABLE_FILTER;
|
const { path, method } = this.FILTERING_SET_URL;
|
||||||
const parameter = 'url';
|
const parameters = {
|
||||||
const requestBody = `${parameter}=${url}`;
|
data: config,
|
||||||
const config = {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
data: requestBody,
|
|
||||||
header: { 'Content-Type': 'text/plain' },
|
|
||||||
};
|
};
|
||||||
return this.makeRequest(path, method, config);
|
return this.makeRequest(path, method, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parental
|
// Parental
|
||||||
|
|
|
@ -33,27 +33,13 @@ class Modal extends Component {
|
||||||
this.setState({ ...this.state, name });
|
this.setState({ ...this.state, name });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNext = () => {
|
|
||||||
this.props.addFilter(this.state.url, this.state.name);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.props.isFilterAdded) {
|
|
||||||
this.closeModal();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
this.props.toggleModal();
|
this.props.toggleModal();
|
||||||
this.setState({ ...this.state, ...initialState });
|
this.setState({ ...this.state, ...initialState });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isOpen, processingAddFilter } = this.props;
|
||||||
isOpen,
|
|
||||||
title,
|
|
||||||
inputDescription,
|
|
||||||
processingAddFilter,
|
|
||||||
} = this.props;
|
|
||||||
const { isUrlValid, url, name } = this.state;
|
const { isUrlValid, url, name } = this.state;
|
||||||
const inputUrlClass = classnames({
|
const inputUrlClass = classnames({
|
||||||
'form-control mb-2': true,
|
'form-control mb-2': true,
|
||||||
|
@ -64,28 +50,7 @@ class Modal extends Component {
|
||||||
'form-control mb-2': true,
|
'form-control mb-2': true,
|
||||||
'is-valid': name.length > 0,
|
'is-valid': name.length > 0,
|
||||||
});
|
});
|
||||||
|
const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0;
|
||||||
const renderBody = () => {
|
|
||||||
if (!this.props.isFilterAdded) {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
|
|
||||||
<input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
|
|
||||||
{inputDescription &&
|
|
||||||
<div className="description">
|
|
||||||
{inputDescription}
|
|
||||||
</div>}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="description">
|
|
||||||
<Trans>filter_added_successfully</Trans>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isValidForSubmit = !(url.length > 0 && isUrlValid && name.length > 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
|
@ -97,16 +62,29 @@ class Modal extends Component {
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h4 className="modal-title">
|
<h4 className="modal-title">
|
||||||
{title}
|
<Trans>new_filter_btn</Trans>
|
||||||
</h4>
|
</h4>
|
||||||
<button type="button" className="close" onClick={this.closeModal}>
|
<button type="button" className="close" onClick={this.closeModal}>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{renderBody()}
|
<input
|
||||||
|
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>
|
||||||
{!this.props.isFilterAdded &&
|
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -118,13 +96,12 @@ class Modal extends Component {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-success"
|
className="btn btn-success"
|
||||||
onClick={this.handleNext}
|
onClick={() => this.props.addFilter(url, name)}
|
||||||
disabled={isValidForSubmit || processingAddFilter}
|
disabled={!isValidForSubmit || processingAddFilter}
|
||||||
>
|
>
|
||||||
<Trans>add_filter_btn</Trans>
|
<Trans>add_filter_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
);
|
);
|
||||||
|
@ -134,12 +111,10 @@ class Modal extends Component {
|
||||||
Modal.propTypes = {
|
Modal.propTypes = {
|
||||||
toggleModal: PropTypes.func.isRequired,
|
toggleModal: PropTypes.func.isRequired,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
inputDescription: PropTypes.string,
|
|
||||||
addFilter: PropTypes.func.isRequired,
|
addFilter: PropTypes.func.isRequired,
|
||||||
isFilterAdded: PropTypes.bool,
|
isFilterAdded: PropTypes.bool.isRequired,
|
||||||
processingAddFilter: PropTypes.bool,
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNamespaces()(Modal);
|
export default withNamespaces()(Modal);
|
||||||
|
|
|
@ -15,13 +15,13 @@ class UserRules extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const { t, userRules } = this.props;
|
||||||
return (
|
return (
|
||||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control form-control--textarea-large"
|
className="form-control form-control--textarea-large"
|
||||||
value={this.props.userRules}
|
value={userRules}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
|
@ -79,10 +79,10 @@ class UserRules extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserRules.propTypes = {
|
UserRules.propTypes = {
|
||||||
userRules: PropTypes.string,
|
userRules: PropTypes.string.isRequired,
|
||||||
handleRulesChange: PropTypes.func,
|
handleRulesChange: PropTypes.func.isRequired,
|
||||||
handleRulesSubmit: PropTypes.func,
|
handleRulesSubmit: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNamespaces()(UserRules);
|
export default withNamespaces()(UserRules);
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import Modal from './Modal';
|
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
|
import CellWrap from '../ui/CellWrap';
|
||||||
import UserRules from './UserRules';
|
import UserRules from './UserRules';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
class Filters extends Component {
|
class Filters extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -20,13 +22,18 @@ class Filters extends Component {
|
||||||
this.props.setRules(this.props.filtering.userRules);
|
this.props.setRules(this.props.filtering.userRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderCheckbox = (row) => {
|
renderCheckbox = ({ original }) => {
|
||||||
const { url } = row.original;
|
const { processingConfigFilter } = this.props.filtering;
|
||||||
const { filters } = this.props.filtering;
|
const { url, enabled } = original;
|
||||||
const filter = filters.filter(filter => filter.url === url)[0];
|
|
||||||
return (
|
return (
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input type="checkbox" className="checkbox__input" onChange={() => this.props.toggleFilterStatus(filter.url)} checked={filter.enabled}/>
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox__input"
|
||||||
|
onChange={() => this.props.toggleFilterStatus(url, enabled)}
|
||||||
|
checked={enabled}
|
||||||
|
disabled={processingConfigFilter}
|
||||||
|
/>
|
||||||
<span className="checkbox__label" />
|
<span className="checkbox__label" />
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
@ -37,32 +44,54 @@ class Filters extends Component {
|
||||||
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
||||||
this.props.removeFilter({ url });
|
this.props.removeFilter({ url });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
columns = [{
|
columns = [
|
||||||
|
{
|
||||||
Header: <Trans>enabled_table_header</Trans>,
|
Header: <Trans>enabled_table_header</Trans>,
|
||||||
accessor: 'enabled',
|
accessor: 'enabled',
|
||||||
Cell: this.renderCheckbox,
|
Cell: this.renderCheckbox,
|
||||||
width: 90,
|
width: 90,
|
||||||
className: 'text-center',
|
className: 'text-center',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Header: <Trans>name_table_header</Trans>,
|
Header: <Trans>name_table_header</Trans>,
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
|
minWidth: 200,
|
||||||
}, {
|
Cell: CellWrap,
|
||||||
|
},
|
||||||
|
{
|
||||||
Header: <Trans>filter_url_table_header</Trans>,
|
Header: <Trans>filter_url_table_header</Trans>,
|
||||||
accessor: 'url',
|
accessor: 'url',
|
||||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>),
|
minWidth: 200,
|
||||||
}, {
|
Cell: ({ value }) => (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<a
|
||||||
|
href={value}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="link logs__text"
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
Header: <Trans>rules_count_table_header</Trans>,
|
Header: <Trans>rules_count_table_header</Trans>,
|
||||||
accessor: 'rulesCount',
|
accessor: 'rulesCount',
|
||||||
className: 'text-center',
|
className: 'text-center',
|
||||||
|
minWidth: 100,
|
||||||
Cell: props => props.value.toLocaleString(),
|
Cell: props => props.value.toLocaleString(),
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Header: <Trans>last_time_updated_table_header</Trans>,
|
Header: <Trans>last_time_updated_table_header</Trans>,
|
||||||
accessor: 'lastUpdated',
|
accessor: 'lastUpdated',
|
||||||
className: 'text-center',
|
className: 'text-center',
|
||||||
}, {
|
minWidth: 150,
|
||||||
|
Cell: CellWrap,
|
||||||
|
},
|
||||||
|
{
|
||||||
Header: <Trans>actions_table_header</Trans>,
|
Header: <Trans>actions_table_header</Trans>,
|
||||||
accessor: 'url',
|
accessor: 'url',
|
||||||
Cell: ({ value }) => (
|
Cell: ({ value }) => (
|
||||||
|
@ -84,10 +113,22 @@ class Filters extends Component {
|
||||||
];
|
];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const {
|
||||||
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
|
filtering, t, toggleFilteringModal, refreshFilters, addFilter,
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
filters,
|
||||||
|
userRules,
|
||||||
|
isModalOpen,
|
||||||
|
isFilterAdded,
|
||||||
|
processingRefreshFilters,
|
||||||
|
processingRemoveFilter,
|
||||||
|
processingAddFilter,
|
||||||
|
processingFilters,
|
||||||
|
} = filtering;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
<PageTitle title={t('filters')} />
|
<PageTitle title={t('filters')} />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
@ -101,8 +142,13 @@ class Filters extends Component {
|
||||||
columns={this.columns}
|
columns={this.columns}
|
||||||
showPagination={true}
|
showPagination={true}
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
|
loading={
|
||||||
|
processingFilters ||
|
||||||
|
processingAddFilter ||
|
||||||
|
processingRemoveFilter ||
|
||||||
|
processingRefreshFilters
|
||||||
|
}
|
||||||
minRows={4}
|
minRows={4}
|
||||||
// Text
|
|
||||||
previousText={t('previous_btn')}
|
previousText={t('previous_btn')}
|
||||||
nextText={t('next_btn')}
|
nextText={t('next_btn')}
|
||||||
loadingText={t('loading_table_status')}
|
loadingText={t('loading_table_status')}
|
||||||
|
@ -115,14 +161,14 @@ class Filters extends Component {
|
||||||
<button
|
<button
|
||||||
className="btn btn-success btn-standard mr-2"
|
className="btn btn-success btn-standard mr-2"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={this.props.toggleFilteringModal}
|
onClick={toggleFilteringModal}
|
||||||
>
|
>
|
||||||
<Trans>add_filter_btn</Trans>
|
<Trans>add_filter_btn</Trans>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-standard"
|
className="btn btn-primary btn-standard"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={this.props.refreshFilters}
|
onClick={refreshFilters}
|
||||||
disabled={processingRefreshFilters}
|
disabled={processingRefreshFilters}
|
||||||
>
|
>
|
||||||
<Trans>check_updates_btn</Trans>
|
<Trans>check_updates_btn</Trans>
|
||||||
|
@ -140,15 +186,13 @@ class Filters extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={this.props.filtering.isFilteringModalOpen}
|
isOpen={isModalOpen}
|
||||||
toggleModal={this.props.toggleFilteringModal}
|
toggleModal={toggleFilteringModal}
|
||||||
addFilter={this.props.addFilter}
|
addFilter={addFilter}
|
||||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
isFilterAdded={isFilterAdded}
|
||||||
processingAddFilter={this.props.filtering.processingAddFilter}
|
processingAddFilter={processingAddFilter}
|
||||||
title={ t('new_filter_btn') }
|
|
||||||
inputDescription={ t('enter_valid_filter_url') }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,12 +201,15 @@ Filters.propTypes = {
|
||||||
setRules: PropTypes.func,
|
setRules: PropTypes.func,
|
||||||
getFilteringStatus: PropTypes.func.isRequired,
|
getFilteringStatus: PropTypes.func.isRequired,
|
||||||
filtering: PropTypes.shape({
|
filtering: PropTypes.shape({
|
||||||
userRules: PropTypes.string,
|
userRules: PropTypes.string.isRequired,
|
||||||
filters: PropTypes.array,
|
filters: PropTypes.array.isRequired,
|
||||||
isFilteringModalOpen: PropTypes.bool.isRequired,
|
isModalOpen: PropTypes.bool.isRequired,
|
||||||
isFilterAdded: PropTypes.bool,
|
isFilterAdded: PropTypes.bool.isRequired,
|
||||||
processingAddFilter: PropTypes.bool,
|
processingFilters: PropTypes.bool.isRequired,
|
||||||
processingRefreshFilters: PropTypes.bool,
|
processingAddFilter: PropTypes.bool.isRequired,
|
||||||
|
processingRefreshFilters: PropTypes.bool.isRequired,
|
||||||
|
processingConfigFilter: PropTypes.bool.isRequired,
|
||||||
|
processingRemoveFilter: PropTypes.bool.isRequired,
|
||||||
}),
|
}),
|
||||||
removeFilter: PropTypes.func.isRequired,
|
removeFilter: PropTypes.func.isRequired,
|
||||||
toggleFilterStatus: PropTypes.func.isRequired,
|
toggleFilterStatus: PropTypes.func.isRequired,
|
||||||
|
@ -170,8 +217,7 @@ Filters.propTypes = {
|
||||||
toggleFilteringModal: PropTypes.func.isRequired,
|
toggleFilteringModal: PropTypes.func.isRequired,
|
||||||
handleRulesChange: PropTypes.func.isRequired,
|
handleRulesChange: PropTypes.func.isRequired,
|
||||||
refreshFilters: PropTypes.func.isRequired,
|
refreshFilters: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default withNamespaces()(Filters);
|
export default withNamespaces()(Filters);
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__text--full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.logs__row .tooltip-custom {
|
.logs__row .tooltip-custom {
|
||||||
top: 0;
|
top: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import endsWith from 'lodash/endsWith';
|
||||||
import { Trans, withNamespaces } from 'react-i18next';
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
import { HashLink as Link } from 'react-router-hash-link';
|
import { HashLink as Link } from 'react-router-hash-link';
|
||||||
|
|
||||||
import { formatTime, getClientName } from '../../helpers/helpers';
|
import { formatTime, formatDateTime, getClientName } from '../../helpers/helpers';
|
||||||
import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
|
import { SERVICES, FILTERED_STATUS } from '../../helpers/constants';
|
||||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
|
@ -114,7 +114,7 @@ class Logs extends Component {
|
||||||
|
|
||||||
getTimeCell = ({ value }) => (
|
getTimeCell = ({ value }) => (
|
||||||
<div className="logs__row">
|
<div className="logs__row">
|
||||||
<span className="logs__text" title={value}>
|
<span className="logs__text" title={formatDateTime(value)}>
|
||||||
{formatTime(value)}
|
{formatTime(value)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -227,7 +227,7 @@ class Logs extends Component {
|
||||||
{
|
{
|
||||||
Header: t('time_table_header'),
|
Header: t('time_table_header'),
|
||||||
accessor: 'time',
|
accessor: 'time',
|
||||||
maxWidth: 90,
|
maxWidth: 100,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
Cell: this.getTimeCell,
|
Cell: this.getTimeCell,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Field, reduxForm } from 'redux-form';
|
||||||
|
import { Trans, withNamespaces } from 'react-i18next';
|
||||||
|
import flow from 'lodash/flow';
|
||||||
|
|
||||||
|
import { renderSelectField, toNumber } from '../../../helpers/form';
|
||||||
|
import { FILTERS_INTERVALS_HOURS } from '../../../helpers/constants';
|
||||||
|
|
||||||
|
const getTitleForInterval = (interval, t) => {
|
||||||
|
if (interval === 0) {
|
||||||
|
return t('disabled');
|
||||||
|
} else if (interval === 72 || interval === 168) {
|
||||||
|
return t('interval_days', { count: interval / 24 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('interval_hours', { count: interval });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIntervalSelect = (processing, t, handleChange, toNumber) => (
|
||||||
|
<Field
|
||||||
|
name="interval"
|
||||||
|
className="custom-select"
|
||||||
|
component="select"
|
||||||
|
onChange={handleChange}
|
||||||
|
normalize={toNumber}
|
||||||
|
disabled={processing}
|
||||||
|
>
|
||||||
|
{FILTERS_INTERVALS_HOURS.map(interval => (
|
||||||
|
<option value={interval} key={interval}>
|
||||||
|
{getTitleForInterval(interval, t)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Form = (props) => {
|
||||||
|
const {
|
||||||
|
handleSubmit, handleChange, processing, t,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name="enabled"
|
||||||
|
type="checkbox"
|
||||||
|
modifier="checkbox--settings"
|
||||||
|
component={renderSelectField}
|
||||||
|
placeholder={t('block_domain_use_filters_and_hosts')}
|
||||||
|
subtitle={t('filters_block_toggle_hint')}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={processing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-5">
|
||||||
|
<div className="form__group form__group--inner mb-5">
|
||||||
|
<label className="form__label">
|
||||||
|
<Trans>filters_interval</Trans>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{getIntervalSelect(processing, t, handleChange, toNumber)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Form.propTypes = {
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
handleChange: PropTypes.func,
|
||||||
|
change: PropTypes.func.isRequired,
|
||||||
|
submitting: PropTypes.bool.isRequired,
|
||||||
|
invalid: PropTypes.bool.isRequired,
|
||||||
|
processing: PropTypes.bool.isRequired,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default flow([
|
||||||
|
withNamespaces(),
|
||||||
|
reduxForm({
|
||||||
|
form: 'filterConfigForm',
|
||||||
|
}),
|
||||||
|
])(Form);
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { withNamespaces } from 'react-i18next';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
|
||||||
|
import Form from './Form';
|
||||||
|
|
||||||
|
class FiltersConfig extends Component {
|
||||||
|
handleFormChange = debounce((values) => {
|
||||||
|
this.props.setFiltersConfig(values);
|
||||||
|
}, DEBOUNCE_TIMEOUT);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { interval, enabled, processing } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
initialValues={{ interval, enabled }}
|
||||||
|
onSubmit={this.handleFormChange}
|
||||||
|
onChange={this.handleFormChange}
|
||||||
|
processing={processing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FiltersConfig.propTypes = {
|
||||||
|
interval: PropTypes.number.isRequired,
|
||||||
|
enabled: PropTypes.bool.isRequired,
|
||||||
|
processing: PropTypes.bool.isRequired,
|
||||||
|
setFiltersConfig: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withNamespaces()(FiltersConfig);
|
|
@ -11,6 +11,17 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form__group--inner {
|
||||||
|
max-width: 300px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__group--checkbox {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
.form__inline {
|
.form__inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -109,3 +120,11 @@
|
||||||
.custom-control-label:before {
|
.custom-control-label:before {
|
||||||
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
|
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-select:disabled {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select {
|
||||||
|
transition: 0.3s ease-in-out background-color, 0.3s ease-in-out color;
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { withNamespaces } from 'react-i18next';
|
||||||
import Services from './Services';
|
import Services from './Services';
|
||||||
import StatsConfig from './StatsConfig';
|
import StatsConfig from './StatsConfig';
|
||||||
import LogsConfig from './LogsConfig';
|
import LogsConfig from './LogsConfig';
|
||||||
|
import FiltersConfig from './FiltersConfig';
|
||||||
import Checkbox from '../ui/Checkbox';
|
import Checkbox from '../ui/Checkbox';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
|
@ -14,11 +15,6 @@ import './Settings.css';
|
||||||
|
|
||||||
class Settings extends Component {
|
class Settings extends Component {
|
||||||
settings = {
|
settings = {
|
||||||
filtering: {
|
|
||||||
enabled: false,
|
|
||||||
title: 'block_domain_use_filters_and_hosts',
|
|
||||||
subtitle: 'filters_block_toggle_hint',
|
|
||||||
},
|
|
||||||
safebrowsing: {
|
safebrowsing: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
title: 'use_adguard_browsing_sec',
|
title: 'use_adguard_browsing_sec',
|
||||||
|
@ -41,17 +37,20 @@ class Settings extends Component {
|
||||||
this.props.getBlockedServices();
|
this.props.getBlockedServices();
|
||||||
this.props.getStatsConfig();
|
this.props.getStatsConfig();
|
||||||
this.props.getLogsConfig();
|
this.props.getLogsConfig();
|
||||||
|
this.props.getFilteringStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSettings = (settings) => {
|
renderSettings = (settings) => {
|
||||||
if (Object.keys(settings).length > 0) {
|
const settingsKeys = Object.keys(settings);
|
||||||
return Object.keys(settings).map((key) => {
|
|
||||||
|
if (settingsKeys.length > 0) {
|
||||||
|
return settingsKeys.map((key) => {
|
||||||
const setting = settings[key];
|
const setting = settings[key];
|
||||||
const { enabled } = setting;
|
const { enabled } = setting;
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={key}
|
|
||||||
{...settings[key]}
|
{...settings[key]}
|
||||||
|
key={key}
|
||||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -71,6 +70,8 @@ class Settings extends Component {
|
||||||
queryLogs,
|
queryLogs,
|
||||||
setLogsConfig,
|
setLogsConfig,
|
||||||
clearLogs,
|
clearLogs,
|
||||||
|
filtering,
|
||||||
|
setFiltersConfig,
|
||||||
t,
|
t,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -90,6 +91,12 @@ class Settings extends Component {
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<Card bodyType="card-body box-body--settings">
|
<Card bodyType="card-body box-body--settings">
|
||||||
<div className="form">
|
<div className="form">
|
||||||
|
<FiltersConfig
|
||||||
|
interval={filtering.interval}
|
||||||
|
enabled={filtering.enabled}
|
||||||
|
processing={filtering.processingSetConfig}
|
||||||
|
setFiltersConfig={setFiltersConfig}
|
||||||
|
/>
|
||||||
{this.renderSettings(settings.settingsList)}
|
{this.renderSettings(settings.settingsList)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -134,6 +141,8 @@ Settings.propTypes = {
|
||||||
getStatsConfig: PropTypes.func.isRequired,
|
getStatsConfig: PropTypes.func.isRequired,
|
||||||
setStatsConfig: PropTypes.func.isRequired,
|
setStatsConfig: PropTypes.func.isRequired,
|
||||||
resetStats: PropTypes.func.isRequired,
|
resetStats: PropTypes.func.isRequired,
|
||||||
|
setFiltersConfig: PropTypes.func.isRequired,
|
||||||
|
getFilteringStatus: PropTypes.func.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const CellWrap = ({ value }) => (
|
||||||
|
<div className="logs__row logs__row--overflow">
|
||||||
|
<span className="logs__text logs__text--full" title={value}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
CellWrap.propTypes = {
|
||||||
|
value: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CellWrap;
|
|
@ -18,7 +18,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox--settings .checkbox__label-title {
|
.checkbox--settings .checkbox__label-title {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 2px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: 12px 10px;
|
background-size: 12px 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition: 0.3s ease box-shadow;
|
transition: 0.3s ease-in-out box-shadow, 0.3s ease-in-out opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox__label .checkbox__label-text {
|
.checkbox__label .checkbox__label-text {
|
||||||
|
@ -82,10 +82,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label {
|
.checkbox__input:disabled + .checkbox__label {
|
||||||
opacity: 0.6;
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox__input:disabled + .checkbox__label:before {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox__label-text {
|
.checkbox__label-text {
|
||||||
max-width: 515px;
|
max-width: 515px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Checkbox extends Component {
|
||||||
t,
|
t,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="form__group">
|
<div className="form__group form__group--checkbox">
|
||||||
<label className="checkbox checkbox--settings">
|
<label className="checkbox checkbox--settings">
|
||||||
<span className="checkbox__marker"/>
|
<span className="checkbox__marker"/>
|
||||||
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as actionCreators from '../actions';
|
import {
|
||||||
|
setRules,
|
||||||
|
getFilteringStatus,
|
||||||
|
addFilter,
|
||||||
|
removeFilter,
|
||||||
|
toggleFilterStatus,
|
||||||
|
toggleFilteringModal,
|
||||||
|
refreshFilters,
|
||||||
|
handleRulesChange,
|
||||||
|
} from '../actions/filtering';
|
||||||
import Filters from '../components/Filters';
|
import Filters from '../components/Filters';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
|
@ -8,7 +17,18 @@ const mapStateToProps = (state) => {
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setRules,
|
||||||
|
getFilteringStatus,
|
||||||
|
addFilter,
|
||||||
|
removeFilter,
|
||||||
|
toggleFilterStatus,
|
||||||
|
toggleFilteringModal,
|
||||||
|
refreshFilters,
|
||||||
|
handleRulesChange,
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
actionCreators,
|
mapDispatchToProps,
|
||||||
)(Filters);
|
)(Filters);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { getFilteringStatus, setRules, addSuccessToast, getClients } from '../actions';
|
import { addSuccessToast, getClients } from '../actions';
|
||||||
|
import { getFilteringStatus, setRules } from '../actions/filtering';
|
||||||
import { getLogs, getLogsConfig } from '../actions/queryLogs';
|
import { getLogs, getLogsConfig } from '../actions/queryLogs';
|
||||||
import Logs from '../components/Logs';
|
import Logs from '../components/Logs';
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,19 @@ import { initSettings, toggleSetting } from '../actions';
|
||||||
import { getBlockedServices, setBlockedServices } from '../actions/services';
|
import { getBlockedServices, setBlockedServices } from '../actions/services';
|
||||||
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
|
import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
|
||||||
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
|
import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
|
||||||
|
import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
|
||||||
import Settings from '../components/Settings';
|
import Settings from '../components/Settings';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const {
|
const {
|
||||||
settings, services, stats, queryLogs,
|
settings, services, stats, queryLogs, filtering,
|
||||||
} = state;
|
} = state;
|
||||||
const props = {
|
const props = {
|
||||||
settings,
|
settings,
|
||||||
services,
|
services,
|
||||||
stats,
|
stats,
|
||||||
queryLogs,
|
queryLogs,
|
||||||
|
filtering,
|
||||||
};
|
};
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
@ -29,6 +31,8 @@ const mapDispatchToProps = {
|
||||||
clearLogs,
|
clearLogs,
|
||||||
getLogsConfig,
|
getLogsConfig,
|
||||||
setLogsConfig,
|
setLogsConfig,
|
||||||
|
getFilteringStatus,
|
||||||
|
setFiltersConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -265,3 +265,5 @@ export const FILTERED_STATUS = {
|
||||||
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||||
|
|
||||||
export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
|
export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||||
|
|
||||||
|
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
|
||||||
|
|
|
@ -32,28 +32,36 @@ export const renderRadioField = ({
|
||||||
}) => (
|
}) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className="custom-control custom-radio custom-control-inline">
|
<label className="custom-control custom-radio custom-control-inline">
|
||||||
<input
|
<input {...input} type="radio" className="custom-control-input" disabled={disabled} />
|
||||||
{...input}
|
|
||||||
type="radio"
|
|
||||||
className="custom-control-input"
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<span className="custom-control-label">{placeholder}</span>
|
<span className="custom-control-label">{placeholder}</span>
|
||||||
</label>
|
</label>
|
||||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
{!disabled &&
|
||||||
|
touched &&
|
||||||
|
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const renderSelectField = ({
|
export const renderSelectField = ({
|
||||||
input, placeholder, disabled, meta: { touched, error },
|
input,
|
||||||
|
placeholder,
|
||||||
|
subtitle,
|
||||||
|
disabled,
|
||||||
|
modifier = 'checkbox--form',
|
||||||
|
meta: { touched, error },
|
||||||
}) => (
|
}) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className="checkbox checkbox--form">
|
<label className={`checkbox ${modifier}`}>
|
||||||
<span className="checkbox__marker" />
|
<span className="checkbox__marker" />
|
||||||
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} />
|
||||||
<span className="checkbox__label">
|
<span className="checkbox__label">
|
||||||
<span className="checkbox__label-text checkbox__label-text--long">
|
<span className="checkbox__label-text checkbox__label-text--long">
|
||||||
<span className="checkbox__label-title">{placeholder}</span>
|
<span className="checkbox__label-title">{placeholder}</span>
|
||||||
|
{subtitle && (
|
||||||
|
<span
|
||||||
|
className="checkbox__label-subtitle"
|
||||||
|
dangerouslySetInnerHTML={{ __html: subtitle }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -64,7 +72,12 @@ export const renderSelectField = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
export const renderServiceField = ({
|
export const renderServiceField = ({
|
||||||
input, placeholder, disabled, modifier, icon, meta: { touched, error },
|
input,
|
||||||
|
placeholder,
|
||||||
|
disabled,
|
||||||
|
modifier,
|
||||||
|
icon,
|
||||||
|
meta: { touched, error },
|
||||||
}) => (
|
}) => (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<label className={`service custom-switch ${modifier}`}>
|
<label className={`service custom-switch ${modifier}`}>
|
||||||
|
@ -81,7 +94,9 @@ export const renderServiceField = ({
|
||||||
<use xlinkHref={`#${icon}`} />
|
<use xlinkHref={`#${icon}`} />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
{!disabled &&
|
||||||
|
touched &&
|
||||||
|
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import addDays from 'date-fns/add_days';
|
||||||
import subDays from 'date-fns/sub_days';
|
import subDays from 'date-fns/sub_days';
|
||||||
import round from 'lodash/round';
|
import round from 'lodash/round';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
STANDARD_DNS_PORT,
|
STANDARD_DNS_PORT,
|
||||||
|
@ -19,6 +20,21 @@ export const formatTime = (time) => {
|
||||||
return dateFormat(parsedTime, 'HH:mm:ss');
|
return dateFormat(parsedTime, 'HH:mm:ss');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatDateTime = (dateTime) => {
|
||||||
|
const currentLanguage = i18n.languages[0] || 'en';
|
||||||
|
const parsedTime = dateParse(dateTime);
|
||||||
|
const options = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return parsedTime.toLocaleString(currentLanguage, options);
|
||||||
|
};
|
||||||
|
|
||||||
export const normalizeLogs = logs => logs.map((log) => {
|
export const normalizeLogs = logs => logs.map((log) => {
|
||||||
const {
|
const {
|
||||||
time,
|
time,
|
||||||
|
@ -74,18 +90,38 @@ export const normalizeTopStats = stats => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const normalizeFilteringStatus = (filteringStatus) => {
|
export const normalizeFilteringStatus = (filteringStatus) => {
|
||||||
const { enabled, filters, user_rules: userRules } = filteringStatus;
|
|
||||||
const newFilters = filters ? filters.map((filter) => {
|
|
||||||
const {
|
const {
|
||||||
id, url, enabled, lastUpdated: lastUpdated = Date.now(), name = 'Default name', rulesCount: rulesCount = 0,
|
enabled, filters, user_rules: userRules, interval,
|
||||||
|
} = filteringStatus;
|
||||||
|
const newFilters = filters
|
||||||
|
? filters.map((filter) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
enabled,
|
||||||
|
last_updated,
|
||||||
|
name = 'Default name',
|
||||||
|
rules_count: rules_count = 0,
|
||||||
} = filter;
|
} = filter;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id, url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
|
id,
|
||||||
|
url,
|
||||||
|
enabled,
|
||||||
|
lastUpdated: last_updated ? formatDateTime(last_updated) : '–',
|
||||||
|
name,
|
||||||
|
rulesCount: rules_count,
|
||||||
};
|
};
|
||||||
}) : [];
|
})
|
||||||
|
: [];
|
||||||
const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
|
const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
|
||||||
return { enabled, userRules: newUserRules, filters: newFilters };
|
|
||||||
|
return {
|
||||||
|
enabled,
|
||||||
|
userRules: newUserRules,
|
||||||
|
filters: newFilters,
|
||||||
|
interval,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPercent = (amount, number) => {
|
export const getPercent = (amount, number) => {
|
||||||
|
@ -241,3 +277,5 @@ export const secondsToMilliseconds = (seconds) => {
|
||||||
|
|
||||||
return seconds;
|
return seconds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const normalizeRulesTextarea = text => text && text.replace(/^\n/g, '').replace(/\n\s*\n/g, '\n');
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
|
import * as actions from '../actions/filtering';
|
||||||
|
|
||||||
|
const filtering = handleActions(
|
||||||
|
{
|
||||||
|
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
|
||||||
|
[actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
|
||||||
|
[actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
|
||||||
|
|
||||||
|
[actions.handleRulesChange]: (state, { payload }) => {
|
||||||
|
const { userRules } = payload;
|
||||||
|
return { ...state, userRules };
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
|
||||||
|
[actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
|
||||||
|
[actions.getFilteringStatusSuccess]: (state, { payload }) => ({
|
||||||
|
...state,
|
||||||
|
...payload,
|
||||||
|
processingFilters: false,
|
||||||
|
}),
|
||||||
|
|
||||||
|
[actions.addFilterRequest]: state => ({
|
||||||
|
...state,
|
||||||
|
processingAddFilter: true,
|
||||||
|
isFilterAdded: false,
|
||||||
|
}),
|
||||||
|
[actions.addFilterFailure]: state => ({
|
||||||
|
...state,
|
||||||
|
processingAddFilter: false,
|
||||||
|
isFilterAdded: false,
|
||||||
|
}),
|
||||||
|
[actions.addFilterSuccess]: state => ({
|
||||||
|
...state,
|
||||||
|
processingAddFilter: false,
|
||||||
|
isFilterAdded: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
[actions.toggleFilteringModal]: (state) => {
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
isModalOpen: !state.isModalOpen,
|
||||||
|
isFilterAdded: false,
|
||||||
|
};
|
||||||
|
return newState;
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.toggleFilterRequest]: state => ({ ...state, processingConfigFilter: true }),
|
||||||
|
[actions.toggleFilterFailure]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
|
[actions.toggleFilterSuccess]: state => ({ ...state, processingConfigFilter: false }),
|
||||||
|
|
||||||
|
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
|
||||||
|
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
|
||||||
|
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
|
||||||
|
|
||||||
|
[actions.removeFilterRequest]: state => ({ ...state, processingRemoveFilter: true }),
|
||||||
|
[actions.removeFilterFailure]: state => ({ ...state, processingRemoveFilter: false }),
|
||||||
|
[actions.removeFilterSuccess]: state => ({ ...state, processingRemoveFilter: false }),
|
||||||
|
|
||||||
|
[actions.setFiltersConfigRequest]: state => ({ ...state, processingSetConfig: true }),
|
||||||
|
[actions.setFiltersConfigFailure]: state => ({ ...state, processingSetConfig: false }),
|
||||||
|
[actions.setFiltersConfigSuccess]: (state, { payload }) => ({
|
||||||
|
...state,
|
||||||
|
...payload,
|
||||||
|
processingSetConfig: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModalOpen: false,
|
||||||
|
processingFilters: false,
|
||||||
|
processingRules: false,
|
||||||
|
processingAddFilter: false,
|
||||||
|
processingRefreshFilters: false,
|
||||||
|
processingConfigFilter: false,
|
||||||
|
processingRemoveFilter: false,
|
||||||
|
processingSetConfig: false,
|
||||||
|
isFilterAdded: false,
|
||||||
|
filters: [],
|
||||||
|
userRules: '',
|
||||||
|
interval: 24,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default filtering;
|
|
@ -13,6 +13,7 @@ import rewrites from './rewrites';
|
||||||
import services from './services';
|
import services from './services';
|
||||||
import stats from './stats';
|
import stats from './stats';
|
||||||
import queryLogs from './queryLogs';
|
import queryLogs from './queryLogs';
|
||||||
|
import filtering from './filtering';
|
||||||
|
|
||||||
const settings = handleActions({
|
const settings = handleActions({
|
||||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||||
|
@ -130,13 +131,6 @@ const dashboard = handleActions({
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.getFilteringRequest]: state => ({ ...state, processingFiltering: true }),
|
|
||||||
[actions.getFilteringFailure]: state => ({ ...state, processingFiltering: false }),
|
|
||||||
[actions.getFilteringSuccess]: (state, { payload }) => {
|
|
||||||
const newState = { ...state, isFilteringEnabled: payload, processingFiltering: false };
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
|
[actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
|
||||||
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
|
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
|
||||||
[actions.toggleProtectionSuccess]: (state) => {
|
[actions.toggleProtectionSuccess]: (state) => {
|
||||||
|
@ -189,62 +183,6 @@ const dashboard = handleActions({
|
||||||
autoClients: [],
|
autoClients: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const filtering = handleActions({
|
|
||||||
[actions.setRulesRequest]: state => ({ ...state, processingRules: true }),
|
|
||||||
[actions.setRulesFailure]: state => ({ ...state, processingRules: false }),
|
|
||||||
[actions.setRulesSuccess]: state => ({ ...state, processingRules: false }),
|
|
||||||
|
|
||||||
[actions.handleRulesChange]: (state, { payload }) => {
|
|
||||||
const { userRules } = payload;
|
|
||||||
return { ...state, userRules };
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.getFilteringStatusRequest]: state => ({ ...state, processingFilters: true }),
|
|
||||||
[actions.getFilteringStatusFailure]: state => ({ ...state, processingFilters: false }),
|
|
||||||
[actions.getFilteringStatusSuccess]: (state, { payload }) => {
|
|
||||||
const { status } = payload;
|
|
||||||
const { filters, userRules } = status;
|
|
||||||
const newState = {
|
|
||||||
...state, filters, userRules, processingFilters: false,
|
|
||||||
};
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.addFilterRequest]: state =>
|
|
||||||
({ ...state, processingAddFilter: true, isFilterAdded: false }),
|
|
||||||
[actions.addFilterFailure]: (state) => {
|
|
||||||
const newState = { ...state, processingAddFilter: false, isFilterAdded: false };
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
[actions.addFilterSuccess]: state =>
|
|
||||||
({ ...state, processingAddFilter: false, isFilterAdded: true }),
|
|
||||||
|
|
||||||
[actions.toggleFilteringModal]: (state) => {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
isFilteringModalOpen: !state.isFilteringModalOpen,
|
|
||||||
isFilterAdded: false,
|
|
||||||
};
|
|
||||||
return newState;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.toggleFilterRequest]: state => ({ ...state, processingFilters: true }),
|
|
||||||
[actions.toggleFilterFailure]: state => ({ ...state, processingFilters: false }),
|
|
||||||
[actions.toggleFilterSuccess]: state => ({ ...state, processingFilters: false }),
|
|
||||||
|
|
||||||
[actions.refreshFiltersRequest]: state => ({ ...state, processingRefreshFilters: true }),
|
|
||||||
[actions.refreshFiltersFailure]: state => ({ ...state, processingRefreshFilters: false }),
|
|
||||||
[actions.refreshFiltersSuccess]: state => ({ ...state, processingRefreshFilters: false }),
|
|
||||||
}, {
|
|
||||||
isFilteringModalOpen: false,
|
|
||||||
processingFilters: false,
|
|
||||||
processingRules: false,
|
|
||||||
processingAddFilter: false,
|
|
||||||
processingRefreshFilters: false,
|
|
||||||
filters: [],
|
|
||||||
userRules: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const dhcp = handleActions({
|
const dhcp = handleActions({
|
||||||
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
||||||
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
||||||
|
|
|
@ -114,9 +114,9 @@ type Dnsfilter struct {
|
||||||
|
|
||||||
// Filter represents a filter list
|
// Filter represents a filter list
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
ID int64 `json:"id"` // auto-assigned when filter is added (see nextFilterID), json by default keeps ID uppercase but we need lowercase
|
ID int64 // auto-assigned when filter is added (see nextFilterID)
|
||||||
Data []byte `json:"-" yaml:"-"` // List of rules divided by '\n'
|
Data []byte `yaml:"-"` // List of rules divided by '\n'
|
||||||
FilePath string `json:"-" yaml:"-"` // Path to a filtering rules file
|
FilePath string `yaml:"-"` // Path to a filtering rules file
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate stringer -type=Reason
|
//go:generate stringer -type=Reason
|
||||||
|
|
|
@ -68,6 +68,8 @@ func NewServer(stats stats.Stats, queryLog querylog.QueryLog) *Server {
|
||||||
type FilteringConfig struct {
|
type FilteringConfig struct {
|
||||||
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
||||||
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
||||||
|
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
|
||||||
|
|
||||||
BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests
|
BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests
|
||||||
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600)
|
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600)
|
||||||
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -7,7 +7,6 @@ require (
|
||||||
github.com/AdguardTeam/golibs v0.2.1
|
github.com/AdguardTeam/golibs v0.2.1
|
||||||
github.com/AdguardTeam/urlfilter v0.5.0
|
github.com/AdguardTeam/urlfilter v0.5.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
|
|
||||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
|
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
|
||||||
github.com/etcd-io/bbolt v1.3.3
|
github.com/etcd-io/bbolt v1.3.3
|
||||||
github.com/go-test/deep v1.0.1
|
github.com/go-test/deep v1.0.1
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/querylog"
|
"github.com/AdguardTeam/AdGuardHome/querylog"
|
||||||
"github.com/AdguardTeam/AdGuardHome/stats"
|
"github.com/AdguardTeam/AdGuardHome/stats"
|
||||||
|
@ -72,6 +71,7 @@ type configuration struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
stats stats.Stats
|
stats stats.Stats
|
||||||
queryLog querylog.QueryLog
|
queryLog querylog.QueryLog
|
||||||
|
filteringStarted bool
|
||||||
|
|
||||||
// cached version.json to avoid hammering github.io for each page reload
|
// cached version.json to avoid hammering github.io for each page reload
|
||||||
versionCheckJSON []byte
|
versionCheckJSON []byte
|
||||||
|
@ -174,6 +174,7 @@ var config = configuration{
|
||||||
FilteringConfig: dnsforward.FilteringConfig{
|
FilteringConfig: dnsforward.FilteringConfig{
|
||||||
ProtectionEnabled: true, // whether or not use any of dnsfilter features
|
ProtectionEnabled: true, // whether or not use any of dnsfilter features
|
||||||
FilteringEnabled: true, // whether or not use filter lists
|
FilteringEnabled: true, // whether or not use filter lists
|
||||||
|
FiltersUpdateIntervalHours: 24,
|
||||||
BlockingMode: "nxdomain", // mode how to answer filtered requests
|
BlockingMode: "nxdomain", // mode how to answer filtered requests
|
||||||
BlockedResponseTTL: 10, // in seconds
|
BlockedResponseTTL: 10, // in seconds
|
||||||
QueryLogEnabled: true,
|
QueryLogEnabled: true,
|
||||||
|
@ -191,12 +192,6 @@ var config = configuration{
|
||||||
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
PortDNSOverTLS: 853, // needs to be passed through to dnsproxy
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Filters: []filter{
|
|
||||||
{Filter: dnsfilter.Filter{ID: 1}, Enabled: true, URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt", Name: "AdGuard Simplified Domain Names filter"},
|
|
||||||
{Filter: dnsfilter.Filter{ID: 2}, Enabled: false, URL: "https://adaway.org/hosts.txt", Name: "AdAway"},
|
|
||||||
{Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"},
|
|
||||||
{Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "https://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"},
|
|
||||||
},
|
|
||||||
DHCP: dhcpd.ServerConfig{
|
DHCP: dhcpd.ServerConfig{
|
||||||
LeaseDuration: 86400,
|
LeaseDuration: 86400,
|
||||||
ICMPTimeout: 1000,
|
ICMPTimeout: 1000,
|
||||||
|
@ -226,6 +221,7 @@ func initConfig() {
|
||||||
config.DNS.SafeSearchCacheSize = 1 * 1024 * 1024
|
config.DNS.SafeSearchCacheSize = 1 * 1024 * 1024
|
||||||
config.DNS.ParentalCacheSize = 1 * 1024 * 1024
|
config.DNS.ParentalCacheSize = 1 * 1024 * 1024
|
||||||
config.DNS.CacheTime = 30
|
config.DNS.CacheTime = 30
|
||||||
|
config.Filters = defaultFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigFilename returns path to the current config file
|
// getConfigFilename returns path to the current config file
|
||||||
|
@ -276,6 +272,9 @@ func parseConfig() error {
|
||||||
if !checkStatsInterval(config.DNS.StatsInterval) {
|
if !checkStatsInterval(config.DNS.StatsInterval) {
|
||||||
config.DNS.StatsInterval = 1
|
config.DNS.StatsInterval = 1
|
||||||
}
|
}
|
||||||
|
if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
|
||||||
|
config.DNS.FiltersUpdateIntervalHours = 24
|
||||||
|
}
|
||||||
|
|
||||||
if !checkQueryLogInterval(config.DNS.QueryLogInterval) {
|
if !checkQueryLogInterval(config.DNS.QueryLogInterval) {
|
||||||
config.DNS.QueryLogInterval = 1
|
config.DNS.QueryLogInterval = 1
|
||||||
|
@ -308,11 +307,6 @@ func parseConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate filters
|
|
||||||
deduplicateFilters()
|
|
||||||
|
|
||||||
updateUniqueFilterID(config.Filters)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
|
@ -17,8 +16,6 @@ import (
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
const updatePeriod = time.Hour * 24
|
|
||||||
|
|
||||||
var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
|
var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
|
||||||
|
|
||||||
// ----------------
|
// ----------------
|
||||||
|
@ -547,15 +544,6 @@ func registerControlHandlers() {
|
||||||
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
||||||
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
||||||
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
||||||
httpRegister(http.MethodPost, "/control/filtering/enable", handleFilteringEnable)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/disable", handleFilteringDisable)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/add_url", handleFilteringAddURL)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/remove_url", handleFilteringRemoveURL)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/enable_url", handleFilteringEnableURL)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/disable_url", handleFilteringDisableURL)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/refresh", handleFilteringRefresh)
|
|
||||||
httpRegister(http.MethodGet, "/control/filtering/status", handleFilteringStatus)
|
|
||||||
httpRegister(http.MethodPost, "/control/filtering/set_rules", handleFilteringSetRules)
|
|
||||||
httpRegister(http.MethodPost, "/control/safebrowsing/enable", handleSafeBrowsingEnable)
|
httpRegister(http.MethodPost, "/control/safebrowsing/enable", handleSafeBrowsingEnable)
|
||||||
httpRegister(http.MethodPost, "/control/safebrowsing/disable", handleSafeBrowsingDisable)
|
httpRegister(http.MethodPost, "/control/safebrowsing/disable", handleSafeBrowsingDisable)
|
||||||
httpRegister(http.MethodGet, "/control/safebrowsing/status", handleSafeBrowsingStatus)
|
httpRegister(http.MethodGet, "/control/safebrowsing/status", handleSafeBrowsingStatus)
|
||||||
|
@ -575,6 +563,7 @@ func registerControlHandlers() {
|
||||||
httpRegister(http.MethodGet, "/control/access/list", handleAccessList)
|
httpRegister(http.MethodGet, "/control/access/list", handleAccessList)
|
||||||
httpRegister(http.MethodPost, "/control/access/set", handleAccessSet)
|
httpRegister(http.MethodPost, "/control/access/set", handleAccessSet)
|
||||||
|
|
||||||
|
RegisterFilteringHandlers()
|
||||||
RegisterTLSHandlers()
|
RegisterTLSHandlers()
|
||||||
RegisterClientsHandlers()
|
RegisterClientsHandlers()
|
||||||
registerRewritesHandlers()
|
registerRewritesHandlers()
|
||||||
|
|
|
@ -5,74 +5,57 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/asaskevich/govalidator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleFilteringEnable(w http.ResponseWriter, r *http.Request) {
|
// IsValidURL - return TRUE if URL is valid
|
||||||
config.DNS.FilteringEnabled = true
|
func IsValidURL(rawurl string) bool {
|
||||||
httpUpdateConfigReloadDNSReturnOK(w, r)
|
url, err := url.ParseRequestURI(rawurl)
|
||||||
}
|
|
||||||
|
|
||||||
func handleFilteringDisable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
config.DNS.FilteringEnabled = false
|
|
||||||
httpUpdateConfigReloadDNSReturnOK(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleFilteringStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"enabled": config.DNS.FilteringEnabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
config.RLock()
|
|
||||||
data["filters"] = config.Filters
|
|
||||||
data["user_rules"] = config.UserRules
|
|
||||||
jsonVal, err := json.Marshal(data)
|
|
||||||
config.RUnlock()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
return false //Couldn't even parse the rawurl
|
||||||
return
|
}
|
||||||
|
if len(url.Scheme) == 0 {
|
||||||
|
return false //No Scheme found
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
type filterAddJSON struct {
|
||||||
_, err = w.Write(jsonVal)
|
Name string `json:"name"`
|
||||||
if err != nil {
|
URL string `json:"url"`
|
||||||
httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
|
func handleFilteringAddURL(w http.ResponseWriter, r *http.Request) {
|
||||||
f := filter{}
|
fj := filterAddJSON{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&f)
|
err := json.NewDecoder(r.Body).Decode(&fj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "Failed to parse request body json: %s", err)
|
httpError(w, http.StatusBadRequest, "Failed to parse request body json: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(f.URL) == 0 {
|
if !IsValidURL(fj.URL) {
|
||||||
http.Error(w, "URL parameter was not specified", http.StatusBadRequest)
|
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid := govalidator.IsRequestURL(f.URL); !valid {
|
|
||||||
http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates
|
// Check for duplicates
|
||||||
if filterExists(f.URL) {
|
if filterExists(fj.URL) {
|
||||||
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", f.URL)
|
httpError(w, http.StatusBadRequest, "Filter URL already added -- %s", fj.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set necessary properties
|
// Set necessary properties
|
||||||
|
f := filter{
|
||||||
|
Enabled: true,
|
||||||
|
URL: fj.URL,
|
||||||
|
Name: fj.Name,
|
||||||
|
}
|
||||||
f.ID = assignUniqueFilterID()
|
f.ID = assignUniqueFilterID()
|
||||||
f.Enabled = true
|
|
||||||
|
|
||||||
// Download the filter contents
|
// Download the filter contents
|
||||||
ok, err := f.update()
|
ok, err := f.update()
|
||||||
|
@ -133,7 +116,7 @@ func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid := govalidator.IsRequestURL(req.URL); !valid {
|
if !IsValidURL(req.URL) {
|
||||||
http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
|
http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -166,54 +149,27 @@ func handleFilteringRemoveURL(w http.ResponseWriter, r *http.Request) {
|
||||||
httpUpdateConfigReloadDNSReturnOK(w, r)
|
httpUpdateConfigReloadDNSReturnOK(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFilteringEnableURL(w http.ResponseWriter, r *http.Request) {
|
type filterURLJSON struct {
|
||||||
parameters, err := parseParametersFromBody(r.Body)
|
URL string `json:"url"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFilteringSetURL(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fj := filterURLJSON{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&fj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, http.StatusBadRequest, "failed to parse parameters from body: %s", err)
|
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url, ok := parameters["url"]
|
if !IsValidURL(fj.URL) {
|
||||||
if !ok {
|
http.Error(w, "invalid URL", http.StatusBadRequest)
|
||||||
http.Error(w, "URL parameter was not specified", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid := govalidator.IsRequestURL(url); !valid {
|
found := filterEnable(fj.URL, fj.Enabled)
|
||||||
http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
found := filterEnable(url, true)
|
|
||||||
if !found {
|
if !found {
|
||||||
http.Error(w, "URL parameter was not previously added", http.StatusBadRequest)
|
http.Error(w, "URL doesn't exist", http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
httpUpdateConfigReloadDNSReturnOK(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleFilteringDisableURL(w http.ResponseWriter, r *http.Request) {
|
|
||||||
parameters, err := parseParametersFromBody(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, http.StatusBadRequest, "failed to parse parameters from body: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
url, ok := parameters["url"]
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, "URL parameter was not specified", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid := govalidator.IsRequestURL(url); !valid {
|
|
||||||
http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
found := filterEnable(url, false)
|
|
||||||
if !found {
|
|
||||||
http.Error(w, "URL parameter was not previously added", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,3 +191,91 @@ func handleFilteringRefresh(w http.ResponseWriter, r *http.Request) {
|
||||||
updated := refreshFiltersIfNecessary(true)
|
updated := refreshFiltersIfNecessary(true)
|
||||||
fmt.Fprintf(w, "OK %d filters updated\n", updated)
|
fmt.Fprintf(w, "OK %d filters updated\n", updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filterJSON struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RulesCount uint32 `json:"rules_count"`
|
||||||
|
LastUpdated string `json:"last_updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type filteringConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Interval uint32 `json:"interval"` // in hours
|
||||||
|
Filters []filterJSON `json:"filters"`
|
||||||
|
UserRules []string `json:"user_rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get filtering configuration
|
||||||
|
func handleFilteringInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resp := filteringConfig{}
|
||||||
|
config.RLock()
|
||||||
|
resp.Enabled = config.DNS.FilteringEnabled
|
||||||
|
resp.Interval = config.DNS.FiltersUpdateIntervalHours
|
||||||
|
for _, f := range config.Filters {
|
||||||
|
fj := filterJSON{
|
||||||
|
ID: f.ID,
|
||||||
|
Enabled: f.Enabled,
|
||||||
|
URL: f.URL,
|
||||||
|
Name: f.Name,
|
||||||
|
RulesCount: uint32(f.RulesCount),
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.LastUpdated.Second() != 0 {
|
||||||
|
fj.LastUpdated = f.LastUpdated.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Filters = append(resp.Filters, fj)
|
||||||
|
}
|
||||||
|
resp.UserRules = config.UserRules
|
||||||
|
config.RUnlock()
|
||||||
|
|
||||||
|
jsonVal, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusInternalServerError, "json encode: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(jsonVal)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusInternalServerError, "http write: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set filtering configuration
|
||||||
|
func handleFilteringConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req := filteringConfig{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusBadRequest, "json decode: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checkFiltersUpdateIntervalHours(req.Interval) {
|
||||||
|
httpError(w, http.StatusBadRequest, "Unsupported interval")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config.DNS.FilteringEnabled = req.Enabled
|
||||||
|
config.DNS.FiltersUpdateIntervalHours = req.Interval
|
||||||
|
httpUpdateConfigReloadDNSReturnOK(w, r)
|
||||||
|
|
||||||
|
returnOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFilteringHandlers - register handlers
|
||||||
|
func RegisterFilteringHandlers() {
|
||||||
|
httpRegister(http.MethodGet, "/control/filtering_info", handleFilteringInfo)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering_config", handleFilteringConfig)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering/add_url", handleFilteringAddURL)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering/remove_url", handleFilteringRemoveURL)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering/set_url", handleFilteringSetURL)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering/refresh", handleFilteringRefresh)
|
||||||
|
httpRegister(http.MethodPost, "/control/filtering/set_rules", handleFilteringSetRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFiltersUpdateIntervalHours(i uint32) bool {
|
||||||
|
return i == 0 || i == 1 || i == 12 || i == 1*24 || i == 3*24 || i == 7*24
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
@ -239,6 +240,9 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
config.AuthName = newSettings.Username
|
config.AuthName = newSettings.Username
|
||||||
config.AuthPass = newSettings.Password
|
config.AuthPass = newSettings.Password
|
||||||
|
|
||||||
|
dnsBaseDir := filepath.Join(config.ourWorkingDir, dataDir)
|
||||||
|
initDNSServer(dnsBaseDir)
|
||||||
|
|
||||||
err = startDNSServer()
|
err = startDNSServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.firstRun = true
|
config.firstRun = true
|
||||||
|
@ -255,8 +259,6 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go refreshFiltersIfNecessary(false)
|
|
||||||
|
|
||||||
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
|
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
|
||||||
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
|
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
|
||||||
if restartHTTP {
|
if restartHTTP {
|
||||||
|
|
|
@ -49,6 +49,7 @@ func initDNSServer(baseDir string) {
|
||||||
config.dnsServer = dnsforward.NewServer(config.stats, config.queryLog)
|
config.dnsServer = dnsforward.NewServer(config.stats, config.queryLog)
|
||||||
|
|
||||||
initRDNS()
|
initRDNS()
|
||||||
|
initFiltering()
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRunning() bool {
|
func isRunning() bool {
|
||||||
|
@ -165,6 +166,11 @@ func startDNSServer() error {
|
||||||
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
return errorx.Decorate(err, "Couldn't start forwarding DNS server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !config.filteringStarted {
|
||||||
|
config.filteringStarted = true
|
||||||
|
startRefreshFilters()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,35 @@ var (
|
||||||
filterTitleRegexp = regexp.MustCompile(`^! Title: +(.*)$`)
|
filterTitleRegexp = regexp.MustCompile(`^! Title: +(.*)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func initFiltering() {
|
||||||
|
loadFilters()
|
||||||
|
deduplicateFilters()
|
||||||
|
updateUniqueFilterID(config.Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startRefreshFilters() {
|
||||||
|
go func() {
|
||||||
|
_ = refreshFiltersIfNecessary(false)
|
||||||
|
}()
|
||||||
|
go periodicallyRefreshFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultFilters() []filter {
|
||||||
|
return []filter{
|
||||||
|
{Filter: dnsfilter.Filter{ID: 1}, Enabled: true, URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt", Name: "AdGuard Simplified Domain Names filter"},
|
||||||
|
{Filter: dnsfilter.Filter{ID: 2}, Enabled: false, URL: "https://adaway.org/hosts.txt", Name: "AdAway"},
|
||||||
|
{Filter: dnsfilter.Filter{ID: 3}, Enabled: false, URL: "https://hosts-file.net/ad_servers.txt", Name: "hpHosts - Ad and Tracking servers only"},
|
||||||
|
{Filter: dnsfilter.Filter{ID: 4}, Enabled: false, URL: "https://www.malwaredomainlist.com/hostslist/hosts.txt", Name: "MalwareDomainList.com Hosts List"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// field ordering is important -- yaml fields will mirror ordering from here
|
// field ordering is important -- yaml fields will mirror ordering from here
|
||||||
type filter struct {
|
type filter struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool
|
||||||
URL string `json:"url"`
|
URL string
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
RulesCount int `json:"rulesCount" yaml:"-"`
|
RulesCount int `yaml:"-"`
|
||||||
LastUpdated time.Time `json:"lastUpdated,omitempty" yaml:"-"`
|
LastUpdated time.Time `yaml:"-"`
|
||||||
checksum uint32 // checksum of the file data
|
checksum uint32 // checksum of the file data
|
||||||
|
|
||||||
dnsfilter.Filter `yaml:",inline"`
|
dnsfilter.Filter `yaml:",inline"`
|
||||||
|
@ -119,8 +141,7 @@ func loadFilters() {
|
||||||
|
|
||||||
err := filter.load()
|
err := filter.load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This is okay for the first start, the filter will be loaded later
|
log.Error("Couldn't load filter %d contents due to %s", filter.ID, err)
|
||||||
log.Debug("Couldn't load filter %d contents due to %s", filter.ID, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +180,12 @@ func assignUniqueFilterID() int64 {
|
||||||
|
|
||||||
// Sets up a timer that will be checking for filters updates periodically
|
// Sets up a timer that will be checking for filters updates periodically
|
||||||
func periodicallyRefreshFilters() {
|
func periodicallyRefreshFilters() {
|
||||||
for range time.Tick(time.Minute) {
|
for {
|
||||||
|
time.Sleep(1 * time.Hour)
|
||||||
|
if config.DNS.FiltersUpdateIntervalHours == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
refreshFiltersIfNecessary(false)
|
refreshFiltersIfNecessary(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,10 +206,7 @@ func refreshFiltersIfNecessary(force bool) int {
|
||||||
var updateFilters []filter
|
var updateFilters []filter
|
||||||
var updateFlags []bool // 'true' if filter data has changed
|
var updateFlags []bool // 'true' if filter data has changed
|
||||||
|
|
||||||
if config.firstRun {
|
now := time.Now()
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
config.RLock()
|
config.RLock()
|
||||||
for i := range config.Filters {
|
for i := range config.Filters {
|
||||||
f := &config.Filters[i] // otherwise we will be operating on a copy
|
f := &config.Filters[i] // otherwise we will be operating on a copy
|
||||||
|
@ -192,7 +215,8 @@ func refreshFiltersIfNecessary(force bool) int {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !force && time.Since(f.LastUpdated) <= updatePeriod {
|
expireTime := f.LastUpdated.Unix() + int64(config.DNS.FiltersUpdateIntervalHours)*60*60
|
||||||
|
if !force && expireTime > now.Unix() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +238,7 @@ func refreshFiltersIfNecessary(force bool) int {
|
||||||
log.Printf("Failed to update filter %s: %s\n", uf.URL, err)
|
log.Printf("Failed to update filter %s: %s\n", uf.URL, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
uf.LastUpdated = time.Now()
|
uf.LastUpdated = now
|
||||||
if updated {
|
if updated {
|
||||||
updateCount++
|
updateCount++
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilters(t *testing.T) {
|
||||||
|
config.client = &http.Client{
|
||||||
|
Timeout: time.Minute * 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
f := filter{
|
||||||
|
URL: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
|
||||||
|
}
|
||||||
|
|
||||||
|
// download
|
||||||
|
ok, err := f.update()
|
||||||
|
assert.True(t, ok && err == nil)
|
||||||
|
|
||||||
|
// refresh
|
||||||
|
ok, err = f.update()
|
||||||
|
assert.True(t, !ok && err == nil)
|
||||||
|
|
||||||
|
err = f.save()
|
||||||
|
assert.True(t, err == nil)
|
||||||
|
|
||||||
|
err = f.load()
|
||||||
|
assert.True(t, err == nil)
|
||||||
|
|
||||||
|
f.unload()
|
||||||
|
os.Remove(f.Path())
|
||||||
|
}
|
14
home/home.go
14
home/home.go
|
@ -135,22 +135,17 @@ func run(args options) {
|
||||||
config.BindPort = args.bindPort
|
config.BindPort = args.bindPort
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFilters()
|
|
||||||
|
|
||||||
if !config.firstRun {
|
if !config.firstRun {
|
||||||
// Save the updated config
|
// Save the updated config
|
||||||
err := config.write()
|
err := config.write()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Init the DNS server instance before registering HTTP handlers
|
|
||||||
dnsBaseDir := filepath.Join(config.ourWorkingDir, dataDir)
|
dnsBaseDir := filepath.Join(config.ourWorkingDir, dataDir)
|
||||||
initDNSServer(dnsBaseDir)
|
initDNSServer(dnsBaseDir)
|
||||||
|
|
||||||
if !config.firstRun {
|
err = startDNSServer()
|
||||||
err := startDNSServer()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -165,13 +160,6 @@ func run(args options) {
|
||||||
config.pidFileName = args.pidFile
|
config.pidFileName = args.pidFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update filters we've just loaded right away, don't wait for periodic update timer
|
|
||||||
go func() {
|
|
||||||
refreshFiltersIfNecessary(false)
|
|
||||||
}()
|
|
||||||
// Schedule automatic filters updates
|
|
||||||
go periodicallyRefreshFilters()
|
|
||||||
|
|
||||||
// Initialize and run the admin Web interface
|
// Initialize and run the admin Web interface
|
||||||
box := packr.NewBox("../build/static")
|
box := packr.NewBox("../build/static")
|
||||||
|
|
||||||
|
|
|
@ -438,34 +438,30 @@ paths:
|
||||||
# Filtering status methods
|
# Filtering status methods
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
/filtering/status:
|
/filtering_info:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- filtering
|
- filtering
|
||||||
operationId: filteringStatus
|
operationId: filteringInfo
|
||||||
summary: 'Get status of rules-based filter'
|
summary: 'Get filtering parameters'
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/FilteringStatus"
|
$ref: "#/definitions/FilterInfo"
|
||||||
|
|
||||||
/filtering/enable:
|
/filtering_config:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- filtering
|
- filtering
|
||||||
operationId: filteringEnable
|
operationId: filteringConfig
|
||||||
summary: 'Enable filtering'
|
summary: 'Set filtering parameters'
|
||||||
responses:
|
parameters:
|
||||||
200:
|
- in: "body"
|
||||||
description: OK
|
name: "body"
|
||||||
|
required: true
|
||||||
/filtering/disable:
|
schema:
|
||||||
post:
|
$ref: "#/definitions/FilterConfig"
|
||||||
tags:
|
|
||||||
- filtering
|
|
||||||
operationId: filteringDisable
|
|
||||||
summary: 'Disable filtering'
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
|
@ -506,42 +502,19 @@ paths:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
|
|
||||||
/filtering/enable_url:
|
/filtering/set_url:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- filtering
|
- filtering
|
||||||
operationId: filteringEnableURL
|
operationId: filteringSetURL
|
||||||
summary: 'Enable filter URL'
|
summary: 'Set URL parameters'
|
||||||
consumes:
|
consumes:
|
||||||
- text/plain
|
- application/json
|
||||||
parameters:
|
parameters:
|
||||||
- in: body
|
- in: "body"
|
||||||
name: url
|
name: "body"
|
||||||
description: 'Previously added URL containing filtering rules'
|
|
||||||
required: true
|
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: "#/definitions/FilterSetUrl"
|
||||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: OK
|
|
||||||
|
|
||||||
/filtering/disable_url:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- filtering
|
|
||||||
operationId: filteringDisableURL
|
|
||||||
summary: 'Disable filter URL'
|
|
||||||
consumes:
|
|
||||||
- text/plain
|
|
||||||
parameters:
|
|
||||||
- in: body
|
|
||||||
name: url
|
|
||||||
description: 'Previously added URL containing filtering rules'
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
|
@ -1051,7 +1024,7 @@ definitions:
|
||||||
lastUpdated:
|
lastUpdated:
|
||||||
type: "string"
|
type: "string"
|
||||||
format: "date-time"
|
format: "date-time"
|
||||||
example: "2018-10-30T12:18:57.223101822+03:00"
|
example: "2018-10-30T12:18:57+03:00"
|
||||||
name:
|
name:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "AdGuard Simplified Domain Names filter"
|
example: "AdGuard Simplified Domain Names filter"
|
||||||
|
@ -1061,16 +1034,15 @@ definitions:
|
||||||
url:
|
url:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
|
example: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
|
||||||
FilteringStatus:
|
|
||||||
|
FilterInfo:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "Filtering settings"
|
description: "Filtering settings"
|
||||||
required:
|
|
||||||
- "enabled"
|
|
||||||
- "filters"
|
|
||||||
- "user_rules"
|
|
||||||
properties:
|
properties:
|
||||||
enabled:
|
enabled:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
|
interval:
|
||||||
|
type: "integer"
|
||||||
filters:
|
filters:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
|
@ -1079,9 +1051,25 @@ definitions:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
type: "string"
|
type: "string"
|
||||||
example:
|
|
||||||
- '||example.org^'
|
FilterConfig:
|
||||||
- '||example.com^'
|
type: "object"
|
||||||
|
description: "Filtering settings"
|
||||||
|
properties:
|
||||||
|
enabled:
|
||||||
|
type: "boolean"
|
||||||
|
interval:
|
||||||
|
type: "integer"
|
||||||
|
|
||||||
|
FilterSetUrl:
|
||||||
|
type: "object"
|
||||||
|
description: "Filtering URL settings"
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: "string"
|
||||||
|
enabled:
|
||||||
|
type: "boolean"
|
||||||
|
|
||||||
GetVersionRequest:
|
GetVersionRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "/version.json request data"
|
description: "/version.json request data"
|
||||||
|
|
|
@ -35,7 +35,7 @@ type queryLog struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newQueryLog creates a new instance of the query log
|
// create a new instance of the query log
|
||||||
func newQueryLog(conf Config) *queryLog {
|
func newQueryLog(conf Config) *queryLog {
|
||||||
l := queryLog{}
|
l := queryLog{}
|
||||||
l.logFile = filepath.Join(conf.BaseDir, queryLogFileName)
|
l.logFile = filepath.Join(conf.BaseDir, queryLogFileName)
|
||||||
|
@ -53,7 +53,6 @@ func (l *queryLog) Configure(conf Config) {
|
||||||
l.conf = conf
|
l.conf = conf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear memory buffer and remove the file
|
|
||||||
func (l *queryLog) Clear() {
|
func (l *queryLog) Clear() {
|
||||||
l.fileFlushLock.Lock()
|
l.fileFlushLock.Lock()
|
||||||
defer l.fileFlushLock.Unlock()
|
defer l.fileFlushLock.Unlock()
|
||||||
|
@ -164,7 +163,6 @@ func (l *queryLog) Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getQueryLogJson returns a map with the current query log ready to be converted to a JSON
|
|
||||||
func (l *queryLog) GetData() []map[string]interface{} {
|
func (l *queryLog) GetData() []map[string]interface{} {
|
||||||
l.lock.RLock()
|
l.lock.RLock()
|
||||||
values := make([]*logEntry, len(l.cache))
|
values := make([]*logEntry, len(l.cache))
|
||||||
|
|
|
@ -10,14 +10,20 @@ import (
|
||||||
|
|
||||||
// QueryLog - main interface
|
// QueryLog - main interface
|
||||||
type QueryLog interface {
|
type QueryLog interface {
|
||||||
|
// Close query log object
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
// Set new configuration at runtime
|
// Set new configuration at runtime
|
||||||
// Currently only 'Interval' field is supported.
|
// Currently only 'Interval' field is supported.
|
||||||
Configure(conf Config)
|
Configure(conf Config)
|
||||||
|
|
||||||
|
// Add a log entry
|
||||||
Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Result, elapsed time.Duration, addr net.Addr, upstream string)
|
Add(question *dns.Msg, answer *dns.Msg, result *dnsfilter.Result, elapsed time.Duration, addr net.Addr, upstream string)
|
||||||
|
|
||||||
|
// Get log entries
|
||||||
GetData() []map[string]interface{}
|
GetData() []map[string]interface{}
|
||||||
|
|
||||||
|
// Clear memory buffer and remove log files
|
||||||
Clear()
|
Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +33,7 @@ type Config struct {
|
||||||
Interval uint32 // interval to rotate logs (in hours)
|
Interval uint32 // interval to rotate logs (in hours)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New - create instance
|
// New - create a new instance of the query log
|
||||||
func New(conf Config) QueryLog {
|
func New(conf Config) QueryLog {
|
||||||
return newQueryLog(conf)
|
return newQueryLog(conf)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue