+ client: functional components for dashboard

This commit is contained in:
Ildar Kamalov 2019-08-27 16:43:58 +03:00
parent 75df5e8292
commit 6bce41bb0a
14 changed files with 310 additions and 370 deletions

View File

@ -66,19 +66,21 @@
"disabled_protection": "Disabled protection", "disabled_protection": "Disabled protection",
"refresh_statics": "Refresh statistics", "refresh_statics": "Refresh statistics",
"dns_query": "DNS Queries", "dns_query": "DNS Queries",
"blocked_by": "Blocked by Filters", "blocked_by": "<0>Blocked by Filters</0>",
"stats_malware_phishing": "Blocked malware/phishing", "stats_malware_phishing": "Blocked malware/phishing",
"stats_adult": "Blocked adult websites", "stats_adult": "Blocked adult websites",
"stats_query_domain": "Top queried domains", "stats_query_domain": "Top queried domains",
"for_last_24_hours": "for the last 24 hours", "for_last_24_hours": "for the last 24 hours",
"for_last_days": "for the last {{value}} days", "for_last_days": "for the last {{count}} day",
"for_last_days_plural": "for the last {{count}} days",
"no_domains_found": "No domains found", "no_domains_found": "No domains found",
"requests_count": "Requests count", "requests_count": "Requests count",
"top_blocked_domains": "Top blocked domains", "top_blocked_domains": "Top blocked domains",
"top_clients": "Top clients", "top_clients": "Top clients",
"no_clients_found": "No clients found", "no_clients_found": "No clients found",
"general_statistics": "General statistics", "general_statistics": "General statistics",
"number_of_dns_query_days": "A number of DNS queries processed for the last {{value}} days", "number_of_dns_query_days": "A number of DNS queries processed for the last {{count}} day",
"number_of_dns_query_days_plural": "A number of DNS queries processed for the last {{count}} days",
"number_of_dns_query_24_hours": "A number of DNS queries processed for the last 24 hours", "number_of_dns_query_24_hours": "A number of DNS queries 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": "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", "number_of_dns_query_blocked_24_hours_by_sec": "A number of DNS requests blocked by the AdGuard browsing security module",
@ -365,7 +367,8 @@
"stats_params": "Statistics configuration", "stats_params": "Statistics configuration",
"config_successfully_saved": "Configuration successfully saved", "config_successfully_saved": "Configuration successfully saved",
"interval_24_hour": "24 hours", "interval_24_hour": "24 hours",
"interval_days": "{{value}} days", "interval_days": "{{count}} day",
"interval_days_plural": "{{count}} days",
"time_period": "Time period", "time_period": "Time period",
"domain": "Domain", "domain": "Domain",
"answer": "Answer", "answer": "Answer",

View File

