fix: add machine actions to 'Dashboard > Machine Details'
This commit is contained in:
parent
3ac49760da
commit
f69b71da65
6 changed files with 390 additions and 413 deletions
|
|
@ -91,9 +91,54 @@ function getMachineName (machineId) {
|
||||||
.then(it => it.name)
|
.then(it => it.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMachine (machineId) {
|
function getMachine (machineId, config) {
|
||||||
|
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
|
||||||
|
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
|
||||||
|
const stuckStatus = { label: 'Stuck', type: 'error' }
|
||||||
|
|
||||||
const sql = 'SELECT * FROM devices WHERE device_id=$1'
|
const sql = 'SELECT * FROM devices WHERE device_id=$1'
|
||||||
return db.oneOrNone(sql, [machineId]).then(res => _.mapKeys(_.camelCase)(res))
|
const queryMachine = db.oneOrNone(sql, [machineId]).then(r => ({
|
||||||
|
deviceId: r.device_id,
|
||||||
|
cashbox: r.cashbox,
|
||||||
|
cassette1: r.cassette1,
|
||||||
|
cassette2: r.cassette2,
|
||||||
|
version: r.version,
|
||||||
|
model: r.model,
|
||||||
|
pairedAt: new Date(r.created),
|
||||||
|
lastPing: new Date(r.last_online),
|
||||||
|
name: r.name,
|
||||||
|
paired: r.paired
|
||||||
|
}))
|
||||||
|
|
||||||
|
return Promise.all([queryMachine, dbm.machineEvents(), config])
|
||||||
|
.then(([machine, events, config]) => {
|
||||||
|
const pings = checkPings([machine])
|
||||||
|
|
||||||
|
const getStatus = (ping, stuck) => {
|
||||||
|
if (ping && ping.age) return unresponsiveStatus
|
||||||
|
|
||||||
|
if (stuck && stuck.age) return stuckStatus
|
||||||
|
|
||||||
|
return fullyFunctionalStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
const addName = r => {
|
||||||
|
const cashOutConfig = configManager.getCashOut(r.deviceId, config)
|
||||||
|
|
||||||
|
const cashOut = !!cashOutConfig.active
|
||||||
|
|
||||||
|
const statuses = [
|
||||||
|
getStatus(
|
||||||
|
_.first(pings[r.deviceId]),
|
||||||
|
_.first(checkStuckScreen(events, r.name))
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return _.assign(r, { cashOut, statuses })
|
||||||
|
}
|
||||||
|
|
||||||
|
return addName(machine)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renameMachine (rec) {
|
function renameMachine (rec) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
// import moment from 'moment'
|
||||||
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
|
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
||||||
|
import ActionButton from 'src/components/buttons/ActionButton'
|
||||||
|
// // import { Status } from 'src/components/Status'
|
||||||
|
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 RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
|
||||||
|
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
|
||||||
|
import { ReactComponent as ShutdownReversedIcon } from 'src/styling/icons/button/shut down/white.svg'
|
||||||
|
import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut down/zodiac.svg'
|
||||||
|
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
|
||||||
|
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
||||||
|
|
||||||
|
import { machineActionsStyles } from './MachineActions.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(machineActionsStyles)
|
||||||
|
|
||||||
|
const MACHINE_ACTION = gql`
|
||||||
|
mutation MachineAction(
|
||||||
|
$deviceId: ID!
|
||||||
|
$action: MachineAction!
|
||||||
|
$newName: String
|
||||||
|
) {
|
||||||
|
machineAction(deviceId: $deviceId, action: $action, newName: $newName) {
|
||||||
|
deviceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const MACHINE = gql`
|
||||||
|
query getMachine($deviceId: ID!) {
|
||||||
|
machine(deviceId: $deviceId) {
|
||||||
|
latestEvent {
|
||||||
|
note
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const isStaticState = machineState => {
|
||||||
|
if (!machineState) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const staticStates = [
|
||||||
|
'chooseCoin',
|
||||||
|
'idle',
|
||||||
|
'pendingIdle',
|
||||||
|
'dualIdle',
|
||||||
|
'networkDown',
|
||||||
|
'unpaired',
|
||||||
|
'maintenance',
|
||||||
|
'virgin',
|
||||||
|
'wifiList'
|
||||||
|
]
|
||||||
|
return staticStates.includes(machineState)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getState = machineEventsLazy =>
|
||||||
|
JSON.parse(machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}')
|
||||||
|
.state
|
||||||
|
|
||||||
|
const Label = ({ children }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return <div className={classes.label}>{children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const MachineActions = memo(({ machine, onActionSuccess }) => {
|
||||||
|
const [action, setAction] = useState({ command: null })
|
||||||
|
const [errorMessage, setErrorMessage] = useState(null)
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const warningMessage = (
|
||||||
|
<span className={classes.warning}>
|
||||||
|
A user may be in the middle of a transaction and they could lose their
|
||||||
|
funds if you continue.
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
const [fetchMachineEvents, { loading: loadingEvents }] = useLazyQuery(
|
||||||
|
MACHINE,
|
||||||
|
{
|
||||||
|
variables: {
|
||||||
|
deviceId: machine.deviceId
|
||||||
|
},
|
||||||
|
onCompleted: machineEventsLazy => {
|
||||||
|
const message = !isStaticState(getState(machineEventsLazy))
|
||||||
|
? warningMessage
|
||||||
|
: null
|
||||||
|
setAction(action => ({ ...action, message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
||||||
|
onError: ({ message }) => {
|
||||||
|
const errorMessage = message ?? 'An error ocurred'
|
||||||
|
setErrorMessage(errorMessage)
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
onActionSuccess && onActionSuccess()
|
||||||
|
setAction({ command: null })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirmDialogOpen = Boolean(action.command)
|
||||||
|
const disabled = !!(action?.command === 'restartServices' && loadingEvents)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Label>Actions</Label>
|
||||||
|
<div className={classes.stack}>
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.mr}
|
||||||
|
Icon={EditIcon}
|
||||||
|
InverseIcon={EditReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() =>
|
||||||
|
setAction({
|
||||||
|
command: 'rename',
|
||||||
|
display: 'Rename',
|
||||||
|
confirmationMessage: 'Write the new name for this machine'
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Rename
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.mr}
|
||||||
|
Icon={UnpairIcon}
|
||||||
|
InverseIcon={UnpairReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() =>
|
||||||
|
setAction({
|
||||||
|
command: 'unpair',
|
||||||
|
display: 'Unpair'
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Unpair
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.mr}
|
||||||
|
Icon={RebootIcon}
|
||||||
|
InverseIcon={RebootReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() =>
|
||||||
|
setAction({
|
||||||
|
command: 'reboot',
|
||||||
|
display: 'Reboot'
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Reboot
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.mr}
|
||||||
|
Icon={ShutdownIcon}
|
||||||
|
InverseIcon={ShutdownReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() =>
|
||||||
|
setAction({
|
||||||
|
command: 'shutdown',
|
||||||
|
display: 'Shutdown',
|
||||||
|
message:
|
||||||
|
'In order to bring it back online, the machine will need to be visited and its power reset.'
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
Shutdown
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.inlineChip}
|
||||||
|
Icon={RebootIcon}
|
||||||
|
InverseIcon={RebootReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => {
|
||||||
|
fetchMachineEvents()
|
||||||
|
setAction({
|
||||||
|
command: 'restartServices',
|
||||||
|
display: 'Restart services for'
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
Restart Services
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
<ConfirmDialog
|
||||||
|
disabled={disabled}
|
||||||
|
open={confirmDialogOpen}
|
||||||
|
title={`${action?.display} this machine?`}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
toBeConfirmed={machine.name}
|
||||||
|
message={action?.message}
|
||||||
|
confirmationMessage={action?.confirmationMessage}
|
||||||
|
saveButtonAlwaysEnabled={action?.command === 'rename'}
|
||||||
|
onConfirmed={value => {
|
||||||
|
setErrorMessage(null)
|
||||||
|
machineAction({
|
||||||
|
variables: {
|
||||||
|
deviceId: machine.deviceId,
|
||||||
|
action: `${action?.command}`,
|
||||||
|
...(action?.command === 'rename' && { newName: value })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onDissmised={() => {
|
||||||
|
setAction({ command: null })
|
||||||
|
setErrorMessage(null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default MachineActions
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { fade } from '@material-ui/core/styles/colorManipulator'
|
||||||
|
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
|
import {
|
||||||
|
offColor,
|
||||||
|
spacer,
|
||||||
|
comet,
|
||||||
|
primaryColor,
|
||||||
|
fontSize4,
|
||||||
|
errorColor
|
||||||
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
|
const { label1 } = typographyStyles
|
||||||
|
|
||||||
|
const machineActionsStyles = {
|
||||||
|
colDivider: {
|
||||||
|
width: 1,
|
||||||
|
margin: [[spacer * 2, spacer * 4]],
|
||||||
|
backgroundColor: comet,
|
||||||
|
border: 'none'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
extend: label1,
|
||||||
|
color: offColor,
|
||||||
|
marginBottom: 4
|
||||||
|
},
|
||||||
|
inlineChip: {
|
||||||
|
marginInlineEnd: '0.25em'
|
||||||
|
},
|
||||||
|
stack: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'start'
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
// marginTop: 24,
|
||||||
|
// marginBottom: 32,
|
||||||
|
marginTop: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
fontSize: fontSize4
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
// marginBottom: 36
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
listStyle: 'none'
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
height: spacer * 3,
|
||||||
|
marginBottom: spacer * 1.5
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
color: primaryColor,
|
||||||
|
textDecoration: 'none'
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
margin: '0 1rem'
|
||||||
|
},
|
||||||
|
mr: {
|
||||||
|
marginRight: spacer,
|
||||||
|
marginBottom: spacer
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
width: 1,
|
||||||
|
height: 170,
|
||||||
|
zIndex: 1,
|
||||||
|
marginRight: 60,
|
||||||
|
marginLeft: 'auto',
|
||||||
|
background: fade(comet, 0.5)
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
color: errorColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { machineActionsStyles }
|
||||||
|
|
@ -1,36 +1,15 @@
|
||||||
import { useMutation } from '@apollo/react-hooks'
|
|
||||||
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 gql from 'graphql-tag'
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
|
||||||
import { Status } from 'src/components/Status'
|
import { Status } from 'src/components/Status'
|
||||||
import ActionButton from 'src/components/buttons/ActionButton'
|
import MachineActions from 'src/components/machineActions/MachineActions'
|
||||||
import { H3, Label3, P } from 'src/components/typography'
|
import { H3, Label3, P } from 'src/components/typography'
|
||||||
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
|
|
||||||
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
|
|
||||||
import { ReactComponent as ShutdownReversedIcon } from 'src/styling/icons/button/shut down/white.svg'
|
|
||||||
import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut down/zodiac.svg'
|
|
||||||
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
|
|
||||||
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
|
||||||
|
|
||||||
import styles from '../Machines.styles'
|
import styles from '../Machines.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const MACHINE_ACTION = gql`
|
|
||||||
mutation MachineAction(
|
|
||||||
$deviceId: ID!
|
|
||||||
$action: MachineAction!
|
|
||||||
$newName: String
|
|
||||||
) {
|
|
||||||
machineAction(deviceId: $deviceId, action: $action, newName: $newName) {
|
|
||||||
deviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const makeLastPing = lastPing => {
|
const makeLastPing = lastPing => {
|
||||||
if (!lastPing) return null
|
if (!lastPing) return null
|
||||||
const now = moment()
|
const now = moment()
|
||||||
|
|
@ -51,25 +30,8 @@ const makeLastPing = lastPing => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Overview = ({ data, onActionSuccess }) => {
|
const Overview = ({ data, onActionSuccess }) => {
|
||||||
const [action, setAction] = useState('')
|
|
||||||
const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false)
|
|
||||||
const [errorMessage, setErrorMessage] = useState(null)
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [machineAction] = useMutation(MACHINE_ACTION, {
|
|
||||||
onError: ({ message }) => {
|
|
||||||
const errorMessage = message ?? 'An error ocurred'
|
|
||||||
setErrorMessage(errorMessage)
|
|
||||||
},
|
|
||||||
onCompleted: () => {
|
|
||||||
onActionSuccess && onActionSuccess()
|
|
||||||
setConfirmActionDialogOpen(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const confirmActionDialog = action =>
|
|
||||||
setAction(action) || setConfirmActionDialogOpen(true)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.row}>
|
<div className={classes.row}>
|
||||||
|
|
@ -101,78 +63,10 @@ const Overview = ({ data, onActionSuccess }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.row}>
|
<div className={classes.row}>
|
||||||
<div className={classes.rowItem}>
|
<MachineActions
|
||||||
<Label3 className={classes.label3}>Latency</Label3>
|
machine={data}
|
||||||
<P>
|
onActionSuccess={onActionSuccess}></MachineActions>
|
||||||
{data.responseTime
|
|
||||||
? new BigNumber(data.responseTime).toFixed(3).toString() + ' ms'
|
|
||||||
: 'unavailable'}
|
|
||||||
</P>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.row}>
|
|
||||||
<div className={classes.rowItem}>
|
|
||||||
<Label3 className={classes.label3}>Packet Loss</Label3>
|
|
||||||
<P>
|
|
||||||
{data.packetLoss
|
|
||||||
? new BigNumber(data.packetLoss).toFixed(3).toString() + ' %'
|
|
||||||
: 'unavailable'}
|
|
||||||
</P>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={classes.row}>
|
|
||||||
<div className={classes.rowItem}>
|
|
||||||
{' '}
|
|
||||||
<Label3 className={classes.label3}>Actions</Label3>
|
|
||||||
{data.name && (
|
|
||||||
<div className={classes.actionButtonsContainer}>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.actionButton}
|
|
||||||
Icon={UnpairIcon}
|
|
||||||
InverseIcon={UnpairReversedIcon}
|
|
||||||
onClick={() => confirmActionDialog('Unpair')}>
|
|
||||||
Unpair
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.actionButton}
|
|
||||||
Icon={RebootIcon}
|
|
||||||
InverseIcon={RebootReversedIcon}
|
|
||||||
onClick={() => confirmActionDialog('Reboot')}>
|
|
||||||
Reboot
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
className={classes.actionButton}
|
|
||||||
color="primary"
|
|
||||||
Icon={ShutdownIcon}
|
|
||||||
InverseIcon={ShutdownReversedIcon}
|
|
||||||
onClick={() => confirmActionDialog('Shutdown')}>
|
|
||||||
Shutdown
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ConfirmDialog
|
|
||||||
open={confirmActionDialogOpen}
|
|
||||||
title={`${action} this machine?`}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
toBeConfirmed={data.name}
|
|
||||||
onConfirmed={() => {
|
|
||||||
setErrorMessage(null)
|
|
||||||
machineAction({
|
|
||||||
variables: {
|
|
||||||
deviceId: data.deviceId,
|
|
||||||
action: `${action}`.toLowerCase()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
onDissmised={() => {
|
|
||||||
setConfirmActionDialogOpen(false)
|
|
||||||
setErrorMessage(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import NavigateNextIcon from '@material-ui/icons/NavigateNext'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import { TL1, TL2, Label3 } from 'src/components/typography'
|
import { TL1, TL2, Label3 } from 'src/components/typography'
|
||||||
|
|
@ -19,11 +19,9 @@ import Transactions from './MachineComponents/Transactions'
|
||||||
import styles from './Machines.styles'
|
import styles from './Machines.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const getMachineInfo = R.compose(R.find, R.propEq('name'))
|
|
||||||
|
|
||||||
const GET_INFO = gql`
|
const GET_INFO = gql`
|
||||||
query getInfo {
|
query getMachine($deviceId: ID!) {
|
||||||
machines {
|
machine(deviceId: $deviceId) {
|
||||||
name
|
name
|
||||||
deviceId
|
deviceId
|
||||||
paired
|
paired
|
||||||
|
|
@ -41,26 +39,37 @@ const GET_INFO = gql`
|
||||||
downloadSpeed
|
downloadSpeed
|
||||||
responseTime
|
responseTime
|
||||||
packetLoss
|
packetLoss
|
||||||
|
latestEvent {
|
||||||
|
note
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const getMachines = R.path(['machines'])
|
const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
|
||||||
|
|
||||||
const Machines = () => {
|
const Machines = () => {
|
||||||
const { data, refetch } = useQuery(GET_INFO)
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const [machine, setMachine] = useState({})
|
||||||
|
const [config, setConfig] = useState({})
|
||||||
|
const { data, refetch } = useQuery(GET_INFO, {
|
||||||
|
variables: {
|
||||||
|
deviceId: getMachineID(location.pathname)
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
setMachine(data.machine)
|
||||||
|
setConfig(data.config)
|
||||||
|
}
|
||||||
|
})
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const selectedMachine =
|
|
||||||
R.path(['state', 'selectedMachine'])(location) ??
|
|
||||||
R.path(['machines', 0, 'name'])(data) ??
|
|
||||||
''
|
|
||||||
const machines = getMachines(data) ?? []
|
|
||||||
const machineInfo = getMachineInfo(selectedMachine)(machines) ?? {}
|
|
||||||
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
|
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) setMachine(data.machine)
|
||||||
|
}, [data])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container className={classes.grid}>
|
<Grid container className={classes.grid}>
|
||||||
<Grid item xs={3}>
|
<Grid item xs={3}>
|
||||||
|
|
@ -73,10 +82,10 @@ const Machines = () => {
|
||||||
</Label3>
|
</Label3>
|
||||||
</Link>
|
</Link>
|
||||||
<TL2 noMargin className={classes.subtitle}>
|
<TL2 noMargin className={classes.subtitle}>
|
||||||
{selectedMachine}
|
{machine.name}
|
||||||
</TL2>
|
</TL2>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<Overview data={machineInfo} onActionSuccess={refetch} />
|
<Overview data={machine} onActionSuccess={refetch} />
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|
@ -94,26 +103,23 @@ const Machines = () => {
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.detailItem, classes.detailsMargin)}>
|
className={classnames(classes.detailItem, classes.detailsMargin)}>
|
||||||
<TL1 className={classes.subtitle}>{'Details'}</TL1>
|
<TL1 className={classes.subtitle}>{'Details'}</TL1>
|
||||||
<Details data={machineInfo} timezone={timezone} />
|
<Details data={machine} timezone={timezone} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.detailItem}>
|
<div className={classes.detailItem}>
|
||||||
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
|
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
|
||||||
<Cassettes
|
<Cassettes
|
||||||
refetchData={refetch}
|
refetchData={refetch}
|
||||||
machine={machineInfo}
|
machine={machine}
|
||||||
config={data?.config ?? false}
|
config={config ?? false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.transactionsItem}>
|
<div className={classes.transactionsItem}>
|
||||||
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
|
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
|
||||||
<Transactions id={machineInfo?.deviceId ?? null} />
|
<Transactions id={machine?.deviceId ?? null} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.detailItem}>
|
<div className={classes.detailItem}>
|
||||||
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
|
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
|
||||||
<Commissions
|
<Commissions name={'commissions'} id={machine?.deviceId ?? null} />
|
||||||
name={'commissions'}
|
|
||||||
id={machineInfo?.deviceId ?? null}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,16 @@
|
||||||
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
|
||||||
import { Grid /*, Divider */ } from '@material-ui/core'
|
import { Grid /*, Divider */ } from '@material-ui/core'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import gql from 'graphql-tag'
|
import React from 'react'
|
||||||
import React, { useState } from 'react'
|
|
||||||
|
import MachineActions from 'src/components/machineActions/MachineActions'
|
||||||
|
|
||||||
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
|
||||||
// import { Status } from 'src/components/Status'
|
// import { Status } from 'src/components/Status'
|
||||||
import ActionButton from 'src/components/buttons/ActionButton'
|
|
||||||
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 LinkIcon } from 'src/styling/icons/button/link/zodiac.svg'
|
// import { ReactComponent as LinkIcon } from 'src/styling/icons/button/link/zodiac.svg'
|
||||||
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
|
|
||||||
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
|
|
||||||
import { ReactComponent as ShutdownReversedIcon } from 'src/styling/icons/button/shut down/white.svg'
|
|
||||||
import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut down/zodiac.svg'
|
|
||||||
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
|
|
||||||
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
|
||||||
import { modelPrettifier } from 'src/utils/machine'
|
import { modelPrettifier } from 'src/utils/machine'
|
||||||
import { formatDate } from 'src/utils/timezones'
|
import { formatDate } from 'src/utils/timezones'
|
||||||
|
|
||||||
import { labelStyles, machineDetailsStyles } from './MachineDetailsCard.styles'
|
import { labelStyles, machineDetailsStyles } from './MachineDetailsCard.styles'
|
||||||
|
|
||||||
const MACHINE_ACTION = gql`
|
|
||||||
mutation MachineAction(
|
|
||||||
$deviceId: ID!
|
|
||||||
$action: MachineAction!
|
|
||||||
$newName: String
|
|
||||||
) {
|
|
||||||
machineAction(deviceId: $deviceId, action: $action, newName: $newName) {
|
|
||||||
deviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const MACHINE = gql`
|
|
||||||
query getMachine($deviceId: ID!) {
|
|
||||||
machine(deviceId: $deviceId) {
|
|
||||||
latestEvent {
|
|
||||||
note
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// const supportArtices = [
|
// const supportArtices = [
|
||||||
// {
|
// {
|
||||||
// // Default article for non-maped statuses
|
// // Default article for non-maped statuses
|
||||||
|
|
@ -54,24 +22,6 @@ const MACHINE = gql`
|
||||||
// // TODO add Stuck and Fully Functional statuses articles for the new-admins
|
// // TODO add Stuck and Fully Functional statuses articles for the new-admins
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
const isStaticState = machineState => {
|
|
||||||
if (!machineState) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const staticStates = [
|
|
||||||
'chooseCoin',
|
|
||||||
'idle',
|
|
||||||
'pendingIdle',
|
|
||||||
'dualIdle',
|
|
||||||
'networkDown',
|
|
||||||
'unpaired',
|
|
||||||
'maintenance',
|
|
||||||
'virgin',
|
|
||||||
'wifiList'
|
|
||||||
]
|
|
||||||
return staticStates.includes(machineState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// const article = ({ code: status }) =>
|
// const article = ({ code: status }) =>
|
||||||
// supportArtices.find(({ code: article }) => article === status)
|
// supportArtices.find(({ code: article }) => article === status)
|
||||||
|
|
||||||
|
|
@ -97,51 +47,9 @@ const Item = ({ children, ...props }) => (
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
|
|
||||||
const getState = machineEventsLazy =>
|
|
||||||
JSON.parse(machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}')
|
|
||||||
.state
|
|
||||||
|
|
||||||
const MachineDetailsRow = ({ it: machine, onActionSuccess, timezone }) => {
|
const MachineDetailsRow = ({ it: machine, onActionSuccess, timezone }) => {
|
||||||
const [action, setAction] = useState({ command: null })
|
|
||||||
const [errorMessage, setErrorMessage] = useState(null)
|
|
||||||
const classes = useMDStyles()
|
const classes = useMDStyles()
|
||||||
|
|
||||||
const warningMessage = (
|
|
||||||
<span className={classes.warning}>
|
|
||||||
A user may be in the middle of a transaction and they could lose their
|
|
||||||
funds if you continue.
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
|
|
||||||
const [fetchMachineEvents, { loading: loadingEvents }] = useLazyQuery(
|
|
||||||
MACHINE,
|
|
||||||
{
|
|
||||||
variables: {
|
|
||||||
deviceId: machine.deviceId
|
|
||||||
},
|
|
||||||
onCompleted: machineEventsLazy => {
|
|
||||||
const message = !isStaticState(getState(machineEventsLazy))
|
|
||||||
? warningMessage
|
|
||||||
: null
|
|
||||||
setAction(action => ({ ...action, message }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
|
||||||
onError: ({ message }) => {
|
|
||||||
const errorMessage = message ?? 'An error ocurred'
|
|
||||||
setErrorMessage(errorMessage)
|
|
||||||
},
|
|
||||||
onCompleted: () => {
|
|
||||||
onActionSuccess && onActionSuccess()
|
|
||||||
setAction({ command: null })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const confirmDialogOpen = Boolean(action.command)
|
|
||||||
const disabled = !!(action?.command === 'restartServices' && loadingEvents)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={classes.wrapper}>
|
<Container className={classes.wrapper}>
|
||||||
{/* <Item xs={5}>
|
{/* <Item xs={5}>
|
||||||
|
|
@ -181,30 +89,6 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess, timezone }) => {
|
||||||
flexItem
|
flexItem
|
||||||
className={classes.separator}
|
className={classes.separator}
|
||||||
/> */}
|
/> */}
|
||||||
<ConfirmDialog
|
|
||||||
disabled={disabled}
|
|
||||||
open={confirmDialogOpen}
|
|
||||||
title={`${action?.display} this machine?`}
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
toBeConfirmed={machine.name}
|
|
||||||
message={action?.message}
|
|
||||||
confirmationMessage={action?.confirmationMessage}
|
|
||||||
saveButtonAlwaysEnabled={action?.command === 'rename'}
|
|
||||||
onConfirmed={value => {
|
|
||||||
setErrorMessage(null)
|
|
||||||
machineAction({
|
|
||||||
variables: {
|
|
||||||
deviceId: machine.deviceId,
|
|
||||||
action: `${action?.command}`,
|
|
||||||
...(action?.command === 'rename' && { newName: value })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
onDissmised={() => {
|
|
||||||
setAction({ command: null })
|
|
||||||
setErrorMessage(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Item xs>
|
<Item xs>
|
||||||
<Container className={classes.row}>
|
<Container className={classes.row}>
|
||||||
<Item xs={2}>
|
<Item xs={2}>
|
||||||
|
|
@ -219,166 +103,11 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess, timezone }) => {
|
||||||
</span>
|
</span>
|
||||||
</Item>
|
</Item>
|
||||||
<Item xs={6}>
|
<Item xs={6}>
|
||||||
<Label>Actions</Label>
|
<MachineActions
|
||||||
<div className={classes.stack}>
|
machine={machine}
|
||||||
<ActionButton
|
onActionSuccess={onActionSuccess}></MachineActions>
|
||||||
className={classes.mr}
|
|
||||||
disabled={loading}
|
|
||||||
color="primary"
|
|
||||||
Icon={EditIcon}
|
|
||||||
InverseIcon={EditReversedIcon}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'rename',
|
|
||||||
display: 'Rename',
|
|
||||||
confirmationMessage: 'Write the new name for this machine'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Rename
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.mr}
|
|
||||||
Icon={UnpairIcon}
|
|
||||||
InverseIcon={UnpairReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'unpair',
|
|
||||||
display: 'Unpair'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Unpair
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.mr}
|
|
||||||
Icon={RebootIcon}
|
|
||||||
InverseIcon={RebootReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'reboot',
|
|
||||||
display: 'Reboot'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Reboot
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
className={classes.mr}
|
|
||||||
disabled={loading}
|
|
||||||
color="primary"
|
|
||||||
Icon={ShutdownIcon}
|
|
||||||
InverseIcon={ShutdownReversedIcon}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'shutdown',
|
|
||||||
display: 'Shutdown',
|
|
||||||
message:
|
|
||||||
'In order to bring it back online, the machine will need to be visited and its power reset.'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Shutdown
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.inlineChip}
|
|
||||||
Icon={RebootIcon}
|
|
||||||
InverseIcon={RebootReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() => {
|
|
||||||
fetchMachineEvents()
|
|
||||||
setAction({
|
|
||||||
command: 'restartServices',
|
|
||||||
display: 'Restart services for'
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
Restart Services
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</Item>
|
</Item>
|
||||||
</Container>
|
</Container>
|
||||||
{/* <Container>
|
|
||||||
<Item>
|
|
||||||
<Label>Actions</Label>
|
|
||||||
<div className={classes.stack}>
|
|
||||||
<ActionButton
|
|
||||||
className={classes.mr}
|
|
||||||
disabled={loading}
|
|
||||||
color="primary"
|
|
||||||
Icon={EditIcon}
|
|
||||||
InverseIcon={EditReversedIcon}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'rename',
|
|
||||||
display: 'Rename',
|
|
||||||
confirmationMessage: 'Write the new name for this machine'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Rename
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.mr}
|
|
||||||
Icon={UnpairIcon}
|
|
||||||
InverseIcon={UnpairReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'unpair',
|
|
||||||
display: 'Unpair'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Unpair
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.mr}
|
|
||||||
Icon={RebootIcon}
|
|
||||||
InverseIcon={RebootReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'reboot',
|
|
||||||
display: 'Reboot'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Reboot
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
className={classes.mr}
|
|
||||||
disabled={loading}
|
|
||||||
color="primary"
|
|
||||||
Icon={ShutdownIcon}
|
|
||||||
InverseIcon={ShutdownReversedIcon}
|
|
||||||
onClick={() =>
|
|
||||||
setAction({
|
|
||||||
command: 'shutdown',
|
|
||||||
display: 'Shutdown',
|
|
||||||
message:
|
|
||||||
'In order to bring it back online, the machine will need to be visited and its power reset.'
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
Shutdown
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
color="primary"
|
|
||||||
className={classes.inlineChip}
|
|
||||||
Icon={RebootIcon}
|
|
||||||
InverseIcon={RebootReversedIcon}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() => {
|
|
||||||
fetchMachineEvents()
|
|
||||||
setAction({
|
|
||||||
command: 'restartServices',
|
|
||||||
display: 'Restart services for'
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
Restart Services
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</Item>
|
|
||||||
</Container> */}
|
|
||||||
</Item>
|
</Item>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue