From bf5ee5feb474c1d3108ce8bc95a54495a847828a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Leal?= <37.joao.leal@gmail.com>
Date: Fri, 23 Apr 2021 15:01:39 +0100
Subject: [PATCH] fix: add machine actions to 'Dashboard > Machine Details'
---
lib/machine-loader.js | 49 ++-
.../machineActions/MachineActions.js | 221 ++++++++++++++
.../machineActions/MachineActions.styles.js | 82 +++++
.../Machines/MachineComponents/Overview.js | 94 +-----
.../src/pages/Machines/Machines.js | 52 ++--
.../pages/Maintenance/MachineDetailsCard.js | 283 +-----------------
6 files changed, 389 insertions(+), 392 deletions(-)
create mode 100644 new-lamassu-admin/src/components/machineActions/MachineActions.js
create mode 100644 new-lamassu-admin/src/components/machineActions/MachineActions.styles.js
diff --git a/lib/machine-loader.js b/lib/machine-loader.js
index b449e68b..acc31ad1 100644
--- a/lib/machine-loader.js
+++ b/lib/machine-loader.js
@@ -87,9 +87,54 @@ function getMachineName (machineId) {
.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'
- 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) {
diff --git a/new-lamassu-admin/src/components/machineActions/MachineActions.js b/new-lamassu-admin/src/components/machineActions/MachineActions.js
new file mode 100644
index 00000000..90953a22
--- /dev/null
+++ b/new-lamassu-admin/src/components/machineActions/MachineActions.js
@@ -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
{children}
+}
+
+const MachineActions = memo(({ machine, onActionSuccess }) => {
+ const [action, setAction] = useState({ command: null })
+ const [errorMessage, setErrorMessage] = useState(null)
+ const classes = useStyles()
+
+ const warningMessage = (
+
+ A user may be in the middle of a transaction and they could lose their
+ funds if you continue.
+
+ )
+
+ 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 (
+
+
+
+
+ setAction({
+ command: 'rename',
+ display: 'Rename',
+ confirmationMessage: 'Write the new name for this machine'
+ })
+ }>
+ Rename
+
+
+ setAction({
+ command: 'unpair',
+ display: 'Unpair'
+ })
+ }>
+ Unpair
+
+
+ setAction({
+ command: 'reboot',
+ display: 'Reboot'
+ })
+ }>
+ Reboot
+
+
+ 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
+
+
{
+ fetchMachineEvents()
+ setAction({
+ command: 'restartServices',
+ display: 'Restart services for'
+ })
+ }}>
+ Restart Services
+
+
+
{
+ setErrorMessage(null)
+ machineAction({
+ variables: {
+ deviceId: machine.deviceId,
+ action: `${action?.command}`,
+ ...(action?.command === 'rename' && { newName: value })
+ }
+ })
+ }}
+ onDissmised={() => {
+ setAction({ command: null })
+ setErrorMessage(null)
+ }}
+ />
+
+ )
+})
+
+export default MachineActions
diff --git a/new-lamassu-admin/src/components/machineActions/MachineActions.styles.js b/new-lamassu-admin/src/components/machineActions/MachineActions.styles.js
new file mode 100644
index 00000000..672ee3b1
--- /dev/null
+++ b/new-lamassu-admin/src/components/machineActions/MachineActions.styles.js
@@ -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 }
diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js
index 11f9f295..8e40f25d 100644
--- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js
+++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js
@@ -1,35 +1,14 @@
-import { useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles'
-import gql from 'graphql-tag'
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 ActionButton from 'src/components/buttons/ActionButton'
+import MachineActions from 'src/components/machineActions/MachineActions'
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'
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 => {
if (!lastPing) return null
const now = moment()
@@ -50,25 +29,8 @@ const makeLastPing = lastPing => {
}
const Overview = ({ data, onActionSuccess }) => {
- const [action, setAction] = useState('')
- const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false)
- const [errorMessage, setErrorMessage] = useState(null)
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 (
<>
@@ -90,57 +52,11 @@ const Overview = ({ data, onActionSuccess }) => {
- {' '}
-
Actions
- {data.name && (
-
-
confirmActionDialog('Unpair')}>
- Unpair
-
-
confirmActionDialog('Reboot')}>
- Reboot
-
-
confirmActionDialog('Shutdown')}>
- Shutdown
-
-
- )}
+
- {
- setErrorMessage(null)
- machineAction({
- variables: {
- deviceId: data.deviceId,
- action: `${action}`.toLowerCase()
- }
- })
- }}
- onDissmised={() => {
- setConfirmActionDialogOpen(false)
- setErrorMessage(null)
- }}
- />
>
)
}
diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js
index 75e87c2c..84980c99 100644
--- a/new-lamassu-admin/src/pages/Machines/Machines.js
+++ b/new-lamassu-admin/src/pages/Machines/Machines.js
@@ -5,8 +5,7 @@ import { makeStyles } from '@material-ui/core/styles'
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import classnames from 'classnames'
import gql from 'graphql-tag'
-import * as R from 'ramda'
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { TL1, TL2, Label3 } from 'src/components/typography'
@@ -19,11 +18,9 @@ import Transactions from './MachineComponents/Transactions'
import styles from './Machines.styles'
const useStyles = makeStyles(styles)
-const getMachineInfo = R.compose(R.find, R.propEq('name'))
-
const GET_INFO = gql`
- query getInfo {
- machines {
+ query getMachine($deviceId: ID!) {
+ machine(deviceId: $deviceId) {
name
deviceId
paired
@@ -38,24 +35,34 @@ const GET_INFO = gql`
label
type
}
+ latestEvent {
+ note
+ }
}
config
}
`
-const getMachines = R.path(['machines'])
+const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
const Machines = () => {
- const { data, refetch } = useQuery(GET_INFO)
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 selectedMachine =
- R.path(['state', 'selectedMachine'])(location) ??
- R.path(['machines', 0, 'name'])(data) ??
- ''
- const machines = getMachines(data) ?? []
- const machineInfo = getMachineInfo(selectedMachine)(machines) ?? {}
+ useEffect(() => {
+ if (data) setMachine(data.machine)
+ }, [data])
return (
@@ -69,10 +76,10 @@ const Machines = () => {
- {selectedMachine}
+ {machine.name}
-
+
@@ -90,26 +97,23 @@ const Machines = () => {
{'Details'}
-
+
{'Cash cassettes'}
{'Latest transactions'}
-
+
{'Commissions'}
-
+
diff --git a/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js
index 75579f2a..7781470b 100644
--- a/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js
+++ b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js
@@ -1,47 +1,15 @@
-import { useMutation, useLazyQuery } from '@apollo/react-hooks'
import { Grid /*, Divider */ } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
-import gql from 'graphql-tag'
import moment from 'moment'
-import React, { useState } from 'react'
+import React from 'react'
+
+import MachineActions from 'src/components/machineActions/MachineActions'
-import { ConfirmDialog } from 'src/components/ConfirmDialog'
// 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 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 { 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 = [
// {
// // Default article for non-maped statuses
@@ -53,24 +21,6 @@ const MACHINE = gql`
// // 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 }) =>
// supportArtices.find(({ code: article }) => article === status)
@@ -96,51 +46,9 @@ const Item = ({ children, ...props }) => (
)
-const getState = machineEventsLazy =>
- JSON.parse(machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}')
- .state
-
const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
- const [action, setAction] = useState({ command: null })
- const [errorMessage, setErrorMessage] = useState(null)
const classes = useMDStyles()
- const warningMessage = (
-
- A user may be in the middle of a transaction and they could lose their
- funds if you continue.
-
- )
-
- 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 (
{/* -
@@ -180,30 +88,6 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
flexItem
className={classes.separator}
/> */}
- {
- setErrorMessage(null)
- machineAction({
- variables: {
- deviceId: machine.deviceId,
- action: `${action?.command}`,
- ...(action?.command === 'rename' && { newName: value })
- }
- })
- }}
- onDissmised={() => {
- setAction({ command: null })
- setErrorMessage(null)
- }}
- />
-
-
@@ -217,166 +101,11 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
-
-
-
-
- setAction({
- command: 'rename',
- display: 'Rename',
- confirmationMessage: 'Write the new name for this machine'
- })
- }>
- Rename
-
-
- setAction({
- command: 'unpair',
- display: 'Unpair'
- })
- }>
- Unpair
-
-
- setAction({
- command: 'reboot',
- display: 'Reboot'
- })
- }>
- Reboot
-
-
- 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
-
-
{
- fetchMachineEvents()
- setAction({
- command: 'restartServices',
- display: 'Restart services for'
- })
- }}>
- Restart Services
-
-
+
- {/*
- -
-
-
-
- setAction({
- command: 'rename',
- display: 'Rename',
- confirmationMessage: 'Write the new name for this machine'
- })
- }>
- Rename
-
-
- setAction({
- command: 'unpair',
- display: 'Unpair'
- })
- }>
- Unpair
-
-
- setAction({
- command: 'reboot',
- display: 'Reboot'
- })
- }>
- Reboot
-
-
- 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
-
-
{
- fetchMachineEvents()
- setAction({
- command: 'restartServices',
- display: 'Restart services for'
- })
- }}>
- Restart Services
-
-
-
- */}
)