Merge pull request #1100 from chaotixkilla/fix-dashboard-network-performance

Fix missing machine performance on machine overview page
This commit is contained in:
Rafael Taranto 2022-02-17 10:33:42 +00:00 committed by GitHub
commit 045f430766
8 changed files with 75 additions and 55 deletions

View file

@ -110,11 +110,17 @@ function getMachine (machineId, config) {
else return toMachineObject(r) else return toMachineObject(r)
}) })
return Promise.all([queryMachine, dbm.machineEvents(), config]) return Promise.all([queryMachine, dbm.machineEvents(), config, getNetworkHeartbeatByDevice(machineId), getNetworkPerformanceByDevice(machineId)])
.then(([machine, events, config]) => { .then(([machine, events, config, heartbeat, performance]) => {
const pings = checkPings([machine]) const pings = checkPings([machine])
const mergedMachine = {
...machine,
responseTime: _.get('responseTime', heartbeat),
packetLoss: _.get('packetLoss', heartbeat),
downloadSpeed: _.get('downloadSpeed', performance),
}
return addName(pings, events, config)(machine) return addName(pings, events, config)(mergedMachine)
}) })
} }
@ -234,6 +240,20 @@ function getNetworkHeartbeat () {
.then(res => _.map(_.mapKeys(_.camelCase))(res)) .then(res => _.map(_.mapKeys(_.camelCase))(res))
} }
function getNetworkPerformanceByDevice (deviceId) {
const sql = `SELECT device_id, download_speed FROM machine_network_performance WHERE device_id = $1`
return db.manyOrNone(sql, [deviceId])
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
}
function getNetworkHeartbeatByDevice (deviceId) {
const sql = `SELECT AVG(average_response_time) AS response_time, AVG(average_packet_loss) AS packet_loss, device_id
FROM machine_network_heartbeat WHERE device_id = $1
GROUP BY device_id`
return db.manyOrNone(sql, [deviceId])
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
}
module.exports = { module.exports = {
getMachineName, getMachineName,
getMachines, getMachines,

View file

@ -5,6 +5,7 @@ import React, { memo, useState } from 'react'
import { ConfirmDialog } from 'src/components/ConfirmDialog' import { ConfirmDialog } from 'src/components/ConfirmDialog'
import ActionButton from 'src/components/buttons/ActionButton' import ActionButton from 'src/components/buttons/ActionButton'
import { H3 } from 'src/components/typography'
import { ReactComponent as EditReversedIcon } from 'src/styling/icons/button/edit/white.svg' import { ReactComponent as EditReversedIcon } from 'src/styling/icons/button/edit/white.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/button/edit/zodiac.svg' import { ReactComponent as EditIcon } from 'src/styling/icons/button/edit/zodiac.svg'
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg' import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
@ -62,12 +63,6 @@ const getState = machineEventsLazy =>
JSON.parse(machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}') JSON.parse(machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}')
.state .state
const Label = ({ children }) => {
const classes = useStyles()
return <div className={classes.label}>{children}</div>
}
const MachineActions = memo(({ machine, onActionSuccess }) => { const MachineActions = memo(({ machine, onActionSuccess }) => {
const [action, setAction] = useState({ command: null }) const [action, setAction] = useState({ command: null })
const [preflightOptions, setPreflightOptions] = useState({}) const [preflightOptions, setPreflightOptions] = useState({})
@ -115,7 +110,7 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
return ( return (
<div> <div>
<Label>Actions</Label> <H3>Actions</H3>
<div className={classes.stack}> <div className={classes.stack}>
<ActionButton <ActionButton
color="primary" color="primary"

View file

@ -18,9 +18,9 @@ import styles from './Cassettes.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const widthsByNumberOfCassettes = { const widthsByNumberOfCassettes = {
2: { cashbox: 116, cassette: 280, cassetteGraph: 80, editWidth: 174 }, 2: { cashbox: 203, cassette: 280, cassetteGraph: 80, editWidth: 87 },
3: { cashbox: 106, cassette: 200, cassetteGraph: 60, editWidth: 145 }, 3: { cashbox: 164, cassette: 200, cassetteGraph: 60, editWidth: 87 },
4: { cashbox: 106, cassette: 164, cassetteGraph: 40, editWidth: 90 } 4: { cashbox: 131, cassette: 158, cassetteGraph: 40, editWidth: 87 }
} }
const ValidationSchema = Yup.object().shape({ const ValidationSchema = Yup.object().shape({
@ -159,7 +159,7 @@ const CashCassettes = ({ machine, config, refetchData, bills }) => {
elements.push({ elements.push({
name: 'edit', name: 'edit',
header: 'Edit', header: 'Edit',
width: 87, width: widthsByNumberOfCassettes[numberOfCassettes].editWidth,
view: () => { view: () => {
return ( return (
<IconButton <IconButton

View file

@ -49,7 +49,7 @@ const getOverridesFields = currency => {
{ {
name: 'fixedFee', name: 'fixedFee',
display: 'Fixed fee', display: 'Fixed fee',
width: 144, width: 155,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
suffix: currency suffix: currency
@ -57,7 +57,7 @@ const getOverridesFields = currency => {
{ {
name: 'minimumTx', name: 'minimumTx',
display: 'Minimun Tx', display: 'Minimun Tx',
width: 144, width: 155,
doubleHeader: 'Cash-in only', doubleHeader: 'Cash-in only',
textAlign: 'right', textAlign: 'right',
suffix: currency suffix: currency

View file

@ -2,6 +2,7 @@ import { makeStyles } from '@material-ui/core/styles'
import React from 'react' import React from 'react'
import { Label3, P } from 'src/components/typography' import { Label3, P } from 'src/components/typography'
import { modelPrettifier } from 'src/utils/machine'
import { formatDate } from 'src/utils/timezones' import { formatDate } from 'src/utils/timezones'
import styles from '../Machines.styles' import styles from '../Machines.styles'
@ -21,7 +22,7 @@ const Details = ({ data, timezone }) => {
</div> </div>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Machine model</Label3> <Label3 className={classes.label3}>Machine model</Label3>
<P>{data.model}</P> <P>{modelPrettifier[data.model]}</P>
</div> </div>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Software version</Label3> <Label3 className={classes.label3}>Software version</Label3>

View file

@ -1,39 +1,21 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { differenceInSeconds } from 'date-fns/fp' import { formatDistance } from 'date-fns'
import React from 'react' import React from 'react'
import { Status } from 'src/components/Status' import { Status } from 'src/components/Status'
import MachineActions from 'src/components/machineActions/MachineActions' import MachineActions from 'src/components/machineActions/MachineActions'
import { H3, Label3, P } from 'src/components/typography' import { H3, Label1, P } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard.js' import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard.js'
import styles from '../Machines.styles' import styles from '../Machines.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const makeLastPing = lastPing => {
if (!lastPing) return null
const secondsAgo = differenceInSeconds(lastPing, new Date())
if (secondsAgo < 60) {
return `${secondsAgo} ${secondsAgo === 1 ? 'second' : 'seconds'} ago`
}
if (secondsAgo < 3600) {
const minutes = Math.round(secondsAgo / 60)
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`
}
if (secondsAgo < 3600 * 24) {
const hours = Math.round(secondsAgo / 3600)
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`
}
const days = Math.round(secondsAgo / 3600 / 24)
return `${days} ${days === 1 ? 'day' : 'days'} ago`
}
const Overview = ({ data, onActionSuccess }) => { const Overview = ({ data, onActionSuccess }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<> <div className={classes.contentContainer}>
<div className={classes.row}> <div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<H3>{data.name}</H3> <H3>{data.name}</H3>
@ -41,20 +23,32 @@ const Overview = ({ data, onActionSuccess }) => {
</div> </div>
<div className={classes.row}> <div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Status</Label3> <Label1 className={classes.label3}>Status</Label1>
{data && data.statuses ? <Status status={data.statuses[0]} /> : null} {data && data.statuses ? <Status status={data.statuses[0]} /> : null}
</div> </div>
</div> </div>
<div className={classes.row}> <div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Last ping</Label3> <Label1 className={classes.label3}>Ping</Label1>
<P>{makeLastPing(data.lastPing)}</P> <P noMargin>
{data.responseTime
? new BigNumber(data.responseTime).toFixed(3).toString() + ' ms'
: 'unavailable'}
</P>
</div> </div>
</div>
<div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Network speed</Label3> <Label1 className={classes.label3}>Last ping</Label1>
<P> <P noMargin>
{data.lastPing
? formatDistance(new Date(data.lastPing), new Date(), {
addSuffix: true
})
: 'unknown'}
</P>
</div>
<div className={classes.rowItem}>
<Label1 className={classes.label3}>Network speed</Label1>
<P noMargin>
{data.downloadSpeed {data.downloadSpeed
? new BigNumber(data.downloadSpeed).toFixed(4).toString() + ? new BigNumber(data.downloadSpeed).toFixed(4).toString() +
' MB/s' ' MB/s'
@ -62,22 +56,22 @@ const Overview = ({ data, onActionSuccess }) => {
</P> </P>
</div> </div>
</div> </div>
<div className={classes.row}>
<MachineActions
machine={data}
onActionSuccess={onActionSuccess}></MachineActions>
</div>
<div className={classes.row}> <div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Device ID</Label3> <Label1 className={classes.label3}>Device ID</Label1>
<P> <P noMargin>
<CopyToClipboard buttonClassname={classes.copyToClipboard}> <CopyToClipboard buttonClassname={classes.copyToClipboard}>
{data.deviceId} {data.deviceId}
</CopyToClipboard> </CopyToClipboard>
</P> </P>
</div> </div>
</div> </div>
</> <div className={classes.row}>
<MachineActions
machine={data}
onActionSuccess={onActionSuccess}></MachineActions>
</div>
</div>
) )
} }

View file

@ -21,7 +21,8 @@ const styles = {
}, },
label3: { label3: {
color: comet, color: comet,
marginTop: 0 marginTop: 0,
fontSize: 12
}, },
row: { row: {
display: 'flex', display: 'flex',
@ -51,6 +52,14 @@ const styles = {
sidebarContainer: { sidebarContainer: {
height: 400, height: 400,
overflowY: 'auto' overflowY: 'auto'
},
contentContainer: {
'& > *': {
marginTop: 26
},
'& > *:first-child': {
marginTop: 0
}
} }
} }

View file

@ -1,7 +1,8 @@
const modelPrettifier = { const modelPrettifier = {
douro1: 'Douro', douro1: 'Douro',
sintra: 'Sintra', sintra: 'Sintra',
gaia: 'Gaia' gaia: 'Gaia',
tejo: 'Tejo'
} }
export { modelPrettifier } export { modelPrettifier }