From a753ae86cc9c546658a12be77f600bcf0f18623f Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Wed, 4 Sep 2019 17:39:35 +0300 Subject: [PATCH] + client: handle logs configuration --- client/package-lock.json | 5 - client/package.json | 1 - client/src/__locales/en.json | 15 +-- client/src/actions/index.js | 65 +--------- client/src/actions/queryLogs.js | 69 +++++++++++ client/src/actions/stats.js | 6 +- client/src/api/Api.js | 54 +++++---- client/src/components/Logs/index.js | 112 ++++++------------ .../components/Settings/LogsConfig/Form.js | 82 +++++++++++++ .../components/Settings/LogsConfig/index.js | 69 +++++++++++ .../components/Settings/StatsConfig/Form.js | 11 +- .../components/Settings/StatsConfig/index.js | 10 +- client/src/components/Settings/index.js | 34 ++++-- client/src/components/ui/Checkbox.css | 2 +- client/src/containers/Logs.js | 6 +- client/src/containers/Settings.js | 10 +- client/src/helpers/constants.js | 2 + client/src/reducers/index.js | 28 +---- client/src/reducers/queryLogs.js | 49 ++++++++ 19 files changed, 401 insertions(+), 229 deletions(-) create mode 100644 client/src/actions/queryLogs.js create mode 100644 client/src/components/Settings/LogsConfig/Form.js create mode 100644 client/src/components/Settings/LogsConfig/index.js create mode 100644 client/src/reducers/queryLogs.js 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>settings", "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/index.js b/client/src/actions/index.js index 98d77ca4..70913e29 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -4,7 +4,7 @@ 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'; @@ -292,52 +292,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 +419,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/queryLogs.js b/client/src/actions/queryLogs.js new file mode 100644 index 00000000..1295ff6a --- /dev/null +++ b/client/src/actions/queryLogs.js @@ -0,0 +1,69 @@ +import { createAction } from 'redux-actions'; + +import Api from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; +import { normalizeLogs } from '../helpers/helpers'; + +const apiClient = new Api(); + +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/stats.js b/client/src/actions/stats.js index a24b3ec7..e8483443 100644 --- a/client/src/actions/stats.js +++ b/client/src/actions/stats.js @@ -6,9 +6,9 @@ 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..495be5b8 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -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,34 @@ 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); + } } diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js index 5d5471e9..5c1db03c 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 ( - - - - - - ); - } - - return ( - - ); - } - render() { const { queryLogs, dashboard, t } = this.props; - const { queryLogEnabled } = dashboard; + const { enabled, processingGetLogs, processingGetConfig } = queryLogs; + const { processingClients } = dashboard; + const isDataReady = + !processingGetLogs && !processingGetConfig && !dashboard.processingClients; + + const refreshButton = enabled ? ( + + ) : ( + '' + ); + return ( -
- {this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)} -
+ {refreshButton}
- {queryLogEnabled && - queryLogs.getLogsProcessing && - dashboard.processingClients && } - {queryLogEnabled && - !queryLogs.getLogsProcessing && - !dashboard.processingClients && - this.renderLogs(queryLogs.logs)} + {enabled && (processingGetLogs || processingClients || processingGetConfig) && ( + + )} + {enabled && isDataReady && this.renderLogs(queryLogs.logs)} + {!enabled && isDataReady && ( +
+ link]}> + query_log_disabled + +
+ )}
); @@ -425,13 +388,12 @@ Logs.propTypes = { getLogs: PropTypes.func.isRequired, queryLogs: PropTypes.object.isRequired, dashboard: PropTypes.object.isRequired, - toggleLogStatus: PropTypes.func.isRequired, - downloadQueryLog: PropTypes.func.isRequired, getFilteringStatus: PropTypes.func.isRequired, filtering: PropTypes.object.isRequired, setRules: PropTypes.func.isRequired, addSuccessToast: PropTypes.func.isRequired, getClients: PropTypes.func.isRequired, + getLogsConfig: PropTypes.func.isRequired, t: PropTypes.func.isRequired, }; diff --git a/client/src/components/Settings/LogsConfig/Form.js b/client/src/components/Settings/LogsConfig/Form.js new file mode 100644 index 00000000..e90c6487 --- /dev/null +++ b/client/src/components/Settings/LogsConfig/Form.js @@ -0,0 +1,82 @@ +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, renderRadioField, toNumber } from '../../../helpers/form'; +import { QUERY_LOG_INTERVALS_DAYS } from '../../../helpers/constants'; + +const getIntervalFields = (processing, t, handleChange, toNumber) => + QUERY_LOG_INTERVALS_DAYS.map((interval) => { + const title = + interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval }); + + return ( + + ); + }); + +const Form = (props) => { + const { + handleSubmit, handleChange, processing, t, + } = props; + + return ( +
+
+
+
+ +
+
+
+ +
+
+
+
+ {getIntervalFields(processing, t, handleChange, toNumber)} +
+
+
+
+
+ ); +}; + +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: 'logConfigForm', + }), +])(Form); diff --git a/client/src/components/Settings/LogsConfig/index.js b/client/src/components/Settings/LogsConfig/index.js new file mode 100644 index 00000000..3c7b0a47 --- /dev/null +++ b/client/src/components/Settings/LogsConfig/index.js @@ -0,0 +1,69 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withNamespaces, Trans } from 'react-i18next'; +import debounce from 'lodash/debounce'; + +import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants'; +import Card from '../../ui/Card'; +import Form from './Form'; + +class LogsConfig extends Component { + handleFormChange = debounce((values) => { + this.props.setLogsConfig(values); + }, DEBOUNCE_TIMEOUT); + + handleLogsClear = () => { + const { t, clearLogs } = this.props; + // eslint-disable-next-line no-alert + if (window.confirm(t('query_log_confirm_clear'))) { + clearLogs(); + } + }; + + render() { + const { + t, enabled, interval, processing, processingClear, + } = this.props; + + return ( + +
+
+ + +
+
+ ); + } +} + +LogsConfig.propTypes = { + interval: PropTypes.number.isRequired, + enabled: PropTypes.bool.isRequired, + processing: PropTypes.bool.isRequired, + processingClear: PropTypes.bool.isRequired, + setLogsConfig: PropTypes.func.isRequired, + clearLogs: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +export default withNamespaces()(LogsConfig); diff --git a/client/src/components/Settings/StatsConfig/Form.js b/client/src/components/Settings/StatsConfig/Form.js index 463476c9..7da5286f 100644 --- a/client/src/components/Settings/StatsConfig/Form.js +++ b/client/src/components/Settings/StatsConfig/Form.js @@ -9,9 +9,8 @@ import { STATS_INTERVALS_DAYS } from '../../../helpers/constants'; const getIntervalFields = (processing, t, handleChange, toNumber) => STATS_INTERVALS_DAYS.map((interval) => { - const title = interval === 1 - ? t('interval_24_hour') - : t('interval_days', { count: interval }); + const title = + interval === 1 ? t('interval_24_hour') : t('interval_days', { count: interval }); return ( {
-
-
+
{getIntervalFields(processing, t, handleChange, toNumber)}
@@ -69,6 +68,6 @@ Form.propTypes = { export default flow([ withNamespaces(), reduxForm({ - form: 'logConfigForm', + form: 'statsConfigForm', }), ])(Form); diff --git a/client/src/components/Settings/StatsConfig/index.js b/client/src/components/Settings/StatsConfig/index.js index b649c6c9..0b9775d6 100644 --- a/client/src/components/Settings/StatsConfig/index.js +++ b/client/src/components/Settings/StatsConfig/index.js @@ -4,15 +4,15 @@ import { withNamespaces, Trans } from 'react-i18next'; import debounce from 'lodash/debounce'; import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants'; -import Form from './Form'; import Card from '../../ui/Card'; +import Form from './Form'; class StatsConfig extends Component { handleFormChange = debounce((values) => { this.props.setStatsConfig(values); }, DEBOUNCE_TIMEOUT); - handleReset = () => { + handleStatsReset = () => { const { t, resetStats } = this.props; // eslint-disable-next-line no-alert if (window.confirm(t('statistics_clear_confirm'))) { @@ -26,7 +26,7 @@ class StatsConfig extends Component { } = this.props; return ( - +
statistics_clear diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 5f7f2fd6..2ca90782 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -1,9 +1,10 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { withNamespaces, Trans } from 'react-i18next'; +import { withNamespaces } from 'react-i18next'; import Services from './Services'; import StatsConfig from './StatsConfig'; +import LogsConfig from './LogsConfig'; import Checkbox from '../ui/Checkbox'; import Loading from '../ui/Loading'; import PageTitle from '../ui/PageTitle'; @@ -39,6 +40,7 @@ class Settings extends Component { this.props.initSettings(this.settings); this.props.getBlockedServices(); this.props.getStatsConfig(); + this.props.getLogsConfig(); } renderSettings = (settings) => { @@ -55,11 +57,7 @@ class Settings extends Component { ); }); } - return ( -
- no_settings -
- ); + return ''; }; render() { @@ -70,13 +68,23 @@ class Settings extends Component { setStatsConfig, resetStats, stats, + queryLogs, + setLogsConfig, + clearLogs, t, } = this.props; + + const isDataReady = + !settings.processing && + !services.processing && + !stats.processingGetConfig && + !queryLogs.processingGetConfig; + return ( - {settings.processing && } - {!settings.processing && ( + {!isDataReady && } + {isDataReady && (
@@ -95,6 +103,16 @@ class Settings extends Component { resetStats={resetStats} />
+
+ +
{ @@ -10,12 +11,11 @@ const mapStateToProps = (state) => { const mapDispatchToProps = { getLogs, - toggleLogStatus, - downloadQueryLog, getFilteringStatus, setRules, addSuccessToast, getClients, + getLogsConfig, }; export default connect( diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js index 14f08cfc..726e10a0 100644 --- a/client/src/containers/Settings.js +++ b/client/src/containers/Settings.js @@ -2,14 +2,18 @@ import { connect } from 'react-redux'; import { initSettings, toggleSetting } from '../actions'; import { getBlockedServices, setBlockedServices } from '../actions/services'; import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats'; +import { toggleLogStatus, clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs'; import Settings from '../components/Settings'; const mapStateToProps = (state) => { - const { settings, services, stats } = state; + const { + settings, services, stats, queryLogs, + } = state; const props = { settings, services, stats, + queryLogs, }; return props; }; @@ -22,6 +26,10 @@ const mapDispatchToProps = { getStatsConfig, setStatsConfig, resetStats, + toggleLogStatus, + clearLogs, + getLogsConfig, + setLogsConfig, }; export default connect( diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 3551a41a..6001293a 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -263,3 +263,5 @@ export const FILTERED_STATUS = { }; export const STATS_INTERVALS_DAYS = [1, 7, 30, 90]; + +export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90]; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 2af571b2..49e7729f 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -12,6 +12,7 @@ import access from './access'; import rewrites from './rewrites'; import services from './services'; import stats from './stats'; +import queryLogs from './queryLogs'; const settings = handleActions({ [actions.initSettingsRequest]: state => ({ ...state, processing: true }), @@ -43,6 +44,7 @@ const settings = handleActions({ processingTestUpstream: false, processingSetUpstream: false, processingDhcpStatus: false, + settingsList: {}, }); const dashboard = handleActions({ @@ -54,7 +56,6 @@ const dashboard = handleActions({ running, dns_port: dnsPort, dns_addresses: dnsAddresses, - querylog_enabled: queryLogEnabled, upstream_dns: upstreamDns, bootstrap_dns: bootstrapDns, all_servers: allServers, @@ -69,7 +70,6 @@ const dashboard = handleActions({ dnsVersion: version, dnsPort, dnsAddresses, - queryLogEnabled, upstreamDns: upstreamDns.join('\n'), bootstrapDns: bootstrapDns.join('\n'), allServers, @@ -94,13 +94,6 @@ const dashboard = handleActions({ return newState; }, - [actions.toggleLogStatusRequest]: state => ({ ...state, logStatusProcessing: true }), - [actions.toggleLogStatusFailure]: state => ({ ...state, logStatusProcessing: false }), - [actions.toggleLogStatusSuccess]: (state) => { - const { queryLogEnabled } = state; - return ({ ...state, queryLogEnabled: !queryLogEnabled, logStatusProcessing: false }); - }, - [actions.getVersionRequest]: state => ({ ...state, processingVersion: true }), [actions.getVersionFailure]: state => ({ ...state, processingVersion: false }), [actions.getVersionSuccess]: (state, { payload }) => { @@ -179,7 +172,6 @@ const dashboard = handleActions({ }, { processing: true, isCoreRunning: false, - logStatusProcessing: false, processingVersion: true, processingFiltering: true, processingClients: true, @@ -197,22 +189,6 @@ const dashboard = handleActions({ autoClients: [], }); -const queryLogs = handleActions({ - [actions.getLogsRequest]: state => ({ ...state, getLogsProcessing: true }), - [actions.getLogsFailure]: state => ({ ...state, getLogsProcessing: false }), - [actions.getLogsSuccess]: (state, { payload }) => { - const newState = { ...state, logs: payload, getLogsProcessing: false }; - return newState; - }, - [actions.downloadQueryLogRequest]: state => ({ ...state, logsDownloading: true }), - [actions.downloadQueryLogFailure]: state => ({ ...state, logsDownloading: false }), - [actions.downloadQueryLogSuccess]: state => ({ ...state, logsDownloading: false }), -}, { - getLogsProcessing: false, - logsDownloading: false, - logs: [], -}); - const filtering = handleActions({ [actions.setRulesRequest]: state => ({ ...state, processingRules: true }), [actions.setRulesFailure]: state => ({ ...state, processingRules: false }), diff --git a/client/src/reducers/queryLogs.js b/client/src/reducers/queryLogs.js new file mode 100644 index 00000000..3dfbadff --- /dev/null +++ b/client/src/reducers/queryLogs.js @@ -0,0 +1,49 @@ +import { handleActions } from 'redux-actions'; + +import * as actions from '../actions/queryLogs'; + +const queryLogs = handleActions( + { + [actions.getLogsRequest]: state => ({ ...state, processingGetLogs: true }), + [actions.getLogsFailure]: state => ({ ...state, processingGetLogs: false }), + [actions.getLogsSuccess]: (state, { payload }) => { + const newState = { ...state, logs: payload, processingGetLogs: false }; + return newState; + }, + + [actions.clearLogsRequest]: state => ({ ...state, processingClear: true }), + [actions.clearLogsFailure]: state => ({ ...state, processingClear: false }), + [actions.clearLogsSuccess]: state => ({ + ...state, + logs: [], + processingClear: false, + }), + + [actions.getLogsConfigRequest]: state => ({ ...state, processingGetConfig: true }), + [actions.getLogsConfigFailure]: state => ({ ...state, processingGetConfig: false }), + [actions.getLogsConfigSuccess]: (state, { payload }) => ({ + ...state, + ...payload, + processingGetConfig: false, + }), + + [actions.setLogsConfigRequest]: state => ({ ...state, processingSetConfig: true }), + [actions.setLogsConfigFailure]: state => ({ ...state, processingSetConfig: false }), + [actions.setLogsConfigSuccess]: (state, { payload }) => ({ + ...state, + ...payload, + processingSetConfig: false, + }), + }, + { + processingGetLogs: true, + processingClear: false, + processingGetConfig: false, + processingSetConfig: false, + logs: [], + interval: 1, + enabled: true, + }, +); + +export default queryLogs;