Feat: warn admin before restarting services
This commit is contained in:
parent
a432d913be
commit
6994303069
6 changed files with 126 additions and 8 deletions
|
|
@ -9,7 +9,7 @@ const dbm = require('./postgresql_interface')
|
|||
const configManager = require('./new-config-manager')
|
||||
const settingsLoader = require('./new-settings-loader')
|
||||
|
||||
module.exports = {getMachineName, getMachines, getMachineNames, setMachine}
|
||||
module.exports = {getMachineName, getMachines, getMachine, getMachineNames, setMachine}
|
||||
|
||||
function getMachines () {
|
||||
return db.any('select * from devices where display=TRUE order by created')
|
||||
|
|
@ -88,6 +88,11 @@ function getMachineName (machineId) {
|
|||
.then(it => it.name)
|
||||
}
|
||||
|
||||
function getMachine (machineId) {
|
||||
const sql = 'select * from devices where device_id=$1'
|
||||
return db.oneOrNone(sql, [machineId]).then(res => _.mapKeys(_.camelCase)(res))
|
||||
}
|
||||
|
||||
function renameMachine (rec) {
|
||||
const sql = 'update devices set name=$1 where device_id=$2'
|
||||
return db.none(sql, [rec.newName, rec.deviceId])
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const logs = require('../../logs')
|
|||
const settingsLoader = require('../../new-settings-loader')
|
||||
const tokenManager = require('../../token-manager')
|
||||
const blacklist = require('../../blacklist')
|
||||
const machineEventsByIdBatch = require("../../postgresql_interface").machineEventsByIdBatch
|
||||
|
||||
const serverVersion = require('../../../package.json').version
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ const typeDefs = gql`
|
|||
cassette1: Int
|
||||
cassette2: Int
|
||||
statuses: [MachineStatus]
|
||||
latestEvent: MachineEvent
|
||||
}
|
||||
|
||||
type Customer {
|
||||
|
|
@ -220,6 +222,16 @@ const typeDefs = gql`
|
|||
address: String!
|
||||
}
|
||||
|
||||
type MachineEvent {
|
||||
id: ID
|
||||
deviceId: String
|
||||
eventType: String
|
||||
note: String
|
||||
created: Date
|
||||
age: Float
|
||||
deviceTime: Date
|
||||
}
|
||||
|
||||
type Query {
|
||||
countries: [Country]
|
||||
currencies: [Currency]
|
||||
|
|
@ -227,6 +239,7 @@ const typeDefs = gql`
|
|||
accountsConfig: [AccountConfig]
|
||||
cryptoCurrencies: [CryptoCurrency]
|
||||
machines: [Machine]
|
||||
machine(deviceId: ID!): Machine
|
||||
customers: [Customer]
|
||||
customer(customerId: ID!): Customer
|
||||
machineLogs(deviceId: ID!, from: Date, until: Date, limit: Int, offset: Int): [MachineLog]
|
||||
|
|
@ -267,6 +280,9 @@ const typeDefs = gql`
|
|||
`
|
||||
|
||||
const transactionsLoader = new DataLoader(ids => transactions.getCustomerTransactionsBatch(ids))
|
||||
const machineEventsLoader = new DataLoader(ids => {
|
||||
return machineEventsByIdBatch(ids)
|
||||
}, { cache: false })
|
||||
|
||||
const notify = () => got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
|
|
@ -278,6 +294,9 @@ const resolvers = {
|
|||
Customer: {
|
||||
transactions: parent => transactionsLoader.load(parent.id)
|
||||
},
|
||||
Machine: {
|
||||
latestEvent: parent => machineEventsLoader.load(parent.deviceId)
|
||||
},
|
||||
Query: {
|
||||
countries: () => countries,
|
||||
currencies: () => currencies,
|
||||
|
|
@ -285,6 +304,7 @@ const resolvers = {
|
|||
accountsConfig: () => accountsConfig,
|
||||
cryptoCurrencies: () => coins,
|
||||
machines: () => machineLoader.getMachineNames(),
|
||||
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId),
|
||||
customers: () => customers.getCustomersList(),
|
||||
customer: (...[, { customerId }]) => customers.getCustomerById(customerId),
|
||||
funding: () => funding.getFunding(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const _ = require('lodash/fp')
|
||||
const db = require('./db')
|
||||
const pgp = require('pg-promise')()
|
||||
|
||||
function getInsertQuery (tableName, fields) {
|
||||
// outputs string like: '$1, $2, $3...' with proper No of items
|
||||
|
|
@ -48,6 +50,16 @@ exports.machineEvent = function machineEvent (rec) {
|
|||
.then(() => db.none(deleteSql))
|
||||
}
|
||||
|
||||
exports.machineEventsByIdBatch = function machineEventsByIdBatch (machineIds) {
|
||||
const formattedIds = _.map(pgp.as.text, machineIds).join(',')
|
||||
const sql = `SELECT *, (EXTRACT(EPOCH FROM (now() - created))) * 1000 AS age FROM machine_events WHERE device_id IN ($1^) ORDER BY age ASC LIMIT 1`
|
||||
return db.any(sql, [formattedIds]).then(res => {
|
||||
const events = _.map(_.mapKeys(_.camelCase))(res)
|
||||
const eventMap = _.groupBy('deviceId', events)
|
||||
return machineIds.map(id => _.prop([0], eventMap[id]))
|
||||
})
|
||||
}
|
||||
|
||||
exports.machineEvents = function machineEvents () {
|
||||
const sql = 'SELECT *, (EXTRACT(EPOCH FROM (now() - created))) * 1000 AS age FROM machine_events'
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export const ConfirmDialog = memo(
|
|||
onConfirmed,
|
||||
onDissmised,
|
||||
initialValue = '',
|
||||
disabled = false,
|
||||
...props
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
|
@ -101,6 +102,7 @@ export const ConfirmDialog = memo(
|
|||
<DialogContent className={classes.dialogContent}>
|
||||
{message && <P>{message}</P>}
|
||||
<TextInput
|
||||
disabled={disabled}
|
||||
label={confirmationMessage}
|
||||
name="confirm-input"
|
||||
autoFocus
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useMutation } from '@apollo/react-hooks'
|
||||
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, { useState, useEffect } from 'react'
|
||||
|
||||
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
||||
import { Status } from 'src/components/Status'
|
||||
|
|
@ -32,6 +32,16 @@ const MACHINE_ACTION = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const MACHINE = gql`
|
||||
query getMachine($deviceId: ID!) {
|
||||
machine(deviceId: $deviceId) {
|
||||
latestEvent {
|
||||
note
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const supportArtices = [
|
||||
{
|
||||
// Default article for non-maped statuses
|
||||
|
|
@ -43,6 +53,24 @@ const supportArtices = [
|
|||
// 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)
|
||||
|
||||
|
|
@ -69,10 +97,50 @@ const Item = ({ children, ...props }) => (
|
|||
)
|
||||
|
||||
const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
||||
const [action, setAction] = useState(null)
|
||||
const [action, setAction] = useState({ command: null })
|
||||
const [errorMessage, setErrorMessage] = useState(null)
|
||||
const classes = useMDStyles()
|
||||
|
||||
const [
|
||||
fetchMachineEvents,
|
||||
{ loading: loadingEvents, data: machineEventsLazy }
|
||||
] = useLazyQuery(MACHINE, {
|
||||
variables: {
|
||||
deviceId: machine.deviceId
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (action.command === 'restartServices') {
|
||||
fetchMachineEvents()
|
||||
}
|
||||
}, [action.command, fetchMachineEvents])
|
||||
|
||||
useEffect(() => {
|
||||
if (machineEventsLazy && action.command === 'restartServices') {
|
||||
const state = JSON.parse(
|
||||
machineEventsLazy.machine.latestEvent?.note ?? '{"state": null}'
|
||||
).state
|
||||
if (!isStaticState(state)) {
|
||||
setAction(action => ({
|
||||
...action,
|
||||
message: (
|
||||
<span className={classes.warning}>
|
||||
A user may be in the middle of a transaction and they could lose
|
||||
their funds if you continue.
|
||||
</span>
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
// clear message from object when state goes from not static to static
|
||||
setAction(action => ({
|
||||
...action,
|
||||
message: null
|
||||
}))
|
||||
}
|
||||
}
|
||||
}, [action.command, classes.warning, machineEventsLazy])
|
||||
|
||||
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
||||
onError: ({ message }) => {
|
||||
const errorMessage = message ?? 'An error ocurred'
|
||||
|
|
@ -80,11 +148,12 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
},
|
||||
onCompleted: () => {
|
||||
onActionSuccess && onActionSuccess()
|
||||
setAction(null)
|
||||
setAction({ command: null })
|
||||
}
|
||||
})
|
||||
|
||||
const confirmDialogOpen = Boolean(action)
|
||||
const confirmDialogOpen = Boolean(action.command)
|
||||
const disabled = !!(action?.command === 'restartServices' && loadingEvents)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -127,6 +196,7 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
className={classes.separator}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
disabled={disabled}
|
||||
open={confirmDialogOpen}
|
||||
title={`${action?.display} this machine?`}
|
||||
errorMessage={errorMessage}
|
||||
|
|
@ -145,7 +215,7 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
})
|
||||
}}
|
||||
onDissmised={() => {
|
||||
setAction(null)
|
||||
setAction({ command: null })
|
||||
setErrorMessage(null)
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import {
|
|||
detailsRowStyles,
|
||||
labelStyles
|
||||
} from 'src/pages/Transactions/Transactions.styles'
|
||||
import { spacer, comet, primaryColor, fontSize4 } from 'src/styling/variables'
|
||||
import {
|
||||
spacer,
|
||||
comet,
|
||||
primaryColor,
|
||||
fontSize4,
|
||||
errorColor
|
||||
} from 'src/styling/variables'
|
||||
|
||||
const machineDetailsStyles = {
|
||||
...detailsRowStyles,
|
||||
|
|
@ -58,6 +64,9 @@ const machineDetailsStyles = {
|
|||
marginRight: 60,
|
||||
marginLeft: 'auto',
|
||||
background: fade(comet, 0.5)
|
||||
},
|
||||
warning: {
|
||||
color: errorColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue