diff --git a/client/package.json b/client/package.json index 10080eb5..6c5f8e4e 100644 --- a/client/package.json +++ b/client/package.json @@ -6,7 +6,7 @@ "build-dev": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js", "watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js --watch", "build-prod": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.js", - "lint": "eslint frontend/" + "lint": "eslint client/" }, "dependencies": { "@nivo/line": "^0.49.1", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 09f94851..5043019a 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -72,12 +72,14 @@ "stats_adult": "Blocked adult websites", "stats_query_domain": "Top queried domains", "for_last_24_hours": "for the last 24 hours", + "for_last_days": "for the last {{value}} days", "no_domains_found": "No domains found", "requests_count": "Requests count", "top_blocked_domains": "Top blocked domains", "top_clients": "Top clients", "no_clients_found": "No clients found", "general_statistics": "General statistics", + "number_of_dns_query_days": "A number of DNS quieries processed for the last {{value}} days", "number_of_dns_query_24_hours": "A number of DNS quieries processed for the last 24 hours", "number_of_dns_query_blocked_24_hours": "A number of DNS requests blocked by adblock filters and hosts blocklists", "number_of_dns_query_blocked_24_hours_by_sec": "A number of DNS requests blocked by the AdGuard browsing security module", @@ -300,7 +302,6 @@ "client_deleted": "Client \"{{key}}\" successfully deleted", "client_added": "Client \"{{key}}\" successfully added", "client_updated": "Client \"{{key}}\" successfully updated", - "table_statistics": "Requests count (last 24 hours)", "clients_not_found": "No clients found", "client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?", "filter_confirm_delete": "Are you sure you want to delete filter?", @@ -366,5 +367,6 @@ "config_successfully_saved": "Configuration successfully saved", "interval_24_hour": "24 hours", "interval_days": "{{value}} days", - "time_period": "Time period" + "time_period": "Time period", + "domain": "Domain" } diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 472ae31c..98d77ca4 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -1,11 +1,10 @@ import { createAction } from 'redux-actions'; -import round from 'lodash/round'; import { t } from 'i18next'; import { showLoading, hideLoading } from 'react-redux-loading-bar'; import axios from 'axios'; import versionCompare from '../helpers/versionCompare'; -import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers'; +import { normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers'; import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants'; import { getTlsStatus } from './encryption'; import Api from '../api/Api'; @@ -246,27 +245,6 @@ export const getClients = () => async (dispatch) => { } }; -export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST'); -export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE'); -export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS'); - -export const getTopStats = () => async (dispatch, getState) => { - dispatch(getTopStatsRequest()); - const timer = setInterval(async () => { - const state = getState(); - if (state.dashboard.isCoreRunning) { - clearInterval(timer); - try { - const stats = await apiClient.getGlobalStatsTop(); - dispatch(getTopStatsSuccess(stats)); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(getTopStatsFailure(error)); - } - } - }, 100); -}; - export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST'); export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE'); export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS'); @@ -314,27 +292,6 @@ export const disableDns = () => async (dispatch) => { } }; -export const getStatsRequest = createAction('GET_STATS_REQUEST'); -export const getStatsFailure = createAction('GET_STATS_FAILURE'); -export const getStatsSuccess = createAction('GET_STATS_SUCCESS'); - -export const getStats = () => async (dispatch) => { - dispatch(getStatsRequest()); - try { - const stats = await apiClient.getGlobalStats(); - - const processedStats = { - ...stats, - avg_processing_time: round(stats.avg_processing_time, 2), - }; - - dispatch(getStatsSuccess(processedStats)); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(getStatsFailure()); - } -}; - export const getLogsRequest = createAction('GET_LOGS_REQUEST'); export const getLogsFailure = createAction('GET_LOGS_FAILURE'); export const getLogsSuccess = createAction('GET_LOGS_SUCCESS'); @@ -473,22 +430,6 @@ export const refreshFilters = () => async (dispatch) => { export const handleRulesChange = createAction('HANDLE_RULES_CHANGE'); -export const getStatsHistoryRequest = createAction('GET_STATS_HISTORY_REQUEST'); -export const getStatsHistoryFailure = createAction('GET_STATS_HISTORY_FAILURE'); -export const getStatsHistorySuccess = createAction('GET_STATS_HISTORY_SUCCESS'); - -export const getStatsHistory = () => async (dispatch) => { - dispatch(getStatsHistoryRequest()); - try { - const statsHistory = await apiClient.getGlobalStatsHistory(); - const normalizedHistory = normalizeHistory(statsHistory); - dispatch(getStatsHistorySuccess(normalizedHistory)); - } catch (error) { - dispatch(addErrorToast({ error })); - dispatch(getStatsHistoryFailure()); - } -}; - export const addFilterRequest = createAction('ADD_FILTER_REQUEST'); export const addFilterFailure = createAction('ADD_FILTER_FAILURE'); export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS'); diff --git a/client/src/actions/stats.js b/client/src/actions/stats.js index 19175817..a6eb0f13 100644 --- a/client/src/actions/stats.js +++ b/client/src/actions/stats.js @@ -1,6 +1,8 @@ import { createAction } from 'redux-actions'; + import Api from '../api/Api'; import { addErrorToast, addSuccessToast } from './index'; +import { normalizeTopStats } from '../helpers/helpers'; const apiClient = new Api(); @@ -34,3 +36,26 @@ export const setStatsConfig = config => async (dispatch) => { dispatch(setStatsConfigFailure()); } }; + +export const getStatsRequest = createAction('GET_STATS_REQUEST'); +export const getStatsFailure = createAction('GET_STATS_FAILURE'); +export const getStatsSuccess = createAction('GET_STATS_SUCCESS'); + +export const getStats = () => async (dispatch) => { + dispatch(getStatsRequest()); + try { + const stats = await apiClient.getStats(); + + const normalizedStats = { + ...stats, + top_blocked_domains: normalizeTopStats(stats.top_blocked_domains), + top_clients: normalizeTopStats(stats.top_clients), + top_queried_domains: normalizeTopStats(stats.top_queried_domains), + }; + + dispatch(getStatsSuccess(normalizedStats)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getStatsFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 2967ec53..afd75ea5 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -1,6 +1,4 @@ import axios from 'axios'; -import subHours from 'date-fns/sub_hours'; -import dateFormat from 'date-fns/format'; export default class Api { baseUrl = 'control'; @@ -26,11 +24,8 @@ export default class Api { // Global methods GLOBAL_RESTART = { path: 'restart', method: 'POST' }; GLOBAL_START = { path: 'start', method: 'POST' }; - GLOBAL_STATS = { path: 'stats', method: 'GET' }; - GLOBAL_STATS_HISTORY = { path: 'stats_history', method: 'GET' }; GLOBAL_STATUS = { path: 'status', method: 'GET' }; GLOBAL_STOP = { path: 'stop', method: 'POST' }; - GLOBAL_STATS_TOP = { path: 'stats_top', method: 'GET' }; 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' }; @@ -56,36 +51,11 @@ export default class Api { return this.makeRequest(path, method); } - getGlobalStats() { - const { path, method } = this.GLOBAL_STATS; - return this.makeRequest(path, method); - } - - getGlobalStatsHistory() { - const { path, method } = this.GLOBAL_STATS_HISTORY; - const format = 'YYYY-MM-DDTHH:mm:ssZ'; - const dateNow = Date.now(); - - const config = { - params: { - start_time: dateFormat(subHours(dateNow, 24), format), - end_time: dateFormat(dateNow, format), - time_unit: 'hours', - }, - }; - return this.makeRequest(path, method, config); - } - getGlobalStatus() { const { path, method } = this.GLOBAL_STATUS; return this.makeRequest(path, method); } - getGlobalStatsTop() { - const { path, method } = this.GLOBAL_STATS_TOP; - return this.makeRequest(path, method); - } - getQueryLog() { const { path, method } = this.GLOBAL_QUERY_LOG; return this.makeRequest(path, method); @@ -529,9 +499,15 @@ export default class Api { } // Settings for statistics + GET_STATS = { path: 'stats', method: 'GET' }; STATS_INFO = { path: 'stats_info', method: 'GET' }; STATS_CONFIG = { path: 'stats_config', method: 'POST' }; + getStats() { + const { path, method } = this.GET_STATS; + return this.makeRequest(path, method); + } + getStatsInfo() { const { path, method } = this.STATS_INFO; return this.makeRequest(path, method); diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js index 25b3c8d6..f88ca325 100644 --- a/client/src/components/Dashboard/BlockedDomains.js +++ b/client/src/components/Dashboard/BlockedDomains.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; -import map from 'lodash/map'; import { withNamespaces, Trans } from 'react-i18next'; import Card from '../ui/Card'; @@ -13,52 +12,58 @@ import { getPercent } from '../../helpers/helpers'; import { STATUS_COLORS } from '../../helpers/constants'; class BlockedDomains extends Component { - columns = [{ - Header: 'IP', - accessor: 'ip', - Cell: (row) => { - const { value } = row; - const trackerData = getTrackerData(value); + columns = [ + { + Header: domain, + accessor: 'domain', + Cell: (row) => { + const { value } = row; + const trackerData = getTrackerData(value); - return ( -
-
- {value} + return ( +
+
+ {value} +
+ {trackerData && }
- {trackerData && } -
- ); + ); + }, }, - }, { - Header: requests_count, - accessor: 'domain', - maxWidth: 190, - Cell: ({ value }) => { - const { - blockedFiltering, - replacedSafebrowsing, - replacedParental, - } = this.props; - const blocked = blockedFiltering + replacedSafebrowsing + replacedParental; - const percent = getPercent(blocked, value); + { + Header: requests_count, + accessor: 'count', + maxWidth: 190, + Cell: ({ value }) => { + const { blockedFiltering, replacedSafebrowsing, replacedParental } = this.props; + const blocked = blockedFiltering + replacedSafebrowsing + replacedParental; + const percent = getPercent(blocked, value); - return ( - - ); + return ; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topBlockedDomains, subtitle, + } = this.props; + return ( - + ( - { ip: prop, domain: value } - ))} + data={topBlockedDomains.map(item => ({ + domain: item.name, + count: item.count, + }))} columns={this.columns} showPagination={false} - noDataText={ t('no_domains_found') } + noDataText={t('no_domains_found')} minRows={6} className="-striped -highlight card-table-overflow stats__table" /> @@ -68,12 +73,13 @@ class BlockedDomains extends Component { } BlockedDomains.propTypes = { - topBlockedDomains: PropTypes.object.isRequired, + topBlockedDomains: PropTypes.array.isRequired, blockedFiltering: PropTypes.number.isRequired, replacedSafebrowsing: PropTypes.number.isRequired, replacedParental: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, - t: PropTypes.func, + subtitle: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, }; export default withNamespaces()(BlockedDomains); diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index fbef279e..3fcef21c 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; -import map from 'lodash/map'; import { Trans, withNamespaces } from 'react-i18next'; import Card from '../ui/Card'; @@ -18,55 +17,71 @@ class Clients extends Component { return STATUS_COLORS.yellow; } return STATUS_COLORS.red; - } + }; - columns = [{ - Header: 'IP', - accessor: 'ip', - Cell: ({ value }) => { - const clientName = getClientName(this.props.clients, value) - || getClientName(this.props.autoClients, value); - let client; + columns = [ + { + Header: 'IP', + accessor: 'ip', + Cell: ({ value }) => { + const clientName = + getClientName(this.props.clients, value) || + getClientName(this.props.autoClients, value); + let client; - if (clientName) { - client = {clientName} ({value}); - } else { - client = value; - } + if (clientName) { + client = ( + + {clientName} ({value}) + + ); + } else { + client = value; + } - return ( -
- - {client} - -
- ); + return ( +
+ + {client} + +
+ ); + }, + sortMethod: (a, b) => + parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10), }, - sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10), - }, { - Header: requests_count, - accessor: 'count', - Cell: ({ value }) => { - const percent = getPercent(this.props.dnsQueries, value); - const percentColor = this.getPercentColor(percent); + { + Header: requests_count, + accessor: 'count', + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); - return ( - - ); + return ; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topClients, subtitle, + } = this.props; + return ( - + ( - { ip: prop, count: value } - ))} + data={topClients.map(item => ({ + ip: item.name, + count: item.count, + }))} columns={this.columns} showPagination={false} - noDataText={ t('no_clients_found') } + noDataText={t('no_clients_found')} minRows={6} className="-striped -highlight card-table-overflow" /> @@ -76,12 +91,13 @@ class Clients extends Component { } Clients.propTypes = { - topClients: PropTypes.object.isRequired, + topClients: PropTypes.array.isRequired, dnsQueries: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, clients: PropTypes.array.isRequired, autoClients: PropTypes.array.isRequired, - t: PropTypes.func, + subtitle: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, }; export default withNamespaces()(Clients); diff --git a/client/src/components/Dashboard/Counters.js b/client/src/components/Dashboard/Counters.js index c63e760d..393c8d34 100644 --- a/client/src/components/Dashboard/Counters.js +++ b/client/src/components/Dashboard/Counters.js @@ -1,88 +1,116 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Trans, withNamespaces } from 'react-i18next'; +import round from 'lodash/round'; import Card from '../ui/Card'; import Tooltip from '../ui/Tooltip'; const tooltipType = 'tooltip-custom--narrow'; -const Counters = props => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- dns_query - - - - {props.dnsQueries} - -
- - blocked_by - - - - - {props.blockedFiltering} - -
- stats_malware_phishing - - - - {props.replacedSafebrowsing} - -
- stats_adult - - - - {props.replacedParental} - -
- enforced_save_search - - - - {props.replacedSafesearch} - -
- average_processing_time - - - - {props.avgProcessingTime} - -
-
-); +const Counters = (props) => { + const { + t, + interval, + refreshButton, + subtitle, + dnsQueries, + blockedFiltering, + replacedSafebrowsing, + replacedParental, + replacedSafesearch, + avgProcessingTime, + } = props; + + const tooltipTitle = + interval === 1 + ? t('number_of_dns_query_24_hours') + : t('number_of_dns_query_days', { value: interval }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ dns_query + + + {dnsQueries} +
+ + blocked_by + + + + {blockedFiltering} +
+ stats_malware_phishing + + + {replacedSafebrowsing} +
+ stats_adult + + + {replacedParental} +
+ enforced_save_search + + + {replacedSafesearch} +
+ average_processing_time + + + + {avgProcessingTime ? `${round(avgProcessingTime, 2)} ms` : 0} + +
+
+ ); +}; Counters.propTypes = { dnsQueries: PropTypes.number.isRequired, @@ -92,6 +120,8 @@ Counters.propTypes = { replacedSafesearch: PropTypes.number.isRequired, avgProcessingTime: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, + subtitle: PropTypes.string.isRequired, + interval: PropTypes.number.isRequired, t: PropTypes.func.isRequired, }; diff --git a/client/src/components/Dashboard/Dashboard.css b/client/src/components/Dashboard/Dashboard.css index 0f1e60f5..73ea0567 100644 --- a/client/src/components/Dashboard/Dashboard.css +++ b/client/src/components/Dashboard/Dashboard.css @@ -20,3 +20,8 @@ border-top: 6px solid transparent; border-bottom: 6px solid #585965; } + +.card-chart-bg { + left: -20px; + width: calc(100% + 20px); +} diff --git a/client/src/components/Dashboard/QueriedDomains.js b/client/src/components/Dashboard/QueriedDomains.js index 4d16a27c..82fde13e 100644 --- a/client/src/components/Dashboard/QueriedDomains.js +++ b/client/src/components/Dashboard/QueriedDomains.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; -import map from 'lodash/map'; import { withNamespaces, Trans } from 'react-i18next'; import Card from '../ui/Card'; @@ -20,49 +19,58 @@ class QueriedDomains extends Component { return STATUS_COLORS.yellow; } return STATUS_COLORS.green; - } + }; - columns = [{ - Header: 'IP', - accessor: 'ip', - Cell: (row) => { - const { value } = row; - const trackerData = getTrackerData(value); + columns = [ + { + Header: domain, + accessor: 'domain', + Cell: (row) => { + const { value } = row; + const trackerData = getTrackerData(value); - return ( -
-
- {value} + return ( +
+
+ {value} +
+ {trackerData && }
- {trackerData && } -
- ); + ); + }, }, - }, { - Header: requests_count, - accessor: 'count', - maxWidth: 190, - Cell: ({ value }) => { - const percent = getPercent(this.props.dnsQueries, value); - const percentColor = this.getPercentColor(percent); + { + Header: requests_count, + accessor: 'count', + maxWidth: 190, + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); - return ( - - ); + return ; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topQueriedDomains, subtitle, + } = this.props; return ( - + ( - { ip: prop, count: value } - ))} + data={topQueriedDomains.map(item => ({ + domain: item.name, + count: item.count, + }))} columns={this.columns} showPagination={false} - noDataText={ t('no_domains_found') } + noDataText={t('no_domains_found')} minRows={6} className="-striped -highlight card-table-overflow stats__table" /> @@ -72,10 +80,11 @@ class QueriedDomains extends Component { } QueriedDomains.propTypes = { - topQueriedDomains: PropTypes.object.isRequired, + topQueriedDomains: PropTypes.array.isRequired, dnsQueries: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, - t: PropTypes.func, + subtitle: PropTypes.string.isRequired, + t: PropTypes.func.isRequired, }; export default withNamespaces()(QueriedDomains); diff --git a/client/src/components/Dashboard/Statistics.js b/client/src/components/Dashboard/Statistics.js index d0cc9646..8331add6 100644 --- a/client/src/components/Dashboard/Statistics.js +++ b/client/src/components/Dashboard/Statistics.js @@ -5,37 +5,42 @@ import { Trans, withNamespaces } from 'react-i18next'; import Card from '../ui/Card'; import Line from '../ui/Line'; -import { getPercent } from '../../helpers/helpers'; +import { getPercent, normalizeHistory } from '../../helpers/helpers'; import { STATUS_COLORS } from '../../helpers/constants'; class Statistics extends Component { + getNormalizedHistory = (data, interval, id) => [{ data: normalizeHistory(data, interval), id }]; + render() { const { + interval, dnsQueries, blockedFiltering, replacedSafebrowsing, replacedParental, + numDnsQueries, + numBlockedFiltering, + numReplacedSafebrowsing, + numReplacedParental, } = this.props; - const filteringData = [this.props.history[1]]; - const queriesData = [this.props.history[2]]; - const parentalData = [this.props.history[3]]; - const safebrowsingData = [this.props.history[4]]; - return (
- {dnsQueries} + {numDnsQueries}
dns_query
- +
@@ -43,10 +48,10 @@ class Statistics extends Component {
- {blockedFiltering} + {numBlockedFiltering}
- {getPercent(dnsQueries, blockedFiltering)} + {getPercent(numDnsQueries, numBlockedFiltering)}
- +
@@ -63,17 +75,24 @@ class Statistics extends Component {
- {replacedSafebrowsing} + {numReplacedSafebrowsing}
- {getPercent(dnsQueries, replacedSafebrowsing)} + {getPercent(numDnsQueries, numReplacedSafebrowsing)}
stats_malware_phishing
- +
@@ -81,17 +100,24 @@ class Statistics extends Component {
- {replacedParental} + {numReplacedParental}
- {getPercent(dnsQueries, replacedParental)} + {getPercent(numDnsQueries, numReplacedParental)}
stats_adult
- +
@@ -101,11 +127,15 @@ class Statistics extends Component { } Statistics.propTypes = { - history: PropTypes.array.isRequired, - dnsQueries: PropTypes.number.isRequired, - blockedFiltering: PropTypes.number.isRequired, - replacedSafebrowsing: PropTypes.number.isRequired, - replacedParental: PropTypes.number.isRequired, + interval: PropTypes.number.isRequired, + dnsQueries: PropTypes.array.isRequired, + blockedFiltering: PropTypes.array.isRequired, + replacedSafebrowsing: PropTypes.array.isRequired, + replacedParental: PropTypes.array.isRequired, + numDnsQueries: PropTypes.number.isRequired, + numBlockedFiltering: PropTypes.number.isRequired, + numReplacedSafebrowsing: PropTypes.number.isRequired, + numReplacedParental: PropTypes.number.isRequired, refreshButton: PropTypes.node.isRequired, }; diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index ad207ba0..60fa90ac 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -19,10 +19,9 @@ class Dashboard extends Component { getAllStats = () => { this.props.getStats(); - this.props.getStatsHistory(); - this.props.getTopStats(); + this.props.getStatsConfig(); this.props.getClients(); - } + }; getToggleFilteringButton = () => { const { protectionEnabled, processingProtection } = this.props.dashboard; @@ -39,16 +38,20 @@ class Dashboard extends Component { {buttonText} ); - } + }; render() { - const { dashboard, t } = this.props; + const { dashboard, stats, t } = this.props; const dashboardProcessing = dashboard.processing || - dashboard.processingStats || - dashboard.processingStatsHistory || dashboard.processingClients || - dashboard.processingTopStats; + stats.processingStats || + stats.processingGetConfig; + + const subtitle = + stats.interval === 1 + ? t('for_last_24_hours') + : t('for_last_days', { value: stats.interval }); const refreshFullButton = ( ); + const refreshButton = (