@ -22,7 +22,6 @@ export default class Api {
} }
// Global methods // Global methods
GLOBAL_RESTART = { path: 'restart', method: 'POST' };
GLOBAL_START = { path: 'start', method: 'POST' }; GLOBAL_START = { path: 'start', method: 'POST' };
GLOBAL_STATUS = { path: 'status', method: 'GET' }; GLOBAL_STATUS = { path: 'status', method: 'GET' };
GLOBAL_STOP = { path: 'stop', method: 'POST' }; GLOBAL_STOP = { path: 'stop', method: 'POST' };
@ -36,11 +35,6 @@ export default class Api {
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' }; GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' }; GLOBAL_UPDATE = { path: 'update', method: 'POST' };
restartGlobalFiltering() {
const { path, method } = this.GLOBAL_RESTART;
return this.makeRequest(path, method);
}
startGlobalFiltering() { startGlobalFiltering() {
const { path, method } = this.GLOBAL_START; const { path, method } = this.GLOBAL_START;
return this.makeRequest(path, method); return this.makeRequest(path, method);

View File

@ -1,76 +1,67 @@
import React, { Component } from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next'; import { withNamespaces, Trans } from 'react-i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Cell from '../ui/Cell'; import Cell from '../ui/Cell';
import Popover from '../ui/Popover'; import DomainCell from './DomainCell';
import { getTrackerData } from '../../helpers/trackers/trackers';
import { getPercent } from '../../helpers/helpers'; import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants'; import { STATUS_COLORS } from '../../helpers/constants';
class BlockedDomains extends Component { const CountCell = totalBlocked =>
columns = [ function cell(row) {
{ const { value } = row;
Header: <Trans>domain</Trans>, const percent = getPercent(totalBlocked, value);
accessor: 'domain',
Cell: (row) => {
const { value } = row;
const trackerData = getTrackerData(value);
return ( return <Cell value={value} percent={percent} color={STATUS_COLORS.red} />;
<div className="logs__row"> };
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
</div>
);
},
},
{
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} />; const BlockedDomains = ({
}, t,
}, refreshButton,
]; topBlockedDomains,
subtitle,
blockedFiltering,
replacedSafebrowsing,
replacedParental,
}) => {
const totalBlocked = blockedFiltering + replacedSafebrowsing + replacedParental;
render() { return (
const { <Card
t, refreshButton, topBlockedDomains, subtitle, title={t('top_blocked_domains')}
} = this.props; subtitle={subtitle}
bodyType="card-table"
return ( refresh={refreshButton}
<Card >
title={t('top_blocked_domains')} <ReactTable
subtitle={subtitle} data={topBlockedDomains.map(({ name: domain, count }) => ({
bodyType="card-table" domain,
refresh={refreshButton} count,
> }))}
<ReactTable columns={[
data={topBlockedDomains.map(item => ({ {
domain: item.name, Header: <Trans>domain</Trans>,
count: item.count, accessor: 'domain',
}))} Cell: DomainCell,
columns={this.columns} },
showPagination={false} {
noDataText={t('no_domains_found')} Header: <Trans>requests_count</Trans>,
minRows={6} accessor: 'count',
className="-striped -highlight card-table-overflow stats__table" maxWidth: 190,
/> Cell: CountCell(totalBlocked),
</Card> },
); ]}
} showPagination={false}
} noDataText={t('no_domains_found')}
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
</Card>
);
};
BlockedDomains.propTypes = { BlockedDomains.propTypes = {
topBlockedDomains: PropTypes.array.isRequired, topBlockedDomains: PropTypes.array.isRequired,

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next'; import { Trans, withNamespaces } from 'react-i18next';
@ -9,86 +9,84 @@ import Cell from '../ui/Cell';
import { getPercent, getClientName } from '../../helpers/helpers'; import { getPercent, getClientName } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants'; import { STATUS_COLORS } from '../../helpers/constants';
class Clients extends Component { const getClientsPercentColor = (percent) => {
getPercentColor = (percent) => { if (percent > 50) {
if (percent > 50) { return STATUS_COLORS.green;
return STATUS_COLORS.green; } else if (percent > 10) {
} else if (percent > 10) { return STATUS_COLORS.yellow;
return STATUS_COLORS.yellow; }
return STATUS_COLORS.red;
};
const ipCell = (clients, autoClients) =>
function cell(row) {
let client;
const { value } = row;
const clientName = getClientName(clients, value) || getClientName(autoClients, value);
if (clientName) {
client = (
<span>
{clientName} <small>({value})</small>
</span>
);
} else {
client = value;
} }
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;
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>
);
},
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);
return <Cell value={value} percent={percent} color={percentColor} />;
},
},
];
render() {
const {
t, refreshButton, topClients, subtitle,
} = this.props;
return ( return (
<Card <div className="logs__row logs__row--overflow">
title={t('top_clients')} <span className="logs__text" title={value}>
subtitle={subtitle} {client}
bodyType="card-table" </span>
refresh={refreshButton} </div>
>
<ReactTable
data={topClients.map(item => ({
ip: item.name,
count: item.count,
}))}
columns={this.columns}
showPagination={false}
noDataText={t('no_clients_found')}
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
); );
} };
}
const countCell = dnsQueries =>
function cell(row) {
const { value } = row;
const percent = getPercent(dnsQueries, value);
const percentColor = getClientsPercentColor(percent);
return <Cell value={value} percent={percent} color={percentColor} />;
};
const Clients = ({
t, refreshButton, topClients, subtitle, clients, autoClients, dnsQueries,
}) => (
<Card
title={t('top_clients')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable
data={topClients.map(({ name: ip, count }) => ({
ip,
count,
}))}
columns={[
{
Header: 'IP',
accessor: 'ip',
sortMethod: (a, b) =>
parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
Cell: ipCell(clients, autoClients),
},
{
Header: <Trans>requests_count</Trans>,
accessor: 'count',
Cell: countCell(dnsQueries),
},
]}
showPagination={false}
noDataText={t('no_clients_found')}
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
);
Clients.propTypes = { Clients.propTypes = {
topClients: PropTypes.array.isRequired, topClients: PropTypes.array.isRequired,

View File

@ -25,7 +25,7 @@ const Counters = (props) => {
const tooltipTitle = const tooltipTitle =
interval === 1 interval === 1
? t('number_of_dns_query_24_hours') ? t('number_of_dns_query_24_hours')
: t('number_of_dns_query_days', { value: interval }); : t('number_of_dns_query_days', { count: interval });
return ( return (
<Card <Card
@ -47,9 +47,9 @@ const Counters = (props) => {
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#filters"> <Trans components={[<a href="#filters" key="0">link</a>]}>
<Trans>blocked_by</Trans> blocked_by
</a> </Trans>
<Tooltip <Tooltip
text={t('number_of_dns_query_blocked_24_hours')} text={t('number_of_dns_query_blocked_24_hours')}
type={tooltipType} type={tooltipType}

View File

@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getTrackerData } from '../../helpers/trackers/trackers';
import Popover from '../ui/Popover';
const DomainCell = ({ value }) => {
const trackerData = getTrackerData(value);
return (
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
</div>
);
};
DomainCell.propTypes = {
value: PropTypes.string.isRequired,
};
export default DomainCell;

View File

@ -1,83 +1,67 @@
import React, { Component } from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next'; import { withNamespaces, Trans } from 'react-i18next';
import Card from '../ui/Card'; import Card from '../ui/Card';
import Cell from '../ui/Cell'; import Cell from '../ui/Cell';
import Popover from '../ui/Popover'; import DomainCell from './DomainCell';
import { getTrackerData } from '../../helpers/trackers/trackers';
import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants'; import { STATUS_COLORS } from '../../helpers/constants';
import { getPercent } from '../../helpers/helpers';
class QueriedDomains extends Component { const getQueriedPercentColor = (percent) => {
getPercentColor = (percent) => { if (percent > 10) {
if (percent > 10) { return STATUS_COLORS.red;
return STATUS_COLORS.red; } else if (percent > 5) {
} else if (percent > 5) { return STATUS_COLORS.yellow;
return STATUS_COLORS.yellow; }
} return STATUS_COLORS.green;
return STATUS_COLORS.green; };
const countCell = dnsQueries =>
function cell(row) {
const { value } = row;
const percent = getPercent(dnsQueries, value);
const percentColor = getQueriedPercentColor(percent);
return <Cell value={value} percent={percent} color={percentColor} />;
}; };
columns = [ const QueriedDomains = ({
{ t, refreshButton, topQueriedDomains, subtitle, dnsQueries,
Header: <Trans>domain</Trans>, }) => (
accessor: 'domain', <Card
Cell: (row) => { title={t('stats_query_domain')}
const { value } = row; subtitle={subtitle}
const trackerData = getTrackerData(value); bodyType="card-table"
refresh={refreshButton}
return ( >
<div className="logs__row"> <ReactTable
<div className="logs__text" title={value}> data={topQueriedDomains.map(({ name: domain, count }) => ({
{value} domain,
</div> count,
{trackerData && <Popover data={trackerData} />} }))}
</div> columns={[
); {
}, Header: <Trans>domain</Trans>,
}, accessor: 'domain',
{ Cell: DomainCell,
Header: <Trans>requests_count</Trans>, },
accessor: 'count', {
maxWidth: 190, Header: <Trans>requests_count</Trans>,
Cell: ({ value }) => { accessor: 'count',
const percent = getPercent(this.props.dnsQueries, value); maxWidth: 190,
const percentColor = this.getPercentColor(percent); Cell: countCell(dnsQueries),
},
return <Cell value={value} percent={percent} color={percentColor} />; ]}
}, showPagination={false}
}, noDataText={t('no_domains_found')}
]; minRows={6}
className="-striped -highlight card-table-overflow stats__table"
render() { />
const { </Card>
t, refreshButton, topQueriedDomains, subtitle, );
} = this.props;
return (
<Card
title={t('stats_query_domain')}
subtitle={subtitle}
bodyType="card-table"
refresh={refreshButton}
>
<ReactTable
data={topQueriedDomains.map(item => ({
domain: item.name,
count: item.count,
}))}
columns={this.columns}
showPagination={false}
noDataText={t('no_domains_found')}
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
</Card>
);
}
}
QueriedDomains.propTypes = { QueriedDomains.propTypes = {
topQueriedDomains: PropTypes.array.isRequired, topQueriedDomains: PropTypes.array.isRequired,

View File

@ -1,130 +1,67 @@
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next'; import { withNamespaces, Trans } from 'react-i18next';
import Card from '../ui/Card';
import Line from '../ui/Line';
import StatsCard from './StatsCard';
import { getPercent, normalizeHistory } from '../../helpers/helpers'; import { getPercent, normalizeHistory } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
class Statistics extends Component { const getNormalizedHistory = (data, interval, id) => [
getNormalizedHistory = (data, interval, id) => [{ data: normalizeHistory(data, interval), id }]; { data: normalizeHistory(data, interval), id },
];
render() { const Statistics = ({
const { interval,
interval, dnsQueries,
dnsQueries, blockedFiltering,
blockedFiltering, replacedSafebrowsing,
replacedSafebrowsing, replacedParental,
replacedParental, numDnsQueries,
numDnsQueries, numBlockedFiltering,
numBlockedFiltering, numReplacedSafebrowsing,
numReplacedSafebrowsing, numReplacedParental,
numReplacedParental, }) => (
} = this.props; <div className="row">
<div className="col-sm-6 col-lg-3">
return ( <StatsCard
<div className="row"> total={numDnsQueries}
<div className="col-sm-6 col-lg-3"> lineData={getNormalizedHistory(dnsQueries, interval, 'dnsQuery')}
<Card type="card--full" bodyType="card-wrap"> title={<Trans>dns_query</Trans>}
<div className="card-body-stats"> color="blue"
<div className="card-value card-value-stats text-blue"> />
{numDnsQueries} </div>
</div> <div className="col-sm-6 col-lg-3">
<div className="card-title-stats"> <StatsCard
<Trans>dns_query</Trans> total={numBlockedFiltering}
</div> lineData={getNormalizedHistory(blockedFiltering, interval, 'blockedFiltering')}
</div> percent={getPercent(numDnsQueries, numBlockedFiltering)}
<div className="card-chart-bg"> title={<Trans components={[<a href="#filters" key="0">link</a>]}>blocked_by</Trans>}
<Line color="red"
data={this.getNormalizedHistory(dnsQueries, interval, 'dnsQueries')} />
color={STATUS_COLORS.blue} </div>
/> <div className="col-sm-6 col-lg-3">
</div> <StatsCard
</Card> total={numReplacedSafebrowsing}
</div> lineData={getNormalizedHistory(
<div className="col-sm-6 col-lg-3"> replacedSafebrowsing,
<Card type="card--full" bodyType="card-wrap"> interval,
<div className="card-body-stats"> 'replacedSafebrowsing',
<div className="card-value card-value-stats text-red"> )}
{numBlockedFiltering} percent={getPercent(numDnsQueries, numReplacedSafebrowsing)}
</div> title={<Trans>stats_malware_phishing</Trans>}
<div className="card-value card-value-percent text-red"> color="green"
{getPercent(numDnsQueries, numBlockedFiltering)} />
</div> </div>
<div className="card-title-stats"> <div className="col-sm-6 col-lg-3">
<a href="#filters"> <StatsCard
<Trans>blocked_by</Trans> total={numReplacedParental}
</a> lineData={getNormalizedHistory(replacedParental, interval, 'replacedParental')}
</div> percent={getPercent(numDnsQueries, numReplacedParental)}
</div> title={<Trans>stats_adult</Trans>}
<div className="card-chart-bg"> color="yellow"
<Line />
data={this.getNormalizedHistory( </div>
blockedFiltering, </div>
interval, );
'blockedFiltering',
)}
color={STATUS_COLORS.red}
/>
</div>
</Card>
</div>
<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-green">
{numReplacedSafebrowsing}
</div>
<div className="card-value card-value-percent text-green">
{getPercent(numDnsQueries, numReplacedSafebrowsing)}
</div>
<div className="card-title-stats">
<Trans>stats_malware_phishing</Trans>
</div>
</div>
<div className="card-chart-bg">
<Line
data={this.getNormalizedHistory(
replacedSafebrowsing,
interval,
'replacedSafebrowsing',
)}
color={STATUS_COLORS.green}
/>
</div>
</Card>
</div>
<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-yellow">
{numReplacedParental}
</div>
<div className="card-value card-value-percent text-yellow">
{getPercent(numDnsQueries, numReplacedParental)}
</div>
<div className="card-title-stats">
<Trans>stats_adult</Trans>
</div>
</div>
<div className="card-chart-bg">
<Line
data={this.getNormalizedHistory(
replacedParental,
interval,
'replacedParental',
)}
color={STATUS_COLORS.yellow}
/>
</div>
</Card>
</div>
</div>
);
}
}
Statistics.propTypes = { Statistics.propTypes = {
interval: PropTypes.number.isRequired, interval: PropTypes.number.isRequired,

View File

@ -0,0 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { STATUS_COLORS } from '../../helpers/constants';
import Card from '../ui/Card';
import Line from '../ui/Line';
const StatsCard = ({
total, lineData, percent, title, color,
}) => (
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className={`card-value card-value-stats text-${color}`}>{total}</div>
<div className="card-title-stats">{title}</div>
</div>
{percent >= 0 && (<div className={`card-value card-value-percent text-${color}`}>{percent}</div>)}
<div className="card-chart-bg">
<Line data={lineData} color={STATUS_COLORS[color]} />
</div>
</Card>
);
StatsCard.propTypes = {
total: PropTypes.number.isRequired,
lineData: PropTypes.array.isRequired,
title: PropTypes.object.isRequired,
color: PropTypes.string.isRequired,
percent: PropTypes.number,
};
export default StatsCard;

View File

@ -51,7 +51,7 @@ class Dashboard extends Component {
const subtitle = const subtitle =
stats.interval === 1 stats.interval === 1
? t('for_last_24_hours') ? t('for_last_24_hours')
: t('for_last_days', { value: stats.interval }); : t('for_last_days', { count: stats.interval });
const refreshFullButton = ( const refreshFullButton = (
<button <button

View File

@ -3,29 +3,9 @@ import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next'; import { withNamespaces } from 'react-i18next';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import { CLIENT_ID } from '../../../helpers/constants';
import Card from '../../ui/Card'; import Card from '../../ui/Card';
class AutoClients extends Component { class AutoClients extends Component {
getClient = (name, clients) => {
const client = clients.find(item => name === item.name);
if (client) {
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
return {
identifier,
use_global_settings: true,
...client,
};
}
return {
identifier: 'ip',
use_global_settings: true,
};
};
getStats = (ip, stats) => { getStats = (ip, stats) => {
if (stats) { if (stats) {
const statsForCurrentIP = stats.find(item => item.name === ip); const statsForCurrentIP = stats.find(item => item.name === ip);

View File

@ -5,13 +5,13 @@ import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow'; import flow from 'lodash/flow';
import { renderRadioField, toNumber } from '../../../helpers/form'; import { renderRadioField, toNumber } from '../../../helpers/form';
import { STATS_INTERVALS } from '../../../helpers/constants'; import { STATS_INTERVALS_DAYS } from '../../../helpers/constants';
const getIntervalFields = (processing, t, handleChange, toNumber) => const getIntervalFields = (processing, t, handleChange, toNumber) =>
STATS_INTERVALS.map((interval) => { STATS_INTERVALS_DAYS.map((interval) => {
const title = interval === 1 const title = interval === 1
? t('interval_24_hour') ? t('interval_24_hour')
: t('interval_days', { value: interval }); : t('interval_days', { count: interval });
return ( return (
<Field <Field

View File

@ -5,9 +5,7 @@ const Cell = props => (
<div className="stats__row"> <div className="stats__row">
<div className="stats__row-value mb-1"> <div className="stats__row-value mb-1">
<strong>{props.value}</strong> <strong>{props.value}</strong>
<small className="ml-3 text-muted"> <small className="ml-3 text-muted">{props.percent}%</small>
{props.percent}%
</small>
</div> </div>
<div className="progress progress-xs"> <div className="progress progress-xs">
<div <div

View File

@ -261,4 +261,4 @@ export const FILTERED_STATUS = {
REWRITE: 'Rewrite', REWRITE: 'Rewrite',
}; };
export const STATS_INTERVALS = [1, 7, 30, 90]; export const STATS_INTERVALS_DAYS = [1, 7, 30, 90];