diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js
index 9c72d5dbb..1908e220b 100644
--- a/lib/graphql/resolvers.js
+++ b/lib/graphql/resolvers.js
@@ -206,13 +206,14 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
_.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid),
_.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid),
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
+ _.set('isEnabled', pq.machine.isEnabled),
)(pq)
}
const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, operatorId, pid, settings }, info) =>
plugins(settings, deviceId)
- .pollQueries()
+ .pollQueries(deviceId)
.then(pq => ({
static: staticConfig({
currentConfigVersion,
diff --git a/lib/graphql/types.js b/lib/graphql/types.js
index 921268878..8e6a9200e 100644
--- a/lib/graphql/types.js
+++ b/lib/graphql/types.js
@@ -142,6 +142,7 @@ type DynamicConfig {
reboot: Boolean!
shutdown: Boolean!
restartServices: Boolean!
+ isEnabled: Boolean!
}
type Configs {
diff --git a/lib/machine-loader.js b/lib/machine-loader.js
index ec1f48a2b..1038d8332 100644
--- a/lib/machine-loader.js
+++ b/lib/machine-loader.js
@@ -12,11 +12,12 @@ const configManager = require('./new-config-manager')
const settingsLoader = require('./new-settings-loader')
const notifierUtils = require('./notifier/utils')
const notifierQueries = require('./notifier/queries')
-const { ApolloError } = require('apollo-server-errors');
+const { ApolloError } = require('apollo-server-errors')
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
const stuckStatus = { label: 'Stuck', type: 'error' }
+const disabledStatus = { label: 'Disabled by operator', type: 'warning' }
function toMachineObject (r) {
return {
@@ -32,7 +33,8 @@ function toMachineObject (r) {
pairedAt: new Date(r.created),
lastPing: new Date(r.last_online),
name: r.name,
- paired: r.paired
+ paired: r.paired,
+ isEnabled: r.is_enabled
// TODO: we shall start using this JSON field at some point
// location: r.location,
}
@@ -59,7 +61,9 @@ function getConfig (defaultConfig) {
return settingsLoader.loadLatest().config
}
-const getStatus = (ping, stuck) => {
+const getStatus = (ping, stuck, isEnabled) => {
+ if (!isEnabled) return disabledStatus
+
if (ping && ping.age) return unresponsiveStatus
if (stuck && stuck.age) return stuckStatus
@@ -76,7 +80,8 @@ function addName (pings, events, config) {
const statuses = [
getStatus(
_.first(pings[machine.deviceId]),
- _.first(checkStuckScreen(events, machine))
+ _.first(checkStuckScreen(events, machine)),
+ machine.isEnabled
)
]
@@ -175,6 +180,26 @@ function reboot (rec) {
)])
}
+function disable (rec) {
+ return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
+ {
+ action: 'disable',
+ value: _.pick(['deviceId', 'operatorId', 'action'], rec)
+ }
+ )])
+ .then(() => db.none(`UPDATE devices SET is_enabled = false WHERE device_id = $1`, [rec.deviceId]))
+}
+
+function enable (rec) {
+ return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
+ {
+ action: 'enable',
+ value: _.pick(['deviceId', 'operatorId', 'action'], rec)
+ }
+ )])
+ .then(() => db.none(`UPDATE devices SET is_enabled = true WHERE device_id = $1`, [rec.deviceId]))
+}
+
function shutdown (rec) {
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
{
@@ -202,6 +227,8 @@ function setMachine (rec, operatorId) {
case 'setCassetteBills': return setCassetteBills(rec)
case 'unpair': return unpair(rec)
case 'reboot': return reboot(rec)
+ case 'disable': return disable(rec)
+ case 'enable': return enable(rec)
case 'shutdown': return shutdown(rec)
case 'restartServices': return restartServices(rec)
default: throw new Error('No such action: ' + rec.action)
diff --git a/lib/middlewares/populateSettings.js b/lib/middlewares/populateSettings.js
index 05245a03d..6314aab5a 100644
--- a/lib/middlewares/populateSettings.js
+++ b/lib/middlewares/populateSettings.js
@@ -40,6 +40,12 @@ function machineAction (type, value) {
logger.debug(`Restarting services of machine '${deviceId}' from operator ${operatorId}`)
state.restartServicesMap[operatorId] = { [deviceId]: pid }
break
+ case 'disable':
+ logger.debug(`Disabling machine '${deviceId}' from operator ${operatorId}`)
+ break
+ case 'enable':
+ logger.debug(`Enabling machine '${deviceId}' from operator ${operatorId}`)
+ break
default:
break
}
diff --git a/lib/new-admin/graphql/types/machine.type.js b/lib/new-admin/graphql/types/machine.type.js
index da409d7b8..a15cd146c 100644
--- a/lib/new-admin/graphql/types/machine.type.js
+++ b/lib/new-admin/graphql/types/machine.type.js
@@ -25,6 +25,7 @@ const typeDef = gql`
downloadSpeed: String
responseTime: String
packetLoss: String
+ isEnabled: Boolean
}
type UnpairedMachine {
@@ -53,6 +54,8 @@ const typeDef = gql`
setCassetteBills
unpair
reboot
+ disable
+ enable
shutdown
restartServices
}
diff --git a/lib/notifier/codes.js b/lib/notifier/codes.js
index 10a68be44..842509885 100644
--- a/lib/notifier/codes.js
+++ b/lib/notifier/codes.js
@@ -2,6 +2,7 @@ const T = require('../time')
const PING = 'PING'
const STALE = 'STALE'
+const DISABLED = 'DISABLED'
const LOW_CRYPTO_BALANCE = 'LOW_CRYPTO_BALANCE'
const HIGH_CRYPTO_BALANCE = 'HIGH_CRYPTO_BALANCE'
const CASH_BOX_FULL = 'CASH_BOX_FULL'
@@ -35,6 +36,7 @@ const NOTIFICATION_TYPES = {
module.exports = {
PING,
STALE,
+ DISABLED,
LOW_CRYPTO_BALANCE,
HIGH_CRYPTO_BALANCE,
CASH_BOX_FULL,
diff --git a/lib/notifier/index.js b/lib/notifier/index.js
index 6afba8235..1aa4e59cd 100644
--- a/lib/notifier/index.js
+++ b/lib/notifier/index.js
@@ -10,7 +10,7 @@ const notificationCenter = require('./notificationCenter')
const utils = require('./utils')
const emailFuncs = require('./email')
const smsFuncs = require('./sms')
-const { STALE, STALE_STATE } = require('./codes')
+const { DISABLED, STALE, STALE_STATE } = require('./codes')
function buildMessage (alerts, notifications) {
const smsEnabled = utils.isActive(notifications.sms)
@@ -123,6 +123,8 @@ function checkStuckScreen (deviceEvents, machine) {
const age = Math.floor(lastEvent.age)
const machineName = machine.name
+
+ if (!machine.isEnabled) return [{ code: DISABLED, state, age, machineName }]
if (age > STALE_STATE) return [{ code: STALE, state, age, machineName }]
return []
diff --git a/lib/plugins.js b/lib/plugins.js
index f38931a6e..0f6402e7e 100644
--- a/lib/plugins.js
+++ b/lib/plugins.js
@@ -228,7 +228,7 @@ function plugins (settings, deviceId, schema) {
}
}
- function pollQueries () {
+ function pollQueries (deviceId) {
const localeConfig = configManager.getLocale(deviceId, settings.config)
const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies
@@ -243,6 +243,7 @@ function plugins (settings, deviceId, schema) {
fetchCurrentConfigVersion(),
millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)),
loyalty.getNumberOfAvailablePromoCodes(),
+ machineLoader.getMachine(deviceId),
Promise.all(supportsBatchingPromise),
Promise.all(tickerPromises),
Promise.all(balancePromises),
@@ -253,6 +254,7 @@ function plugins (settings, deviceId, schema) {
configVersion,
timezone,
numberOfAvailablePromoCodes,
+ machine,
batchableCoins,
tickers,
balances,
@@ -278,7 +280,8 @@ function plugins (settings, deviceId, schema) {
coins,
configVersion,
areThereAvailablePromoCodes: numberOfAvailablePromoCodes > 0,
- timezone
+ timezone,
+ machine
}
})
}
diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js
index e0f4b4c2e..c8342c046 100644
--- a/lib/routes/pollingRoutes.js
+++ b/lib/routes/pollingRoutes.js
@@ -83,7 +83,7 @@ function poll (req, res, next) {
return Promise.all([
pi.recordPing(deviceTime, machineVersion, machineModel),
- pi.pollQueries(),
+ pi.pollQueries(deviceId),
buildTriggers(configManager.getTriggers(settings.config)),
configManager.getTriggersAutomation(getCustomInfoRequests(true), settings.config),
])
@@ -92,6 +92,7 @@ function poll (req, res, next) {
const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid
const restartServices = pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid
const langs = localeConfig.languages
+ const isEnabled = results.machine.isEnabled
const locale = {
fiatCode: localeConfig.fiatCurrency,
@@ -111,6 +112,7 @@ function poll (req, res, next) {
enablePaperWalletOnly,
twoWayMode: cashOutConfig.active,
zeroConfLimits,
+ isEnabled,
reboot,
shutdown,
restartServices,
diff --git a/migrations/1664325129303-allow-machine-disabling.js b/migrations/1664325129303-allow-machine-disabling.js
new file mode 100644
index 000000000..2962bb62c
--- /dev/null
+++ b/migrations/1664325129303-allow-machine-disabling.js
@@ -0,0 +1,13 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `ALTER TABLE devices ADD COLUMN is_enabled BOOLEAN DEFAULT true`
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/new-lamassu-admin/src/components/machineActions/MachineActions.js b/new-lamassu-admin/src/components/machineActions/MachineActions.js
index 06c1d6b19..051779da1 100644
--- a/new-lamassu-admin/src/components/machineActions/MachineActions.js
+++ b/new-lamassu-admin/src/components/machineActions/MachineActions.js
@@ -54,7 +54,8 @@ const isStaticState = machineState => {
'unpaired',
'maintenance',
'virgin',
- 'wifiList'
+ 'wifiList',
+ 'disabled'
]
return staticStates.includes(machineState)
}
@@ -155,6 +156,25 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
}>
Reboot
+
+ !machine.isEnabled
+ ? setAction({
+ command: 'enable',
+ display: 'Enable'
+ })
+ : setAction({
+ command: 'disable',
+ display: 'Disable'
+ })
+ }>
+ {!machine.isEnabled ? `Enable` : `Disable`}
+