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,