diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 4b2833d7..cfeddd40 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -42,6 +42,15 @@ Contents:
* API: Clear statistics data
* API: Set statistics parameters
* API: Get statistics parameters
+* Query logs
+ * API: Set querylog parameters
+ * API: Get querylog parameters
+
+
+## Relations between subsystems
+
+![](agh-arch.png)
+
## First startup
@@ -976,3 +985,37 @@ Response:
{
"interval": 1 | 7 | 30 | 90
}
+
+
+## Query logs
+
+### API: Set querylog parameters
+
+Request:
+
+ POST /control/querylog_config
+
+ {
+ "enabled": true | false
+ "interval": 1 | 7 | 30 | 90
+ }
+
+Response:
+
+ 200 OK
+
+
+### API: Get querylog parameters
+
+Request:
+
+ GET /control/querylog_info
+
+Response:
+
+ 200 OK
+
+ {
+ "enabled": true | false
+ "interval": 1 | 7 | 30 | 90
+ }
diff --git a/agh-arch.png b/agh-arch.png
new file mode 100644
index 00000000..a0410075
Binary files /dev/null and b/agh-arch.png differ
diff --git a/client/package-lock.json b/client/package-lock.json
index 0c596c27..b2070cdb 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -5027,11 +5027,6 @@
}
}
},
- "file-saver": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
- "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg=="
- },
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
diff --git a/client/package.json b/client/package.json
index 6c5f8e4e..0e8b6d4d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,7 +13,6 @@
"axios": "^0.19.0",
"classnames": "^2.2.6",
"date-fns": "^1.29.0",
- "file-saver": "^1.3.8",
"i18next": "^12.0.0",
"i18next-browser-languagedetector": "^2.2.3",
"lodash": "^4.17.15",
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 119c1828..570d28e1 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -98,7 +98,6 @@
"enforce_safe_search": "Enforce safe search",
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, DuckDuckGo and Yandex.",
"no_servers_specified": "No servers specified",
- "no_settings": "No settings",
"general_settings": "General settings",
"dns_settings": "DNS settings",
"encryption_settings": "Encryption settings",
@@ -163,10 +162,7 @@
"show_all_filter_type": "Show all",
"show_filtered_type": "Show filtered",
"no_logs_found": "No logs found",
- "disabled_log_btn": "Disable log",
- "download_log_file_btn": "Download log file",
"refresh_btn": "Refresh",
- "enabled_log_btn": "Enable log",
"last_dns_queries": "Last 5000 DNS queries",
"previous_btn": "Previous",
"next_btn": "Next",
@@ -177,10 +173,15 @@
"updated_custom_filtering_toast": "Updated the custom filtering rules",
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules",
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules",
- "query_log_disabled_toast": "Query log disabled",
- "query_log_enabled_toast": "Query log enabled",
"query_log_response_status": "Status: {{value}}",
"query_log_filtered": "Filtered by {{filter}}",
+ "query_log_confirm_clear": "Are you sure you want to clear the entire query log? This will also clear statistics on the dashboard.",
+ "query_log_cleared": "The query log has been successfully cleared",
+ "query_log_clear": "Clear query logs",
+ "query_log_retention": "Query logs retention",
+ "query_log_enable": "Enable log",
+ "query_log_configuration": "Logs configuration",
+ "query_log_disabled": "The query log is disabled and can be configured in the <0>settings0>",
"source_label": "Source",
"found_in_known_domain_db": "Found in the known domains database.",
"category_label": "Category",
@@ -372,7 +373,7 @@
"domain": "Domain",
"answer": "Answer",
"filter_added_successfully": "The filter has been successfully added",
- "statistics_logs": "Statistics and logs",
+ "statistics_configuration": "Statistics configuration",
"statistics_retention": "Statistics retention",
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
"statistics_clear": " Clear statistics",
diff --git a/client/src/actions/access.js b/client/src/actions/access.js
index b10062cb..5b5272d7 100644
--- a/client/src/actions/access.js
+++ b/client/src/actions/access.js
@@ -1,10 +1,8 @@
import { createAction } from 'redux-actions';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { normalizeTextarea } from '../helpers/helpers';
-const apiClient = new Api();
-
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS');
diff --git a/client/src/actions/clients.js b/client/src/actions/clients.js
index 6af28871..3974a38c 100644
--- a/client/src/actions/clients.js
+++ b/client/src/actions/clients.js
@@ -1,11 +1,9 @@
import { createAction } from 'redux-actions';
import { t } from 'i18next';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast, getClients } from './index';
import { CLIENT_ID } from '../helpers/constants';
-const apiClient = new Api();
-
export const toggleClientModal = createAction('TOGGLE_CLIENT_MODAL');
export const addClientRequest = createAction('ADD_CLIENT_REQUEST');
diff --git a/client/src/actions/encryption.js b/client/src/actions/encryption.js
index 6d6f3332..be86bd59 100644
--- a/client/src/actions/encryption.js
+++ b/client/src/actions/encryption.js
@@ -1,10 +1,8 @@
import { createAction } from 'redux-actions';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { redirectToCurrentProtocol } from '../helpers/helpers';
-const apiClient = new Api();
-
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 98d77ca4..8a132060 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -4,12 +4,10 @@ import { showLoading, hideLoading } from 'react-redux-loading-bar';
import axios from 'axios';
import versionCompare from '../helpers/versionCompare';
-import { normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers';
+import { normalizeFilteringStatus, normalizeTextarea, sortClients } from '../helpers/helpers';
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
import { getTlsStatus } from './encryption';
-import Api from '../api/Api';
-
-const apiClient = new Api();
+import apiClient from '../api/Api';
export const addErrorToast = createAction('ADD_ERROR_TOAST');
export const addSuccessToast = createAction('ADD_SUCCESS_TOAST');
@@ -292,52 +290,6 @@ export const disableDns = () => async (dispatch) => {
}
};
-export const getLogsRequest = createAction('GET_LOGS_REQUEST');
-export const getLogsFailure = createAction('GET_LOGS_FAILURE');
-export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
-
-export const getLogs = () => async (dispatch, getState) => {
- dispatch(getLogsRequest());
- const timer = setInterval(async () => {
- const state = getState();
- if (state.dashboard.isCoreRunning) {
- clearInterval(timer);
- try {
- const logs = normalizeLogs(await apiClient.getQueryLog());
- dispatch(getLogsSuccess(logs));
- } catch (error) {
- dispatch(addErrorToast({ error }));
- dispatch(getLogsFailure(error));
- }
- }
- }, 100);
-};
-
-export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
-export const toggleLogStatusFailure = createAction('TOGGLE_LOGS_FAILURE');
-export const toggleLogStatusSuccess = createAction('TOGGLE_LOGS_SUCCESS');
-
-export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
- dispatch(toggleLogStatusRequest());
- let toggleMethod;
- let successMessage;
- if (queryLogEnabled) {
- toggleMethod = apiClient.disableQueryLog.bind(apiClient);
- successMessage = 'query_log_disabled_toast';
- } else {
- toggleMethod = apiClient.enableQueryLog.bind(apiClient);
- successMessage = 'query_log_enabled_toast';
- }
- try {
- await toggleMethod();
- dispatch(addSuccessToast(successMessage));
- dispatch(toggleLogStatusSuccess());
- } catch (error) {
- dispatch(addErrorToast({ error }));
- dispatch(toggleLogStatusFailure());
- }
-};
-
export const setRulesRequest = createAction('SET_RULES_REQUEST');
export const setRulesFailure = createAction('SET_RULES_FAILURE');
export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
@@ -465,23 +417,6 @@ export const removeFilter = url => async (dispatch) => {
export const toggleFilteringModal = createAction('FILTERING_MODAL_TOGGLE');
-export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST');
-export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
-export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
-
-export const downloadQueryLog = () => async (dispatch) => {
- let data;
- dispatch(downloadQueryLogRequest());
- try {
- data = await apiClient.downloadQueryLog();
- dispatch(downloadQueryLogSuccess());
- } catch (error) {
- dispatch(addErrorToast({ error }));
- dispatch(downloadQueryLogFailure());
- }
- return data;
-};
-
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
diff --git a/client/src/actions/install.js b/client/src/actions/install.js
index 3070ff2d..62983892 100644
--- a/client/src/actions/install.js
+++ b/client/src/actions/install.js
@@ -1,9 +1,7 @@
import { createAction } from 'redux-actions';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
-const apiClient = new Api();
-
export const nextStep = createAction('NEXT_STEP');
export const prevStep = createAction('PREV_STEP');
diff --git a/client/src/actions/queryLogs.js b/client/src/actions/queryLogs.js
new file mode 100644
index 00000000..c68ddf15
--- /dev/null
+++ b/client/src/actions/queryLogs.js
@@ -0,0 +1,67 @@
+import { createAction } from 'redux-actions';
+
+import apiClient from '../api/Api';
+import { addErrorToast, addSuccessToast } from './index';
+import { normalizeLogs } from '../helpers/helpers';
+
+export const getLogsRequest = createAction('GET_LOGS_REQUEST');
+export const getLogsFailure = createAction('GET_LOGS_FAILURE');
+export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
+
+export const getLogs = () => async (dispatch) => {
+ dispatch(getLogsRequest());
+ try {
+ const logs = normalizeLogs(await apiClient.getQueryLog());
+ dispatch(getLogsSuccess(logs));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(getLogsFailure(error));
+ }
+};
+
+export const clearLogsRequest = createAction('CLEAR_LOGS_REQUEST');
+export const clearLogsFailure = createAction('CLEAR_LOGS_FAILURE');
+export const clearLogsSuccess = createAction('CLEAR_LOGS_SUCCESS');
+
+export const clearLogs = () => async (dispatch) => {
+ dispatch(clearLogsRequest());
+ try {
+ await apiClient.clearQueryLog();
+ dispatch(clearLogsSuccess());
+ dispatch(addSuccessToast('query_log_cleared'));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(clearLogsFailure(error));
+ }
+};
+
+export const getLogsConfigRequest = createAction('GET_LOGS_CONFIG_REQUEST');
+export const getLogsConfigFailure = createAction('GET_LOGS_CONFIG_FAILURE');
+export const getLogsConfigSuccess = createAction('GET_LOGS_CONFIG_SUCCESS');
+
+export const getLogsConfig = () => async (dispatch) => {
+ dispatch(getLogsConfigRequest());
+ try {
+ const data = await apiClient.getQueryLogInfo();
+ dispatch(getLogsConfigSuccess(data));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(getLogsConfigFailure());
+ }
+};
+
+export const setLogsConfigRequest = createAction('SET_LOGS_CONFIG_REQUEST');
+export const setLogsConfigFailure = createAction('SET_LOGS_CONFIG_FAILURE');
+export const setLogsConfigSuccess = createAction('SET_LOGS_CONFIG_SUCCESS');
+
+export const setLogsConfig = config => async (dispatch) => {
+ dispatch(setLogsConfigRequest());
+ try {
+ await apiClient.setQueryLogConfig(config);
+ dispatch(addSuccessToast('config_successfully_saved'));
+ dispatch(setLogsConfigSuccess(config));
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(setLogsConfigFailure());
+ }
+};
diff --git a/client/src/actions/rewrites.js b/client/src/actions/rewrites.js
index df846fdd..1ff4a012 100644
--- a/client/src/actions/rewrites.js
+++ b/client/src/actions/rewrites.js
@@ -1,10 +1,8 @@
import { createAction } from 'redux-actions';
import { t } from 'i18next';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
-const apiClient = new Api();
-
export const toggleRewritesModal = createAction('TOGGLE_REWRITES_MODAL');
export const getRewritesListRequest = createAction('GET_REWRITES_LIST_REQUEST');
diff --git a/client/src/actions/services.js b/client/src/actions/services.js
index 7aae500f..c6e478de 100644
--- a/client/src/actions/services.js
+++ b/client/src/actions/services.js
@@ -1,9 +1,7 @@
import { createAction } from 'redux-actions';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
-const apiClient = new Api();
-
export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST');
export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
diff --git a/client/src/actions/stats.js b/client/src/actions/stats.js
index a24b3ec7..d8ab5bf5 100644
--- a/client/src/actions/stats.js
+++ b/client/src/actions/stats.js
@@ -1,14 +1,12 @@
import { createAction } from 'redux-actions';
-import Api from '../api/Api';
+import apiClient from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { normalizeTopStats, secondsToMilliseconds } from '../helpers/helpers';
-const apiClient = new Api();
-
-export const getStatsConfigRequest = createAction('GET_LOGS_CONFIG_REQUEST');
-export const getStatsConfigFailure = createAction('GET_LOGS_CONFIG_FAILURE');
-export const getStatsConfigSuccess = createAction('GET_LOGS_CONFIG_SUCCESS');
+export const getStatsConfigRequest = createAction('GET_STATS_CONFIG_REQUEST');
+export const getStatsConfigFailure = createAction('GET_STATS_CONFIG_FAILURE');
+export const getStatsConfigSuccess = createAction('GET_STATS_CONFIG_SUCCESS');
export const getStatsConfig = () => async (dispatch) => {
dispatch(getStatsConfigRequest());
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index 0f63f2ea..f39b28dc 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -1,6 +1,6 @@
import axios from 'axios';
-export default class Api {
+class Api {
baseUrl = 'control';
async makeRequest(path, method = 'POST', config) {
@@ -25,9 +25,6 @@ export default class Api {
GLOBAL_START = { path: 'start', method: 'POST' };
GLOBAL_STATUS = { path: 'status', method: 'GET' };
GLOBAL_STOP = { path: 'stop', method: 'POST' };
- GLOBAL_QUERY_LOG = { path: 'querylog', method: 'GET' };
- GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' };
- GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
@@ -50,27 +47,6 @@ export default class Api {
return this.makeRequest(path, method);
}
- getQueryLog() {
- const { path, method } = this.GLOBAL_QUERY_LOG;
- return this.makeRequest(path, method);
- }
-
- downloadQueryLog() {
- const { path, method } = this.GLOBAL_QUERY_LOG;
- const queryString = '?download=1';
- return this.makeRequest(path + queryString, method);
- }
-
- enableQueryLog() {
- const { path, method } = this.GLOBAL_QUERY_LOG_ENABLE;
- return this.makeRequest(path, method);
- }
-
- disableQueryLog() {
- const { path, method } = this.GLOBAL_QUERY_LOG_DISABLE;
- return this.makeRequest(path, method);
- }
-
setUpstream(url) {
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
const config = {
@@ -521,4 +497,37 @@ export default class Api {
const { path, method } = this.STATS_RESET;
return this.makeRequest(path, method);
}
+
+ // Query log
+ GET_QUERY_LOG = { path: 'querylog', method: 'GET' };
+ QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' };
+ QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' };
+ QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
+
+ getQueryLog() {
+ const { path, method } = this.GET_QUERY_LOG;
+ return this.makeRequest(path, method);
+ }
+
+ getQueryLogInfo() {
+ const { path, method } = this.QUERY_LOG_INFO;
+ return this.makeRequest(path, method);
+ }
+
+ setQueryLogConfig(data) {
+ const { path, method } = this.QUERY_LOG_CONFIG;
+ const config = {
+ data,
+ headers: { 'Content-Type': 'application/json' },
+ };
+ return this.makeRequest(path, method, config);
+ }
+
+ clearQueryLog() {
+ const { path, method } = this.QUERY_LOG_CLEAR;
+ return this.makeRequest(path, method);
+ }
}
+
+const apiClient = new Api();
+export default apiClient;
diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js
index 5d5471e9..e4f880b0 100644
--- a/client/src/components/Logs/index.js
+++ b/client/src/components/Logs/index.js
@@ -1,7 +1,6 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
-import { saveAs } from 'file-saver/FileSaver';
import escapeRegExp from 'lodash/escapeRegExp';
import endsWith from 'lodash/endsWith';
import { Trans, withNamespaces } from 'react-i18next';
@@ -17,7 +16,6 @@ import PopoverFiltered from '../ui/PopoverFilter';
import Popover from '../ui/Popover';
import './Logs.css';
-const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
const FILTERED_REASON = 'Filtered';
const RESPONSE_FILTER = {
ALL: 'all',
@@ -29,18 +27,19 @@ class Logs extends Component {
this.getLogs();
this.props.getFilteringStatus();
this.props.getClients();
+ this.props.getLogsConfig();
}
componentDidUpdate(prevProps) {
// get logs when queryLog becomes enabled
- if (this.props.dashboard.queryLogEnabled && !prevProps.dashboard.queryLogEnabled) {
+ if (this.props.queryLogs.enabled && !prevProps.queryLogs.enabled) {
this.props.getLogs();
}
}
getLogs = () => {
// get logs on initialization if queryLogIsEnabled
- if (this.props.dashboard.queryLogEnabled) {
+ if (this.props.queryLogs.enabled) {
this.props.getLogs();
}
};
@@ -155,10 +154,7 @@ class Logs extends Component {
} else {
const filterItem = Object.keys(filters).filter(key => filters[key].id === filterId)[0];
- if (
- typeof filterItem !== 'undefined' &&
- typeof filters[filterItem] !== 'undefined'
- ) {
+ if (typeof filterItem !== 'undefined' && typeof filters[filterItem] !== 'undefined') {
filterName = filters[filterItem].name;
}
@@ -255,10 +251,7 @@ class Logs extends Component {
if (filter.value === RESPONSE_FILTER.FILTERED) {
// eslint-disable-next-line no-underscore-dangle
const { reason } = row._original;
- return (
- this.checkFiltered(reason) ||
- this.checkWhiteList(reason)
- );
+ return this.checkFiltered(reason) || this.checkWhiteList(reason);
}
return true;
},
@@ -347,74 +340,44 @@ class Logs extends Component {
return null;
}
- handleDownloadButton = async (e) => {
- e.preventDefault();
- const data = await this.props.downloadQueryLog();
- const jsonStr = JSON.stringify(data);
- const dataBlob = new Blob([jsonStr], { type: 'text/plain;charset=utf-8' });
- saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
- };
-
- renderButtons(queryLogEnabled, logStatusProcessing) {
- if (queryLogEnabled) {
- return (
-