feat: camera diagnostics (#1681)
This commit is contained in:
parent
5ae9b76c3b
commit
e1c2cc8619
13 changed files with 395 additions and 3 deletions
|
|
@ -231,6 +231,7 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
||||||
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
|
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
|
||||||
_.set('emptyUnit', !!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid),
|
_.set('emptyUnit', !!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid),
|
||||||
_.set('refillUnit', !!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid),
|
_.set('refillUnit', !!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid),
|
||||||
|
_.set('diagnostics', !!pid && state.diagnostics?.[operatorId]?.[deviceId] === pid),
|
||||||
)(pq)
|
)(pq)
|
||||||
|
|
||||||
// Clean up the state middleware and prevent commands from being issued more than once
|
// Clean up the state middleware and prevent commands from being issued more than once
|
||||||
|
|
@ -242,6 +243,10 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
||||||
delete state.refillUnit?.[operatorId]?.[deviceId]
|
delete state.refillUnit?.[operatorId]?.[deviceId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_.isNil(state.diagnostics?.[operatorId]?.[deviceId])) {
|
||||||
|
delete state.diagnostics?.[operatorId]?.[deviceId]
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,7 @@ type DynamicConfig {
|
||||||
restartServices: Boolean!
|
restartServices: Boolean!
|
||||||
emptyUnit: Boolean!
|
emptyUnit: Boolean!
|
||||||
refillUnit: Boolean!
|
refillUnit: Boolean!
|
||||||
|
diagnostics: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Configs {
|
type Configs {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
const fsPromises = require('fs').promises
|
||||||
|
const path = require('path')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const pgp = require('pg-promise')()
|
const pgp = require('pg-promise')()
|
||||||
const uuid = require('uuid')
|
const uuid = require('uuid')
|
||||||
|
const makeDir = require('make-dir')
|
||||||
|
|
||||||
const batching = require('./cashbox-batches')
|
const batching = require('./cashbox-batches')
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
|
|
@ -13,10 +16,12 @@ const notifierUtils = require('./notifier/utils')
|
||||||
const notifierQueries = require('./notifier/queries')
|
const notifierQueries = require('./notifier/queries')
|
||||||
const { ApolloError } = require('apollo-server-errors');
|
const { ApolloError } = require('apollo-server-errors');
|
||||||
const { loadLatestConfig } = require('./new-settings-loader')
|
const { loadLatestConfig } = require('./new-settings-loader')
|
||||||
|
const logger = require('./logger')
|
||||||
|
|
||||||
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
|
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
|
||||||
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
|
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
|
||||||
const stuckStatus = { label: 'Stuck', type: 'error' }
|
const stuckStatus = { label: 'Stuck', type: 'error' }
|
||||||
|
const OPERATOR_DATA_DIR = process.env.OPERATOR_DATA_DIR
|
||||||
|
|
||||||
const MACHINE_WITH_CALCULATED_FIELD_SQL = `
|
const MACHINE_WITH_CALCULATED_FIELD_SQL = `
|
||||||
select d.*, COALESCE(emptybills, 0) + COALESCE(regularbills, 0) as cashbox from devices d
|
select d.*, COALESCE(emptybills, 0) + COALESCE(regularbills, 0) as cashbox from devices d
|
||||||
|
|
@ -53,6 +58,11 @@ function toMachineObject (r) {
|
||||||
numberOfRecyclers: r.number_of_recyclers,
|
numberOfRecyclers: r.number_of_recyclers,
|
||||||
version: r.version,
|
version: r.version,
|
||||||
model: r.model,
|
model: r.model,
|
||||||
|
diagnostics: {
|
||||||
|
timestamp: r.diagnostics_timestamp? new Date(r.diagnostics_timestamp) : null,
|
||||||
|
scanTimestamp: r.diagnostics_scan_timestamp? new Date(r.diagnostics_scan_timestamp) : null,
|
||||||
|
frontTimestamp: r.diagnostics_front_timestamp? new Date(r.diagnostics_front_timestamp) : null
|
||||||
|
},
|
||||||
pairedAt: new Date(r.created),
|
pairedAt: new Date(r.created),
|
||||||
lastPing: new Date(r.last_online),
|
lastPing: new Date(r.last_online),
|
||||||
name: r.name,
|
name: r.name,
|
||||||
|
|
@ -362,6 +372,36 @@ function refillUnit (rec) {
|
||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function diagnostics (rec) {
|
||||||
|
const directory = `${OPERATOR_DATA_DIR}/diagnostics/${rec.deviceId}/`
|
||||||
|
const sql = `UPDATE devices
|
||||||
|
SET diagnostics_timestamp = NULL,
|
||||||
|
diagnostics_scan_updated_at = NULL,
|
||||||
|
diagnostics_front_updated_at = NULL
|
||||||
|
WHERE device_id = $1`
|
||||||
|
|
||||||
|
const scanPath = path.join(directory, 'scan.jpg')
|
||||||
|
const frontPath = path.join(directory, 'front.jpg')
|
||||||
|
|
||||||
|
const removeFiles = [scanPath, frontPath].map(filePath => {
|
||||||
|
return fsPromises.unlink(filePath).catch(err => {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
// File doesn't exist, no problem
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(removeFiles)
|
||||||
|
.then(() => db.none(sql, [rec.deviceId]))
|
||||||
|
.then(() => db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
|
||||||
|
{
|
||||||
|
action: 'diagnostics',
|
||||||
|
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
|
||||||
|
}
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
|
||||||
function setMachine (rec, operatorId) {
|
function setMachine (rec, operatorId) {
|
||||||
rec.operatorId = operatorId
|
rec.operatorId = operatorId
|
||||||
switch (rec.action) {
|
switch (rec.action) {
|
||||||
|
|
@ -374,6 +414,7 @@ function setMachine (rec, operatorId) {
|
||||||
case 'restartServices': return restartServices(rec)
|
case 'restartServices': return restartServices(rec)
|
||||||
case 'emptyUnit': return emptyUnit(rec)
|
case 'emptyUnit': return emptyUnit(rec)
|
||||||
case 'refillUnit': return refillUnit(rec)
|
case 'refillUnit': return refillUnit(rec)
|
||||||
|
case 'diagnostics': return diagnostics(rec)
|
||||||
default: throw new Error('No such action: ' + rec.action)
|
default: throw new Error('No such action: ' + rec.action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,6 +477,44 @@ function getNetworkHeartbeatByDevice (deviceId) {
|
||||||
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
|
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDiagnostics (deviceId, images) {
|
||||||
|
const sql = `UPDATE devices
|
||||||
|
SET diagnostics_timestamp = NOW(),
|
||||||
|
diagnostics_scan_updated_at = CASE WHEN $2 THEN NOW() ELSE diagnostics_scan_updated_at END,
|
||||||
|
diagnostics_front_updated_at = CASE WHEN $3 THEN NOW() ELSE diagnostics_front_updated_at END
|
||||||
|
WHERE device_id = $1`
|
||||||
|
|
||||||
|
const directory = `${OPERATOR_DATA_DIR}/diagnostics/${deviceId}/`
|
||||||
|
const { scan, front } = images
|
||||||
|
|
||||||
|
return updatePhotos(scan, front, directory)
|
||||||
|
.then(() => db.none(sql, [deviceId, !!scan, !!front]))
|
||||||
|
.catch(err => logger.error('while running machine diagnostics: ', err))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPhoto (name, data, dir) {
|
||||||
|
if (!data) {
|
||||||
|
logger.error(`Diagnostics error: No data to save for ${name} photo`)
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedImageData = Buffer.from(data, 'base64')
|
||||||
|
const filename = path.join(dir, name)
|
||||||
|
return fsPromises.writeFile(filename, decodedImageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePhotos (scan, front, dir) {
|
||||||
|
const dirname = path.join(dir)
|
||||||
|
_.attempt(() => makeDir.sync(dirname))
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
createPhoto('scan.jpg', scan, dirname),
|
||||||
|
createPhoto('front.jpg', front, dirname)
|
||||||
|
]
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMachineName,
|
getMachineName,
|
||||||
getMachines,
|
getMachines,
|
||||||
|
|
@ -450,5 +529,6 @@ module.exports = {
|
||||||
getConfig,
|
getConfig,
|
||||||
getMachineIds,
|
getMachineIds,
|
||||||
emptyMachineUnits,
|
emptyMachineUnits,
|
||||||
refillMachineUnits
|
refillMachineUnits,
|
||||||
|
updateDiagnostics
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ function machineAction (type, value) {
|
||||||
logger.debug(`Refilling recyclers from machine '${deviceId}' from operator ${operatorId}`)
|
logger.debug(`Refilling recyclers from machine '${deviceId}' from operator ${operatorId}`)
|
||||||
state.refillUnit[operatorId] = { [deviceId]: pid }
|
state.refillUnit[operatorId] = { [deviceId]: pid }
|
||||||
break
|
break
|
||||||
|
case 'diagnostics':
|
||||||
|
logger.debug(`Running diagnostics on machine '${deviceId}' from operator ${operatorId}`)
|
||||||
|
state.diagnostics[operatorId] = { [deviceId]: pid }
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ module.exports = (function () {
|
||||||
restartServicesMap: {},
|
restartServicesMap: {},
|
||||||
emptyUnit: {},
|
emptyUnit: {},
|
||||||
refillUnit: {},
|
refillUnit: {},
|
||||||
|
diagnostics: {},
|
||||||
mnemonic: null
|
mnemonic: null
|
||||||
}
|
}
|
||||||
}())
|
}())
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const typeDef = gql`
|
||||||
paired: Boolean!
|
paired: Boolean!
|
||||||
lastPing: Date
|
lastPing: Date
|
||||||
pairedAt: Date
|
pairedAt: Date
|
||||||
|
diagnostics: Diagnostics
|
||||||
version: String
|
version: String
|
||||||
model: String
|
model: String
|
||||||
cashUnits: CashUnits
|
cashUnits: CashUnits
|
||||||
|
|
@ -24,6 +25,12 @@ const typeDef = gql`
|
||||||
packetLoss: String
|
packetLoss: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Diagnostics {
|
||||||
|
timestamp: Date
|
||||||
|
frontTimestamp: Date
|
||||||
|
scanTimestamp: Date
|
||||||
|
}
|
||||||
|
|
||||||
type CashUnits {
|
type CashUnits {
|
||||||
cashbox: Int
|
cashbox: Int
|
||||||
cassette1: Int
|
cassette1: Int
|
||||||
|
|
@ -81,6 +88,7 @@ const typeDef = gql`
|
||||||
restartServices
|
restartServices
|
||||||
emptyUnit
|
emptyUnit
|
||||||
refillUnit
|
refillUnit
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const cashboxRoutes = require('./routes/cashboxRoutes')
|
||||||
const customerRoutes = require('./routes/customerRoutes')
|
const customerRoutes = require('./routes/customerRoutes')
|
||||||
const logsRoutes = require('./routes/logsRoutes')
|
const logsRoutes = require('./routes/logsRoutes')
|
||||||
const pairingRoutes = require('./routes/pairingRoutes')
|
const pairingRoutes = require('./routes/pairingRoutes')
|
||||||
|
const diagnosticsRoutes = require('./routes/diagnosticsRoutes')
|
||||||
const performanceRoutes = require('./routes/performanceRoutes')
|
const performanceRoutes = require('./routes/performanceRoutes')
|
||||||
const phoneCodeRoutes = require('./routes/phoneCodeRoutes')
|
const phoneCodeRoutes = require('./routes/phoneCodeRoutes')
|
||||||
const pollingRoutes = require('./routes/pollingRoutes')
|
const pollingRoutes = require('./routes/pollingRoutes')
|
||||||
|
|
@ -73,6 +74,7 @@ app.use('/state', stateRoutes)
|
||||||
app.use('/cashbox', cashboxRoutes)
|
app.use('/cashbox', cashboxRoutes)
|
||||||
|
|
||||||
app.use('/network', performanceRoutes)
|
app.use('/network', performanceRoutes)
|
||||||
|
app.use('/diagnostics', diagnosticsRoutes)
|
||||||
|
|
||||||
app.use('/verify_user', verifyUserRoutes)
|
app.use('/verify_user', verifyUserRoutes)
|
||||||
app.use('/verify_transaction', verifyTxRoutes)
|
app.use('/verify_transaction', verifyTxRoutes)
|
||||||
|
|
|
||||||
14
lib/routes/diagnosticsRoutes.js
Normal file
14
lib/routes/diagnosticsRoutes.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const express = require('express')
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
const { updateDiagnostics } = require('../machine-loader')
|
||||||
|
|
||||||
|
function diagnostics (req, res, next) {
|
||||||
|
return updateDiagnostics(req.deviceId, req.body)
|
||||||
|
.then(() => res.status(200).send({ status: 'OK' }))
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post('/', diagnostics)
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
15
migrations/1716561996854-diagnostics.js
Normal file
15
migrations/1716561996854-diagnostics.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
const db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
let sql = [
|
||||||
|
'alter table devices add column diagnostics_timestamp timestampz',
|
||||||
|
'alter table devices add column diagnostics_scan_updated_at timestampz',
|
||||||
|
'alter table devices add column diagnostics_front_updated_at timestampz'
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
import { useLazyQuery, useQuery } from '@apollo/react-hooks'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
import Modal from 'src/components/Modal'
|
||||||
|
import { Button } from 'src/components/buttons'
|
||||||
|
import { H3, P } from 'src/components/typography'
|
||||||
|
import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
|
import { diagnosticsModal } from './MachineActions.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(diagnosticsModal)
|
||||||
|
|
||||||
|
const STATES = {
|
||||||
|
INITIAL: 'INITIAL',
|
||||||
|
EMPTY: 'EMPTY',
|
||||||
|
RUNNING: 'RUNNING',
|
||||||
|
FAILURE: 'FAILURE',
|
||||||
|
FILLED: 'FILLED'
|
||||||
|
}
|
||||||
|
|
||||||
|
const MACHINE = gql`
|
||||||
|
query getMachine($deviceId: ID!) {
|
||||||
|
machine(deviceId: $deviceId) {
|
||||||
|
diagnostics {
|
||||||
|
timestamp
|
||||||
|
frontTimestamp
|
||||||
|
scanTimestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const MACHINE_LOGS = gql`
|
||||||
|
query machineLogsCsv(
|
||||||
|
$deviceId: ID!
|
||||||
|
$limit: Int
|
||||||
|
$from: Date
|
||||||
|
$until: Date
|
||||||
|
$timezone: String
|
||||||
|
) {
|
||||||
|
machineLogsCsv(
|
||||||
|
deviceId: $deviceId
|
||||||
|
limit: $limit
|
||||||
|
from: $from
|
||||||
|
until: $until
|
||||||
|
timezone: $timezone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const createCsv = async ({ machineLogsCsv }) => {
|
||||||
|
console.log(machineLogsCsv)
|
||||||
|
const machineLogs = new Blob([machineLogsCsv], {
|
||||||
|
type: 'text/plain;charset=utf-8'
|
||||||
|
})
|
||||||
|
|
||||||
|
FileSaver.saveAs(machineLogs, 'machineLogs.csv')
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiagnosticsModal = ({ onClose, deviceId, sendAction }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [state, setState] = useState(STATES.INITIAL)
|
||||||
|
const [timestamp, setTimestamp] = useState(null)
|
||||||
|
let timeout = null
|
||||||
|
|
||||||
|
const [fetchSummary, { loading }] = useLazyQuery(MACHINE_LOGS, {
|
||||||
|
onCompleted: data => createCsv(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data, stopPolling, startPolling } = useQuery(MACHINE, {
|
||||||
|
variables: { deviceId }
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return
|
||||||
|
if (!timestamp && !data.machine.diagnostics.timestamp) {
|
||||||
|
stopPolling()
|
||||||
|
setState(STATES.EMPTY)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
timestamp &&
|
||||||
|
data.machine.diagnostics.timestamp &&
|
||||||
|
data.machine.diagnostics.timestamp !== timestamp
|
||||||
|
) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
setTimestamp(data.machine.diagnostics.timestamp)
|
||||||
|
setState(STATES.FILLED)
|
||||||
|
stopPolling()
|
||||||
|
}
|
||||||
|
if (!timestamp && data.machine.diagnostics.timestamp) {
|
||||||
|
setTimestamp(data.machine.diagnostics.timestamp)
|
||||||
|
setState(STATES.FILLED)
|
||||||
|
}
|
||||||
|
}, [data, stopPolling, timeout, timestamp])
|
||||||
|
|
||||||
|
const path = `${URI}/operator-data/diagnostics/${deviceId}/`
|
||||||
|
|
||||||
|
function runDiagnostics() {
|
||||||
|
startPolling(2000)
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setState(STATES.FAILURE)
|
||||||
|
stopPolling()
|
||||||
|
}, 60 * 1000)
|
||||||
|
|
||||||
|
setState(STATES.RUNNING)
|
||||||
|
sendAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
closeOnBackdropClick={true}
|
||||||
|
width={800}
|
||||||
|
height={600}
|
||||||
|
handleClose={onClose}
|
||||||
|
open={true}>
|
||||||
|
{state === STATES.INITIAL && (
|
||||||
|
<div className={classes.message}>
|
||||||
|
<H3>Loading...</H3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === STATES.EMPTY && (
|
||||||
|
<div className={classes.message}>
|
||||||
|
<H3>No diagnostics available</H3>
|
||||||
|
<P>Run diagnostics to generate a report</P>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === STATES.RUNNING && (
|
||||||
|
<div className={classes.message}>
|
||||||
|
<H3>Running Diagnostics...</H3>
|
||||||
|
<P>This page should refresh automatically</P>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === STATES.FAILURE && (
|
||||||
|
<div className={classes.message}>
|
||||||
|
<H3>Failed to run diagnostics</H3>
|
||||||
|
<P>Please try again. If the problem persists, contact support.</P>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === STATES.FILLED && (
|
||||||
|
<div>
|
||||||
|
<div className={classes.photoWrapper}>
|
||||||
|
<div>
|
||||||
|
<H3>Scan</H3>
|
||||||
|
<img
|
||||||
|
className={classes.photo}
|
||||||
|
src={path + 'scan.jpg'}
|
||||||
|
alt="Failure getting photo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<H3>Front</H3>
|
||||||
|
<img
|
||||||
|
className={classes.photo}
|
||||||
|
src={path + 'front.jpg'}
|
||||||
|
alt="Failure getting photo"
|
||||||
|
/>
|
||||||
|
<P></P>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<P>Diagnostics executed at: {timestamp}</P>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<Button
|
||||||
|
disabled={state !== STATES.FILLED || !timestamp}
|
||||||
|
onClick={() => {
|
||||||
|
if (loading) return
|
||||||
|
fetchSummary({
|
||||||
|
variables: {
|
||||||
|
from: timestamp,
|
||||||
|
deviceId,
|
||||||
|
limit: 500
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={classes.downloadLogs}>
|
||||||
|
Download Logs
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={state === STATES.RUNNING}
|
||||||
|
onClick={() => {
|
||||||
|
runDiagnostics()
|
||||||
|
}}>
|
||||||
|
Run Diagnostics
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiagnosticsModal
|
||||||
|
|
@ -15,6 +15,7 @@ import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut do
|
||||||
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.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 { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
||||||
|
|
||||||
|
import DiagnosticsModal from './DiagnosticsModal'
|
||||||
import { machineActionsStyles } from './MachineActions.styles'
|
import { machineActionsStyles } from './MachineActions.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(machineActionsStyles)
|
const useStyles = makeStyles(machineActionsStyles)
|
||||||
|
|
@ -66,6 +67,7 @@ const getState = machineEventsLazy =>
|
||||||
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({})
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [errorMessage, setErrorMessage] = useState(null)
|
const [errorMessage, setErrorMessage] = useState(null)
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -81,6 +83,8 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
|
||||||
preflightOptions
|
preflightOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [simpleMachineAction] = useMutation(MACHINE_ACTION)
|
||||||
|
|
||||||
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
||||||
onError: ({ message }) => {
|
onError: ({ message }) => {
|
||||||
const errorMessage = message ?? 'An error ocurred'
|
const errorMessage = message ?? 'An error ocurred'
|
||||||
|
|
@ -188,7 +192,7 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
|
||||||
{machine.model === 'aveiro' && (
|
{machine.model === 'aveiro' && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
className={classes.inlineChip}
|
className={classes.mr}
|
||||||
Icon={RebootIcon}
|
Icon={RebootIcon}
|
||||||
InverseIcon={RebootReversedIcon}
|
InverseIcon={RebootReversedIcon}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
|
@ -221,7 +225,34 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
|
||||||
Refill Unit
|
Refill Unit
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
|
<ActionButton
|
||||||
|
color="primary"
|
||||||
|
className={classes.mr}
|
||||||
|
Icon={RebootIcon}
|
||||||
|
InverseIcon={RebootReversedIcon}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => {
|
||||||
|
setShowModal(true)
|
||||||
|
}}>
|
||||||
|
Diagnostics
|
||||||
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
{showModal && (
|
||||||
|
<DiagnosticsModal
|
||||||
|
sendAction={() =>
|
||||||
|
simpleMachineAction({
|
||||||
|
variables: {
|
||||||
|
deviceId: machine.deviceId,
|
||||||
|
action: 'diagnostics'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
deviceId={machine.deviceId}
|
||||||
|
onClose={() => {
|
||||||
|
setShowModal(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
open={confirmDialogOpen}
|
open={confirmDialogOpen}
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,35 @@ const machineActionsStyles = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { machineActionsStyles }
|
const diagnosticsModal = {
|
||||||
|
modal: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '1em'
|
||||||
|
},
|
||||||
|
photo: {
|
||||||
|
width: 350
|
||||||
|
},
|
||||||
|
photoWrapper: {
|
||||||
|
marginTop: spacer * 3,
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [['auto', 0, spacer * 3, 0]]
|
||||||
|
},
|
||||||
|
downloadLogs: {
|
||||||
|
margin: [['auto', spacer, 0, 'auto']]
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
margin: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { machineActionsStyles, diagnosticsModal }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue