diff --git a/client/.eslintrc b/client/.eslintrc index cb0bff72..98799db6 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -13,6 +13,13 @@ "commonjs": true }, + "settings": { + "react": { + "pragma": "React", + "version": "16.4" + } + }, + "rules": { "indent": ["error", 4, { "SwitchCase": 1, @@ -43,6 +50,6 @@ }], "no-console": ["warn", { "allow": ["warn", "error"] }], "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], - "import/prefer-default-export": "off", + "import/prefer-default-export": "off" } } diff --git a/client/package-lock.json b/client/package-lock.json index f1ffff54..1e9b0f5d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -181,18 +181,32 @@ "glob-to-regexp": "^0.3.0" } }, + "@nivo/axes": { + "version": "0.49.1", + "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.49.1.tgz", + "integrity": "sha512-2ZqpKtnZ9HE30H+r565VCrypKRQzAoMbAg1hsht88dlNQRtghBSxbAS0Y4IUW/wgN/AzvOIBJHvxH7bgaB8Oow==", + "requires": { + "@nivo/core": "0.49.0", + "d3-format": "^1.3.2", + "d3-time-format": "^2.1.3", + "lodash": "^4.17.4", + "react-motion": "^0.5.2", + "recompose": "^0.26.0" + } + }, "@nivo/core": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.42.1.tgz", - "integrity": "sha512-T3DgbV9x6snbHxNQ2vWZYJRCnI6iUqh9A6Kn1Fsy1L7Sn97fsf89e1qMp0CpILhyJu7Fj+VXRYtJwby0wH6GAA==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.49.0.tgz", + "integrity": "sha512-TCPMUO2aJ7fI+ZB6t3d3EBQtNxJnTzaxLJsrVyn/3AQIjUwccAeo2aIy81wLBGWGtlGNUDNdAbnFzXiJosH0yg==", "requires": { "d3-color": "^1.0.3", - "d3-format": "^1.2.0", - "d3-hierarchy": "^1.1.5", - "d3-interpolate": "^1.1.5", - "d3-scale": "^1.0.6", - "d3-scale-chromatic": "^1.1.1", - "d3-shape": "^1.2.0", + "d3-format": "^1.3.2", + "d3-hierarchy": "^1.1.8", + "d3-interpolate": "^1.3.2", + "d3-scale": "^2.1.2", + "d3-scale-chromatic": "^1.3.3", + "d3-shape": "^1.2.2", + "d3-time-format": "^2.1.3", "lodash": "^4.17.4", "react-measure": "^2.0.2", "react-motion": "^0.5.2", @@ -200,29 +214,41 @@ } }, "@nivo/legends": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.42.0.tgz", - "integrity": "sha512-t82aKNaFtbY0mlE12caiSkXml73APMibH+gKsXECwhSutfGfgQzUbqBjPsNKJcMiWfG46noJ1MrFhDB3a6204g==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.49.0.tgz", + "integrity": "sha512-8KbUFYozqwD+/rj4in0mnF9b9CuyNFjVgXqm2KW3ODVlWIgYgjTVlEhlg9VZIArFPlIyyAjEYC88YSRcALHugg==", "requires": { "lodash": "^4.17.4", "recompose": "^0.26.0" } }, "@nivo/line": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.42.1.tgz", - "integrity": "sha512-X/nvNvwMqz10hACBL/owCONDeG78occ6Er0ay6/1n2h+Dm6zn2p6hiFyvu7QtsdwGeHOC5sePcz9O44bycbtoQ==", + "version": "0.49.1", + "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.49.1.tgz", + "integrity": "sha512-wKkOmpnwK2psmZbJReDq+Eh/WV9r1JA8V4Vl4eIRuf971CW0KUT9nCAoc/FcKio0qsiq5wyFt3J5LuAhfzlV/w==", "requires": { - "@nivo/core": "0.42.1", - "@nivo/legends": "0.42.0", - "d3-format": "^1.2.0", - "d3-scale": "^1.0.6", - "d3-shape": "^1.2.0", + "@nivo/axes": "0.49.1", + "@nivo/core": "0.49.0", + "@nivo/legends": "0.49.0", + "@nivo/scales": "0.49.0", + "d3-format": "^1.3.2", + "d3-scale": "^2.1.2", + "d3-shape": "^1.2.2", "lodash": "^4.17.4", "react-motion": "^0.5.2", "recompose": "^0.26.0" } }, + "@nivo/scales": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.49.0.tgz", + "integrity": "sha512-+5Leu4zX6mDSAunf4ZJHeqVlT+ZsqiKXLB6hT/u7r3GjxZP9A+n3rHePhIzikBiBRMlLjyiBlylLzhKBAYbGWQ==", + "requires": { + "d3-scale": "^2.1.2", + "d3-time-format": "^2.1.3", + "lodash": "^4.17.4" + } + }, "@nodelib/fs.stat": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", @@ -2677,6 +2703,15 @@ "source-map": "~0.6.0" } }, + "clean-webpack-plugin": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz", + "integrity": "sha512-M1Li5yLHECcN2MahoreuODul5LkjohJGFxLPTjl3j1ttKrF5rgjZET1SJduuqxLAuT1gAPOdkhg03qcaaU1KeA==", + "dev": true, + "requires": { + "rimraf": "^2.6.1" + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -3502,13 +3537,12 @@ "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==" }, "d3-scale": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", - "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz", + "integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==", "requires": { "d3-array": "^1.2.0", "d3-collection": "1", - "d3-color": "1", "d3-format": "1", "d3-interpolate": "1", "d3-time": "1", @@ -12822,9 +12856,9 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-measure": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.1.0.tgz", - "integrity": "sha512-nHdoq1eTbGVg/jWWAEtxXSHH51j09d1nPabj6PwS+pNSCYYf1H5XLMfcfU2ZTnkDU/Xg0fGY79Xud2Gsp3VsmQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.1.2.tgz", + "integrity": "sha512-2xgrlj77Pv8MIYhm+S5s4Q+QnsYFT3CXzoUkx2mWZBIWzQnT7ubMtmsVtCoNdYV5PGKjTlwo9iGAtS3SrTG/Gg==", "requires": { "get-node-dimensions": "^1.2.0", "prop-types": "^15.5.10", diff --git a/client/package.json b/client/package.json index 2321cbd0..38e18a30 100644 --- a/client/package.json +++ b/client/package.json @@ -9,7 +9,7 @@ "lint": "eslint frontend/" }, "dependencies": { - "@nivo/line": "^0.42.1", + "@nivo/line": "^0.49.1", "axios": "^0.18.0", "classnames": "^2.2.6", "date-fns": "^1.29.0", @@ -44,6 +44,7 @@ "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "babel-runtime": "6.26.0", + "clean-webpack-plugin": "^0.1.19", "compression-webpack-plugin": "^1.1.11", "css-loader": "^0.28.11", "eslint": "^4.19.1", diff --git a/client/src/components/Dashboard/BlockedDomains.js b/client/src/components/Dashboard/BlockedDomains.js index 382a23b8..684bb611 100644 --- a/client/src/components/Dashboard/BlockedDomains.js +++ b/client/src/components/Dashboard/BlockedDomains.js @@ -1,34 +1,61 @@ -import React from 'react'; +import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; import Card from '../ui/Card'; +import Cell from '../ui/Cell'; -const Clients = props => ( - - ( - { ip: prop, domain: value } - ))} - columns={[{ - Header: 'IP', - accessor: 'ip', - }, { - Header: 'Domain name', - accessor: 'domain', - }]} - showPagination={false} - noDataText="No domains found" - minRows={6} - className="-striped -highlight card-table-overflow" - /> - -); +import { getPercent } from '../../helpers/helpers'; +import { STATUS_COLORS } from '../../helpers/constants'; -Clients.propTypes = { +class BlockedDomains extends Component { + columns = [{ + Header: 'IP', + accessor: 'ip', + Cell: ({ value }) => (
{value}
), + }, { + Header: 'Requests count', + accessor: 'domain', + Cell: ({ value }) => { + const { + blockedFiltering, + replacedSafebrowsing, + replacedParental, + } = this.props; + const blocked = blockedFiltering + replacedSafebrowsing + replacedParental; + const percent = getPercent(blocked, value); + + return ( + + ); + }, + }]; + + render() { + return ( + + ( + { ip: prop, domain: value } + ))} + columns={this.columns} + showPagination={false} + noDataText="No domains found" + minRows={6} + className="-striped -highlight card-table-overflow" + /> + + ); + } +} + +BlockedDomains.propTypes = { topBlockedDomains: PropTypes.object.isRequired, - refreshButton: PropTypes.node, + blockedFiltering: PropTypes.number.isRequired, + replacedSafebrowsing: PropTypes.number.isRequired, + replacedParental: PropTypes.number.isRequired, + refreshButton: PropTypes.node.isRequired, }; -export default Clients; +export default BlockedDomains; diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index a3f4ae14..633ef08b 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -1,34 +1,63 @@ -import React from 'react'; +import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; import Card from '../ui/Card'; +import Cell from '../ui/Cell'; -const Clients = props => ( - - ( - { ip: prop, count: value } - ))} - columns={[{ - Header: 'IP', - accessor: 'ip', - }, { - Header: 'Requests count', - accessor: 'count', - }]} - showPagination={false} - noDataText="No clients found" - minRows={6} - className="-striped -highlight card-table-overflow" - /> - -); +import { getPercent } from '../../helpers/helpers'; +import { STATUS_COLORS } from '../../helpers/constants'; + +class Clients extends Component { + getPercentColor = (percent) => { + if (percent > 50) { + return STATUS_COLORS.green; + } else if (percent > 10) { + return STATUS_COLORS.yellow; + } + return STATUS_COLORS.red; + } + + columns = [{ + Header: 'IP', + accessor: 'ip', + Cell: ({ value }) => (
{value}
), + }, { + Header: 'Requests count', + accessor: 'count', + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); + + return ( + + ); + }, + }]; + + render() { + return ( + + ( + { ip: prop, count: value } + ))} + columns={this.columns} + showPagination={false} + noDataText="No clients found" + minRows={6} + className="-striped -highlight card-table-overflow" + /> + + ); + } +} Clients.propTypes = { topClients: PropTypes.object.isRequired, - refreshButton: PropTypes.node, + dnsQueries: PropTypes.number.isRequired, + refreshButton: PropTypes.node.isRequired, }; export default Clients; diff --git a/client/src/components/Dashboard/Counters.js b/client/src/components/Dashboard/Counters.js index 1b7ec92f..a6260e10 100644 --- a/client/src/components/Dashboard/Counters.js +++ b/client/src/components/Dashboard/Counters.js @@ -88,7 +88,7 @@ Counters.propTypes = { replacedParental: PropTypes.number.isRequired, replacedSafesearch: PropTypes.number.isRequired, avgProcessingTime: PropTypes.number.isRequired, - refreshButton: PropTypes.node, + refreshButton: PropTypes.node.isRequired, }; export default Counters; diff --git a/client/src/components/Dashboard/QueriedDomains.js b/client/src/components/Dashboard/QueriedDomains.js index fd756e0e..c1b630a4 100644 --- a/client/src/components/Dashboard/QueriedDomains.js +++ b/client/src/components/Dashboard/QueriedDomains.js @@ -1,34 +1,63 @@ -import React from 'react'; +import React, { Component } from 'react'; import ReactTable from 'react-table'; import PropTypes from 'prop-types'; import map from 'lodash/map'; import Card from '../ui/Card'; +import Cell from '../ui/Cell'; -const QueriedDomains = props => ( - - ( - { ip: prop, count: value } - ))} - columns={[{ - Header: 'IP', - accessor: 'ip', - }, { - Header: 'Requests count', - accessor: 'count', - }]} - showPagination={false} - noDataText="No domains found" - minRows={6} - className="-striped -highlight card-table-overflow" - /> - -); +import { getPercent } from '../../helpers/helpers'; +import { STATUS_COLORS } from '../../helpers/constants'; + +class QueriedDomains extends Component { + getPercentColor = (percent) => { + if (percent > 10) { + return STATUS_COLORS.red; + } else if (percent > 5) { + return STATUS_COLORS.yellow; + } + return STATUS_COLORS.green; + } + + columns = [{ + Header: 'IP', + accessor: 'ip', + Cell: ({ value }) => (
{value}
), + }, { + Header: 'Requests count', + accessor: 'count', + Cell: ({ value }) => { + const percent = getPercent(this.props.dnsQueries, value); + const percentColor = this.getPercentColor(percent); + + return ( + + ); + }, + }]; + + render() { + return ( + + ( + { ip: prop, count: value } + ))} + columns={this.columns} + showPagination={false} + noDataText="No domains found" + minRows={6} + className="-striped -highlight card-table-overflow" + /> + + ); + } +} QueriedDomains.propTypes = { topQueriedDomains: PropTypes.object.isRequired, - refreshButton: PropTypes.node, + dnsQueries: PropTypes.number.isRequired, + refreshButton: PropTypes.node.isRequired, }; export default QueriedDomains; diff --git a/client/src/components/Dashboard/Statistics.js b/client/src/components/Dashboard/Statistics.js index e36c5c5a..434c21fd 100644 --- a/client/src/components/Dashboard/Statistics.js +++ b/client/src/components/Dashboard/Statistics.js @@ -1,59 +1,109 @@ -import React from 'react'; -import { ResponsiveLine } from '@nivo/line'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Card from '../ui/Card'; +import Line from '../ui/Line'; -const Statistics = props => ( - - {props.history ? - - : -

Empty data

- } -
-); +import { getPercent } from '../../helpers/helpers'; +import { STATUS_COLORS } from '../../helpers/constants'; + +class Statistics extends Component { + render() { + const { + dnsQueries, + blockedFiltering, + replacedSafebrowsing, + replacedParental, + } = 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} +
+
+ DNS Queries +
+
+
+ +
+
+
+
+ +
+
+ {blockedFiltering} +
+
+ {getPercent(dnsQueries, blockedFiltering)} +
+
+ Blocked by Filters +
+
+
+ +
+
+
+
+ +
+
+ {replacedSafebrowsing} +
+
+ {getPercent(dnsQueries, replacedSafebrowsing)} +
+
+ Blocked malware/phishing +
+
+
+ +
+
+
+
+ +
+
+ {replacedParental} +
+
+ {getPercent(dnsQueries, replacedParental)} +
+
+ Blocked adult websites +
+
+
+ +
+
+
+
+ ); + } +} Statistics.propTypes = { history: PropTypes.array.isRequired, - refreshButton: PropTypes.node, + dnsQueries: PropTypes.number.isRequired, + blockedFiltering: PropTypes.number.isRequired, + replacedSafebrowsing: PropTypes.number.isRequired, + replacedParental: PropTypes.number.isRequired, + refreshButton: PropTypes.node.isRequired, }; export default Statistics; diff --git a/client/src/components/Dashboard/index.js b/client/src/components/Dashboard/index.js index 28d52f11..99fe8e5e 100644 --- a/client/src/components/Dashboard/index.js +++ b/client/src/components/Dashboard/index.js @@ -61,6 +61,10 @@ class Dashboard extends Component { } @@ -81,12 +85,14 @@ class Dashboard extends Component {
@@ -95,6 +101,9 @@ class Dashboard extends Component {
diff --git a/client/src/components/Header/Header.css b/client/src/components/Header/Header.css index d3a86a61..e1148240 100644 --- a/client/src/components/Header/Header.css +++ b/client/src/components/Header/Header.css @@ -77,7 +77,7 @@ } .header-brand-img { - height: 26px; + height: 32px; } @media screen and (min-width: 992px) { diff --git a/client/src/components/Header/logo.svg b/client/src/components/Header/logo.svg index 2b8c5660..28427d37 100644 --- a/client/src/components/Header/logo.svg +++ b/client/src/components/Header/logo.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/client/src/components/ui/Card.css b/client/src/components/ui/Card.css index 0aaeea01..b02c1409 100644 --- a/client/src/components/ui/Card.css +++ b/client/src/components/ui/Card.css @@ -26,7 +26,6 @@ display: flex; align-items: center; justify-content: center; - height: 400px; } .card-body--status { @@ -48,3 +47,40 @@ .card-refresh:focus:active { background-image: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg=="); } + +.card-title-stats { + color: #9aa0ac; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.card-body-stats { + position: relative; + flex: 1 1 auto; + margin: 0; + padding: 1rem 1.5rem; +} + +.card-value-stats { + display: block; + font-size: 2.1rem; + line-height: 2.7rem; + height: 2.7rem; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.card-value-percent { + position: absolute; + top: 15px; + right: 15px; + font-size: 0.9rem; + line-height: 1; + height: auto; +} + +.card-value-percent:after { + content: "%"; +} diff --git a/client/src/components/ui/Cell.js b/client/src/components/ui/Cell.js new file mode 100644 index 00000000..243b48b2 --- /dev/null +++ b/client/src/components/ui/Cell.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Cell = props => ( +
+
+ {props.value} + + {props.percent}% + +
+
+
+
+
+); + +Cell.propTypes = { + value: PropTypes.number.isRequired, + percent: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, +}; + +export default Cell; diff --git a/client/src/components/ui/Footer.js b/client/src/components/ui/Footer.js index 4221bff0..6afa322d 100644 --- a/client/src/components/ui/Footer.js +++ b/client/src/components/ui/Footer.js @@ -12,17 +12,23 @@ class Footer extends Component {
- +
+
+ +
+ +
- © AdGuard {this.getYear()} + Copyright © {this.getYear()} AdGuard.
diff --git a/client/src/components/ui/Line.css b/client/src/components/ui/Line.css new file mode 100644 index 00000000..4e77647e --- /dev/null +++ b/client/src/components/ui/Line.css @@ -0,0 +1,9 @@ +.line__tooltip { + padding: 2px 10px 7px; + line-height: 1.1; + color: #fff; +} + +.line__tooltip-text { + font-size: 0.7rem; +} diff --git a/client/src/components/ui/Line.js b/client/src/components/ui/Line.js new file mode 100644 index 00000000..9fb805c0 --- /dev/null +++ b/client/src/components/ui/Line.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ResponsiveLine } from '@nivo/line'; + +import './Line.css'; + +const Line = props => ( + props.data && + (props.color)} + tooltip={slice => ( +
+ {slice.data.map(d => ( +
+ + {d.data.y} + +
+ ))} +
+ )} + theme={{ + tooltip: { + container: { + padding: '0', + background: '#333', + borderRadius: '4px', + }, + }, + }} + /> +); + +Line.propTypes = { + data: PropTypes.array.isRequired, + color: PropTypes.string.isRequired, +}; + +export default Line; diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 3ad4f6d5..1af6c276 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -1 +1,17 @@ export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/; + +export const STATS_NAMES = { + avg_processing_time: 'Average processing time', + blocked_filtering: 'Blocked by filters', + dns_queries: 'DNS queries', + replaced_parental: 'Blocked adult websites', + replaced_safebrowsing: 'Blocked malware/phishing', + replaced_safesearch: 'Enforced safe search', +}; + +export const STATUS_COLORS = { + blue: '#467fcf', + red: '#cd201f', + green: '#5eba00', + yellow: '#f1c40f', +}; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 9aea6a11..d21ba510 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -4,6 +4,8 @@ import subHours from 'date-fns/sub_hours'; import addHours from 'date-fns/add_hours'; import round from 'lodash/round'; +import { STATS_NAMES } from './constants'; + const formatTime = (time) => { const parsedTime = dateParse(time); return dateFormat(parsedTime, 'HH:mm:ss'); @@ -34,15 +36,6 @@ export const normalizeLogs = logs => logs.map((log) => { }; }); -const STATS_NAMES = { - avg_processing_time: 'Average processing time', - blocked_filtering: 'Blocked by filters', - dns_queries: 'DNS queries', - replaced_parental: 'Blocked adult websites', - replaced_safebrowsing: 'Blocked malware/phishing', - replaced_safesearch: 'Enforced safe search', -}; - export const normalizeHistory = history => Object.keys(history).map((key) => { let id = STATS_NAMES[key]; if (!id) { @@ -81,3 +74,10 @@ export const normalizeFilteringStatus = (filteringStatus) => { const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : ''; return { enabled, userRules: newUserRules, filters: newFilters }; }; + +export const getPercent = (amount, number) => { + if (amount > 0 && number > 0) { + return round(100 / (amount / number), 2); + } + return 0; +}; diff --git a/client/webpack.common.js b/client/webpack.common.js index d1cfa6d0..4ca3b1e4 100644 --- a/client/webpack.common.js +++ b/client/webpack.common.js @@ -4,6 +4,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const webpack = require('webpack'); const flexBugsFixes = require('postcss-flexbugs-fixes'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); const RESOURCES_PATH = path.resolve(__dirname); const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js'); @@ -92,6 +93,11 @@ const config = { new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }), + new CleanWebpackPlugin(['*.*'], { + root: PUBLIC_PATH, + verbose: false, + dry: false, + }), new HtmlWebpackPlugin({ inject: true, cache: false,