Feat: warn admin before restarting services

This commit is contained in:
Cesar 2020-12-15 14:58:11 +00:00 committed by Josh Harvey
parent a432d913be
commit 6994303069
6 changed files with 126 additions and 8 deletions

View file

@ -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])

View file

@ -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(),

View file

@ -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'

View file

@ -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

View file

@ -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)
}}
/>

View file

@ -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
}
}