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: <Trans>domain</Trans>, + accessor: 'domain', + Cell: (row) => { + const { value } = row; + const trackerData = getTrackerData(value); - return ( - <div className="logs__row"> - <div className="logs__text" title={value}> - {value} + return ( + <div className="logs__row"> + <div className="logs__text" title={value}> + {value} + </div> + {trackerData && <Popover data={trackerData} />} </div> - {trackerData && <Popover data={trackerData} />} - </div> - ); + ); + }, }, - }, { - Header: <Trans>requests_count</Trans>, - accessor: 'domain', - maxWidth: 190, - Cell: ({ value }) => { - const { - blockedFiltering, - replacedSafebrowsing, - replacedParental, - } = this.props; - const blocked = blockedFiltering + replacedSafebrowsing + replacedParental; - const percent = getPercent(blocked, value); + { + Header: <Trans>requests_count</Trans>, + accessor: 'count', + maxWidth: 190, + Cell: ({ value }) => { + const { blockedFiltering, replacedSafebrowsing, replacedParental } = this.props; + const blocked = blockedFiltering + replacedSafebrowsing + replacedParental; + const percent = getPercent(blocked, value); - return ( - <Cell value={value} percent={percent} color={STATUS_COLORS.red} /> - ); + return <Cell value={value} percent={percent} color={STATUS_COLORS.red} />; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topBlockedDomains, subtitle, + } = this.props; + return ( - <Card title={ t('top_blocked_domains') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}> + <Card + title={t('top_blocked_domains')} + subtitle={subtitle} + bodyType="card-table" + refresh={refreshButton} + > <ReactTable - data={map(this.props.topBlockedDomains, (value, prop) => ( - { 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 = <span>{clientName} <small>({value})</small></span>; - } else { - client = value; - } + if (clientName) { + client = ( + <span> + {clientName} <small>({value})</small> + </span> + ); + } else { + client = value; + } - return ( - <div className="logs__row logs__row--overflow"> - <span className="logs__text" title={value}> - {client} - </span> - </div> - ); + return ( + <div className="logs__row logs__row--overflow"> + <span className="logs__text" title={value}> + {client} + </span> + </div> + ); + }, + 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: <Trans>requests_count</Trans>, - accessor: 'count', - Cell: ({ value }) => { - const percent = getPercent(this.props.dnsQueries, value); - const percentColor = this.getPercentColor(percent); + { + Header: <Trans>requests_count</Trans>, + accessor: 'count', + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); - return ( - <Cell value={value} percent={percent} color={percentColor} /> - ); + return <Cell value={value} percent={percent} color={percentColor} />; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topClients, subtitle, + } = this.props; + return ( - <Card title={ t('top_clients') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}> + <Card + title={t('top_clients')} + subtitle={subtitle} + bodyType="card-table" + refresh={refreshButton} + > <ReactTable - data={map(this.props.topClients, (value, prop) => ( - { 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 => ( - <Card title={ props.t('general_statistics') } subtitle={ props.t('for_last_24_hours') } bodyType="card-table" refresh={props.refreshButton}> - <table className="table card-table"> - <tbody> - <tr> - <td> - <Trans>dns_query</Trans> - <Tooltip text={ props.t('number_of_dns_query_24_hours') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.dnsQueries} - </span> - </td> - </tr> - <tr> - <td> - <a href="#filters"> - <Trans>blocked_by</Trans> - </a> - <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.blockedFiltering} - </span> - </td> - </tr> - <tr> - <td> - <Trans>stats_malware_phishing</Trans> - <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_by_sec') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.replacedSafebrowsing} - </span> - </td> - </tr> - <tr> - <td> - <Trans>stats_adult</Trans> - <Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_adult') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.replacedParental} - </span> - </td> - </tr> - <tr> - <td> - <Trans>enforced_save_search</Trans> - <Tooltip text={ props.t('number_of_dns_query_to_safe_search') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.replacedSafesearch} - </span> - </td> - </tr> - <tr> - <td> - <Trans>average_processing_time</Trans> - <Tooltip text={ props.t('average_processing_time_hint') } type={tooltipType} /> - </td> - <td className="text-right"> - <span className="text-muted"> - {props.avgProcessingTime} - </span> - </td> - </tr> - </tbody> - </table> - </Card> -); +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 ( + <Card + title={t('general_statistics')} + subtitle={subtitle} + bodyType="card-table" + refresh={refreshButton} + > + <table className="table card-table"> + <tbody> + <tr> + <td> + <Trans>dns_query</Trans> + <Tooltip text={tooltipTitle} type={tooltipType} /> + </td> + <td className="text-right"> + <span className="text-muted">{dnsQueries}</span> + </td> + </tr> + <tr> + <td> + <a href="#filters"> + <Trans>blocked_by</Trans> + </a> + <Tooltip + text={t('number_of_dns_query_blocked_24_hours')} + type={tooltipType} + /> + </td> + <td className="text-right"> + <span className="text-muted">{blockedFiltering}</span> + </td> + </tr> + <tr> + <td> + <Trans>stats_malware_phishing</Trans> + <Tooltip + text={t('number_of_dns_query_blocked_24_hours_by_sec')} + type={tooltipType} + /> + </td> + <td className="text-right"> + <span className="text-muted">{replacedSafebrowsing}</span> + </td> + </tr> + <tr> + <td> + <Trans>stats_adult</Trans> + <Tooltip + text={t('number_of_dns_query_blocked_24_hours_adult')} + type={tooltipType} + /> + </td> + <td className="text-right"> + <span className="text-muted">{replacedParental}</span> + </td> + </tr> + <tr> + <td> + <Trans>enforced_save_search</Trans> + <Tooltip + text={t('number_of_dns_query_to_safe_search')} + type={tooltipType} + /> + </td> + <td className="text-right"> + <span className="text-muted">{replacedSafesearch}</span> + </td> + </tr> + <tr> + <td> + <Trans>average_processing_time</Trans> + <Tooltip text={t('average_processing_time_hint')} type={tooltipType} /> + </td> + <td className="text-right"> + <span className="text-muted"> + {avgProcessingTime ? `${round(avgProcessingTime, 2)} ms` : 0} + </span> + </td> + </tr> + </tbody> + </table> + </Card> + ); +}; 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: <Trans>domain</Trans>, + accessor: 'domain', + Cell: (row) => { + const { value } = row; + const trackerData = getTrackerData(value); - return ( - <div className="logs__row"> - <div className="logs__text" title={value}> - {value} + return ( + <div className="logs__row"> + <div className="logs__text" title={value}> + {value} + </div> + {trackerData && <Popover data={trackerData} />} </div> - {trackerData && <Popover data={trackerData} />} - </div> - ); + ); + }, }, - }, { - Header: <Trans>requests_count</Trans>, - accessor: 'count', - maxWidth: 190, - Cell: ({ value }) => { - const percent = getPercent(this.props.dnsQueries, value); - const percentColor = this.getPercentColor(percent); + { + Header: <Trans>requests_count</Trans>, + accessor: 'count', + maxWidth: 190, + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); - return ( - <Cell value={value} percent={percent} color={percentColor} /> - ); + return <Cell value={value} percent={percent} color={percentColor} />; + }, }, - }]; + ]; render() { - const { t } = this.props; + const { + t, refreshButton, topQueriedDomains, subtitle, + } = this.props; return ( - <Card title={ t('stats_query_domain') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}> + <Card + title={t('stats_query_domain')} + subtitle={subtitle} + bodyType="card-table" + refresh={refreshButton} + > <ReactTable - data={map(this.props.topQueriedDomains, (value, prop) => ( - { 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 ( <div className="row"> <div className="col-sm-6 col-lg-3"> <Card type="card--full" bodyType="card-wrap"> <div className="card-body-stats"> <div className="card-value card-value-stats text-blue"> - {dnsQueries} + {numDnsQueries} </div> <div className="card-title-stats"> <Trans>dns_query</Trans> </div> </div> <div className="card-chart-bg"> - <Line data={queriesData} color={STATUS_COLORS.blue}/> + <Line + data={this.getNormalizedHistory(dnsQueries, interval, 'dnsQueries')} + color={STATUS_COLORS.blue} + /> </div> </Card> </div> @@ -43,10 +48,10 @@ class Statistics extends Component { <Card type="card--full" bodyType="card-wrap"> <div className="card-body-stats"> <div className="card-value card-value-stats text-red"> - {blockedFiltering} + {numBlockedFiltering} </div> <div className="card-value card-value-percent text-red"> - {getPercent(dnsQueries, blockedFiltering)} + {getPercent(numDnsQueries, numBlockedFiltering)} </div> <div className="card-title-stats"> <a href="#filters"> @@ -55,7 +60,14 @@ class Statistics extends Component { </div> </div> <div className="card-chart-bg"> - <Line data={filteringData} color={STATUS_COLORS.red}/> + <Line + data={this.getNormalizedHistory( + blockedFiltering, + interval, + 'blockedFiltering', + )} + color={STATUS_COLORS.red} + /> </div> </Card> </div> @@ -63,17 +75,24 @@ class Statistics extends Component { <Card type="card--full" bodyType="card-wrap"> <div className="card-body-stats"> <div className="card-value card-value-stats text-green"> - {replacedSafebrowsing} + {numReplacedSafebrowsing} </div> <div className="card-value card-value-percent text-green"> - {getPercent(dnsQueries, replacedSafebrowsing)} + {getPercent(numDnsQueries, numReplacedSafebrowsing)} </div> <div className="card-title-stats"> <Trans>stats_malware_phishing</Trans> </div> </div> <div className="card-chart-bg"> - <Line data={safebrowsingData} color={STATUS_COLORS.green}/> + <Line + data={this.getNormalizedHistory( + replacedSafebrowsing, + interval, + 'replacedSafebrowsing', + )} + color={STATUS_COLORS.green} + /> </div> </Card> </div> @@ -81,17 +100,24 @@ class Statistics extends Component { <Card type="card--full" bodyType="card-wrap"> <div className="card-body-stats"> <div className="card-value card-value-stats text-yellow"> - {replacedParental} + {numReplacedParental} </div> <div className="card-value card-value-percent text-yellow"> - {getPercent(dnsQueries, replacedParental)} + {getPercent(numDnsQueries, numReplacedParental)} </div> <div className="card-title-stats"> <Trans>stats_adult</Trans> </div> </div> <div className="card-chart-bg"> - <Line data={parentalData} color={STATUS_COLORS.yellow}/> + <Line + data={this.getNormalizedHistory( + replacedParental, + interval, + 'replacedParental', + )} + color={STATUS_COLORS.yellow} + /> </div> </Card> </div> @@ -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 { <Trans>{buttonText}</Trans> </button> ); - } + }; 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 = ( <button @@ -59,6 +62,7 @@ class Dashboard extends Component { <Trans>refresh_statics</Trans> </button> ); + const refreshButton = ( <button type="button" @@ -73,87 +77,85 @@ class Dashboard extends Component { return ( <Fragment> - <PageTitle title={ t('dashboard') }> + <PageTitle title={t('dashboard')}> <div className="page-title__actions"> {this.getToggleFilteringButton()} {refreshFullButton} </div> </PageTitle> {dashboardProcessing && <Loading />} - {!dashboardProcessing && + {!dashboardProcessing && ( <div className="row row-cards"> - {dashboard.statsHistory && - <div className="col-lg-12"> - <Statistics - history={dashboard.statsHistory} - refreshButton={refreshButton} - dnsQueries={dashboard.stats.dns_queries} - blockedFiltering={dashboard.stats.blocked_filtering} - replacedSafebrowsing={dashboard.stats.replaced_safebrowsing} - replacedParental={dashboard.stats.replaced_parental} - /> - </div> - } - <div className="col-lg-6"> - {dashboard.stats && - <Counters - refreshButton={refreshButton} - dnsQueries={dashboard.stats.dns_queries} - blockedFiltering={dashboard.stats.blocked_filtering} - replacedSafebrowsing={dashboard.stats.replaced_safebrowsing} - replacedParental={dashboard.stats.replaced_parental} - replacedSafesearch={dashboard.stats.replaced_safesearch} - avgProcessingTime={dashboard.stats.avg_processing_time} - /> - } + <div className="col-lg-12"> + <Statistics + interval={stats.interval} + dnsQueries={stats.dnsQueries} + blockedFiltering={stats.blockedFiltering} + replacedSafebrowsing={stats.replacedSafebrowsing} + replacedParental={stats.replacedParental} + numDnsQueries={stats.numDnsQueries} + numBlockedFiltering={stats.numBlockedFiltering} + numReplacedSafebrowsing={stats.numReplacedSafebrowsing} + numReplacedParental={stats.numReplacedParental} + refreshButton={refreshButton} + /> + </div> + <div className="col-lg-6"> + <Counters + subtitle={subtitle} + interval={stats.interval} + dnsQueries={stats.numDnsQueries} + blockedFiltering={stats.numBlockedFiltering} + replacedSafebrowsing={stats.numReplacedSafebrowsing} + replacedParental={stats.numReplacedParental} + replacedSafesearch={stats.numReplacedSafesearch} + avgProcessingTime={stats.avgProcessingTime} + refreshButton={refreshButton} + /> + </div> + <div className="col-lg-6"> + <Clients + subtitle={subtitle} + dnsQueries={stats.numDnsQueries} + topClients={stats.topClients} + clients={dashboard.clients} + autoClients={dashboard.autoClients} + refreshButton={refreshButton} + /> + </div> + <div className="col-lg-6"> + <QueriedDomains + subtitle={subtitle} + dnsQueries={stats.numDnsQueries} + topQueriedDomains={stats.topQueriedDomains} + refreshButton={refreshButton} + /> + </div> + <div className="col-lg-6"> + <BlockedDomains + subtitle={subtitle} + topBlockedDomains={stats.topBlockedDomains} + blockedFiltering={stats.numBlockedFiltering} + replacedSafebrowsing={stats.numReplacedSafebrowsing} + replacedParental={stats.numReplacedParental} + refreshButton={refreshButton} + /> </div> - {dashboard.topStats && - <Fragment> - <div className="col-lg-6"> - <Clients - dnsQueries={dashboard.stats.dns_queries} - refreshButton={refreshButton} - topClients={dashboard.topStats.top_clients} - clients={dashboard.clients} - autoClients={dashboard.autoClients} - /> - </div> - <div className="col-lg-6"> - <QueriedDomains - dnsQueries={dashboard.stats.dns_queries} - refreshButton={refreshButton} - topQueriedDomains={dashboard.topStats.top_queried_domains} - /> - </div> - <div className="col-lg-6"> - <BlockedDomains - refreshButton={refreshButton} - topBlockedDomains={dashboard.topStats.top_blocked_domains} - blockedFiltering={dashboard.stats.blocked_filtering} - replacedSafebrowsing={dashboard.stats.replaced_safebrowsing} - replacedParental={dashboard.stats.replaced_parental} - /> - </div> - </Fragment> - } </div> - } + )} </Fragment> ); } } Dashboard.propTypes = { - getStats: PropTypes.func, - getStatsHistory: PropTypes.func, - getTopStats: PropTypes.func, - dashboard: PropTypes.object, - isCoreRunning: PropTypes.bool, - getFiltering: PropTypes.func, - toggleProtection: PropTypes.func, - getClients: PropTypes.func, - processingProtection: PropTypes.bool, - t: PropTypes.func, + dashboard: PropTypes.object.isRequired, + stats: PropTypes.object.isRequired, + getStats: PropTypes.func.isRequired, + getStatsConfig: PropTypes.func.isRequired, + toggleProtection: PropTypes.func.isRequired, + getClients: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, }; export default withNamespaces()(Dashboard); diff --git a/client/src/components/Settings/Clients/AutoClients.js b/client/src/components/Settings/Clients/AutoClients.js index 2de074ea..3c40b8b4 100644 --- a/client/src/components/Settings/Clients/AutoClients.js +++ b/client/src/components/Settings/Clients/AutoClients.js @@ -27,8 +27,9 @@ class AutoClients extends Component { }; getStats = (ip, stats) => { - if (stats && stats.top_clients) { - return stats.top_clients[ip]; + if (stats) { + const statsForCurrentIP = stats.find(item => item.name === ip); + return statsForCurrentIP && statsForCurrentIP.count; } return ''; @@ -59,11 +60,11 @@ class AutoClients extends Component { Cell: this.cellWrap, }, { - Header: this.props.t('table_statistics'), + Header: this.props.t('requests_count'), accessor: 'statistics', Cell: (row) => { const clientIP = row.original.ip; - const clientStats = clientIP && this.getStats(clientIP, this.props.topStats); + const clientStats = clientIP && this.getStats(clientIP, this.props.topClients); if (clientStats) { return ( @@ -112,7 +113,7 @@ class AutoClients extends Component { AutoClients.propTypes = { t: PropTypes.func.isRequired, autoClients: PropTypes.array.isRequired, - topStats: PropTypes.object.isRequired, + topClients: PropTypes.array.isRequired, }; export default withNamespaces()(AutoClients); diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js index 1e98da08..27e4c738 100644 --- a/client/src/components/Settings/Clients/ClientsTable.js +++ b/client/src/components/Settings/Clients/ClientsTable.js @@ -63,8 +63,9 @@ class ClientsTable extends Component { }; getStats = (ip, stats) => { - if (stats && stats.top_clients) { - return stats.top_clients[ip]; + if (stats) { + const statsForCurrentIP = stats.find(item => item.name === ip); + return statsForCurrentIP && statsForCurrentIP.count; } return ''; @@ -149,11 +150,11 @@ class ClientsTable extends Component { }, }, { - Header: this.props.t('table_statistics'), + Header: this.props.t('requests_count'), accessor: 'statistics', Cell: (row) => { const clientIP = row.original.ip; - const clientStats = clientIP && this.getStats(clientIP, this.props.topStats); + const clientStats = clientIP && this.getStats(clientIP, this.props.topClients); if (clientStats) { return ( @@ -276,7 +277,7 @@ class ClientsTable extends Component { ClientsTable.propTypes = { t: PropTypes.func.isRequired, clients: PropTypes.array.isRequired, - topStats: PropTypes.object.isRequired, + topClients: PropTypes.array.isRequired, toggleClientModal: PropTypes.func.isRequired, deleteClient: PropTypes.func.isRequired, addClient: PropTypes.func.isRequired, diff --git a/client/src/components/Settings/Clients/index.js b/client/src/components/Settings/Clients/index.js index dc82bd0f..5b656d88 100644 --- a/client/src/components/Settings/Clients/index.js +++ b/client/src/components/Settings/Clients/index.js @@ -10,13 +10,14 @@ import Loading from '../../ui/Loading'; class Clients extends Component { componentDidMount() { this.props.getClients(); - this.props.getTopStats(); + this.props.getStats(); } render() { const { t, dashboard, + stats, clients, addClient, updateClient, @@ -27,12 +28,12 @@ class Clients extends Component { return ( <Fragment> <PageTitle title={t('client_settings')} /> - {(dashboard.processingTopStats || dashboard.processingClients) && <Loading />} - {!dashboard.processingTopStats && !dashboard.processingClients && ( + {(stats.processingStats || dashboard.processingClients) && <Loading />} + {!stats.processingStats && !dashboard.processingClients && ( <Fragment> <ClientsTable clients={dashboard.clients} - topStats={dashboard.topStats} + topClients={stats.topClients} isModalOpen={clients.isModalOpen} modalClientName={clients.modalClientName} modalType={clients.modalType} @@ -46,7 +47,7 @@ class Clients extends Component { /> <AutoClients autoClients={dashboard.autoClients} - topStats={dashboard.topStats} + topClients={stats.topClients} /> </Fragment> )} @@ -58,14 +59,14 @@ class Clients extends Component { Clients.propTypes = { t: PropTypes.func.isRequired, dashboard: PropTypes.object.isRequired, + stats: PropTypes.object.isRequired, clients: PropTypes.object.isRequired, toggleClientModal: PropTypes.func.isRequired, deleteClient: PropTypes.func.isRequired, addClient: PropTypes.func.isRequired, updateClient: PropTypes.func.isRequired, getClients: PropTypes.func.isRequired, - getTopStats: PropTypes.func.isRequired, - topStats: PropTypes.object, + getStats: PropTypes.func.isRequired, }; export default withNamespaces()(Clients); diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 7d703cb8..c928d393 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -88,7 +88,7 @@ class Settings extends Component { <div className="col-md-12"> <StatsConfig interval={stats.interval} - processing={stats.setConfigProcessing} + processing={stats.processingSetConfig} setStatsConfig={setStatsConfig} /> </div> diff --git a/client/src/components/ui/Line.js b/client/src/components/ui/Line.js index 5e94d35b..b74490e3 100644 --- a/client/src/components/ui/Line.js +++ b/client/src/components/ui/Line.js @@ -4,33 +4,27 @@ import { ResponsiveLine } from '@nivo/line'; import './Line.css'; -const Line = props => ( - props.data && +const Line = ({ data, color }) => ( + data && <ResponsiveLine - data={props.data} + data={data} margin={{ top: 15, right: 0, bottom: 1, - left: 0, + left: 20, }} minY="auto" stacked={false} curve='linear' - axisBottom={{ - tickSize: 0, - tickPadding: 10, - }} - axisLeft={{ - tickSize: 0, - tickPadding: 10, - }} + axisBottom={null} + axisLeft={null} enableGridX={false} enableGridY={false} enableDots={false} enableArea={true} animate={false} - colorBy={() => (props.color)} + colorBy={() => (color)} tooltip={slice => ( <div> {slice.data.map(d => ( diff --git a/client/src/containers/Clients.js b/client/src/containers/Clients.js index 20f756d1..6651ceea 100644 --- a/client/src/containers/Clients.js +++ b/client/src/containers/Clients.js @@ -1,20 +1,22 @@ import { connect } from 'react-redux'; -import { getClients, getTopStats } from '../actions'; +import { getClients } from '../actions'; +import { getStats } from '../actions/stats'; import { addClient, updateClient, deleteClient, toggleClientModal } from '../actions/clients'; import Clients from '../components/Settings/Clients'; const mapStateToProps = (state) => { - const { dashboard, clients } = state; + const { dashboard, clients, stats } = state; const props = { dashboard, clients, + stats, }; return props; }; const mapDispatchToProps = { getClients, - getTopStats, + getStats, addClient, updateClient, deleteClient, diff --git a/client/src/containers/Dashboard.js b/client/src/containers/Dashboard.js index d5874768..8d40df18 100644 --- a/client/src/containers/Dashboard.js +++ b/client/src/containers/Dashboard.js @@ -1,14 +1,23 @@ import { connect } from 'react-redux'; -import * as actionCreators from '../actions'; +import { toggleProtection, getClients } from '../actions'; +import { getStats, getStatsConfig, setStatsConfig } from '../actions/stats'; import Dashboard from '../components/Dashboard'; const mapStateToProps = (state) => { - const { dashboard } = state; - const props = { dashboard }; + const { dashboard, stats } = state; + const props = { dashboard, stats }; return props; }; +const mapDispatchToProps = { + toggleProtection, + getClients, + getStats, + getStatsConfig, + setStatsConfig, +}; + export default connect( mapStateToProps, - actionCreators, + mapDispatchToProps, )(Dashboard); diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index def170f0..e84d2b27 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -2,11 +2,12 @@ import dateParse from 'date-fns/parse'; import dateFormat from 'date-fns/format'; import subHours from 'date-fns/sub_hours'; import addHours from 'date-fns/add_hours'; +import addDays from 'date-fns/add_days'; +import subDays from 'date-fns/sub_days'; import round from 'lodash/round'; import axios from 'axios'; import { - STATS_NAMES, STANDARD_DNS_PORT, STANDARD_WEB_PORT, STANDARD_HTTPS_PORT, @@ -49,29 +50,28 @@ export const normalizeLogs = logs => logs.map((log) => { }; }); -export const normalizeHistory = history => Object.keys(history).map((key) => { - let id = STATS_NAMES[key]; - if (!id) { - id = key.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase()); +export const normalizeHistory = (history, interval) => { + if (interval === 1 || interval === 7) { + const hoursAgo = subHours(Date.now(), 24 * interval); + return history.map((item, index) => ({ + x: dateFormat(addHours(hoursAgo, index), 'D MMM HH:00'), + y: round(item, 2), + })); } - const dayAgo = subHours(Date.now(), 24); + const daysAgo = subDays(Date.now(), interval - 1); + return history.map((item, index) => ({ + x: dateFormat(addDays(daysAgo, index), 'D MMM YYYY'), + y: round(item, 2), + })); +}; - const data = history[key].map((item, index) => { - const formatHour = dateFormat(addHours(dayAgo, index), 'ddd HH:00'); - const roundValue = round(item, 2); - - return { - x: formatHour, - y: roundValue, - }; - }); - - return { - id, - data, - }; -}); +export const normalizeTopStats = stats => ( + stats.map(item => ({ + name: Object.keys(item)[0], + count: Object.values(item)[0], + })) +); export const normalizeFilteringStatus = (filteringStatus) => { const { enabled, filters, user_rules: userRules } = filteringStatus; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index ef197cb8..2af571b2 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -94,27 +94,6 @@ const dashboard = handleActions({ return newState; }, - [actions.getStatsRequest]: state => ({ ...state, processingStats: true }), - [actions.getStatsFailure]: state => ({ ...state, processingStats: false }), - [actions.getStatsSuccess]: (state, { payload }) => { - const newState = { ...state, stats: payload, processingStats: false }; - return newState; - }, - - [actions.getTopStatsRequest]: state => ({ ...state, processingTopStats: true }), - [actions.getTopStatsFailure]: state => ({ ...state, processingTopStats: false }), - [actions.getTopStatsSuccess]: (state, { payload }) => { - const newState = { ...state, topStats: payload, processingTopStats: false }; - return newState; - }, - - [actions.getStatsHistoryRequest]: state => ({ ...state, processingStatsHistory: true }), - [actions.getStatsHistoryFailure]: state => ({ ...state, processingStatsHistory: false }), - [actions.getStatsHistorySuccess]: (state, { payload }) => { - const newState = { ...state, statsHistory: payload, processingStatsHistory: false }; - return newState; - }, - [actions.toggleLogStatusRequest]: state => ({ ...state, logStatusProcessing: true }), [actions.toggleLogStatusFailure]: state => ({ ...state, logStatusProcessing: false }), [actions.toggleLogStatusSuccess]: (state) => { @@ -200,8 +179,6 @@ const dashboard = handleActions({ }, { processing: true, isCoreRunning: false, - processingTopStats: true, - processingStats: true, logStatusProcessing: false, processingVersion: true, processingFiltering: true, @@ -218,15 +195,6 @@ const dashboard = handleActions({ dnsVersion: '', clients: [], autoClients: [], - topStats: [], - stats: { - dns_queries: '', - blocked_filtering: '', - replaced_safebrowsing: '', - replaced_parental: '', - replaced_safesearch: '', - avg_processing_time: '', - }, }); const queryLogs = handleActions({ diff --git a/client/src/reducers/stats.js b/client/src/reducers/stats.js index 48e07bb5..3bae662d 100644 --- a/client/src/reducers/stats.js +++ b/client/src/reducers/stats.js @@ -2,26 +2,83 @@ import { handleActions } from 'redux-actions'; import * as actions from '../actions/stats'; -const stats = handleActions({ - [actions.getStatsConfigRequest]: state => ({ ...state, getConfigProcessing: true }), - [actions.getStatsConfigFailure]: state => ({ ...state, getConfigProcessing: false }), - [actions.getStatsConfigSuccess]: (state, { payload }) => ({ - ...state, - interval: payload.interval, - getConfigProcessing: false, - }), +const stats = handleActions( + { + [actions.getStatsConfigRequest]: state => ({ ...state, processingGetConfig: true }), + [actions.getStatsConfigFailure]: state => ({ ...state, processingGetConfig: false }), + [actions.getStatsConfigSuccess]: (state, { payload }) => ({ + ...state, + interval: payload.interval, + processingGetConfig: false, + }), - [actions.setStatsConfigRequest]: state => ({ ...state, setConfigProcessing: true }), - [actions.setStatsConfigFailure]: state => ({ ...state, setConfigProcessing: false }), - [actions.setStatsConfigSuccess]: (state, { payload }) => ({ - ...state, - interval: payload.interval, - setConfigProcessing: false, - }), -}, { - getConfigProcessing: false, - setConfigProcessing: false, - interval: 1, -}); + [actions.setStatsConfigRequest]: state => ({ ...state, processingSetConfig: true }), + [actions.setStatsConfigFailure]: state => ({ ...state, processingSetConfig: false }), + [actions.setStatsConfigSuccess]: (state, { payload }) => ({ + ...state, + interval: payload.interval, + processingSetConfig: false, + }), + + [actions.getStatsRequest]: state => ({ ...state, processingStats: true }), + [actions.getStatsFailure]: state => ({ ...state, processingStats: false }), + [actions.getStatsSuccess]: (state, { payload }) => { + const { + dns_queries: dnsQueries, + blocked_filtering: blockedFiltering, + replaced_parental: replacedParental, + replaced_safebrowsing: replacedSafebrowsing, + top_blocked_domains: topBlockedDomains, + top_clients: topClients, + top_queried_domains: topQueriedDomains, + num_blocked_filtering: numBlockedFiltering, + num_dns_queries: numDnsQueries, + num_replaced_parental: numReplacedParental, + num_replaced_safebrowsing: numReplacedSafebrowsing, + num_replaced_safesearch: numReplacedSafesearch, + avg_processing_time: avgProcessingTime, + } = payload; + + const newState = { + ...state, + processingStats: false, + dnsQueries, + blockedFiltering, + replacedParental, + replacedSafebrowsing, + topBlockedDomains, + topClients, + topQueriedDomains, + numBlockedFiltering, + numDnsQueries, + numReplacedParental, + numReplacedSafebrowsing, + numReplacedSafesearch, + avgProcessingTime, + }; + + return newState; + }, + }, + { + processingGetConfig: false, + processingSetConfig: false, + processingStats: true, + interval: 1, + dnsQueries: [], + blockedFiltering: [], + replacedParental: [], + replacedSafebrowsing: [], + topBlockedDomains: [], + topClients: [], + topQueriedDomains: [], + numBlockedFiltering: 0, + numDnsQueries: 0, + numReplacedParental: 0, + numReplacedSafebrowsing: 0, + numReplacedSafesearch: 0, + avgProcessingTime: 0, + }, +); export default stats;