From b0e1e579aad454d40ba2d18ab62188291afdf9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 28 Sep 2022 02:41:44 +0100 Subject: [PATCH] feat: allow for machine enabling/disabling --- lib/graphql/resolvers.js | 3 +- lib/graphql/types.js | 1 + lib/machine-loader.js | 35 ++++++++++++++++--- lib/middlewares/populateSettings.js | 6 ++++ lib/new-admin/graphql/types/machine.type.js | 3 ++ lib/notifier/codes.js | 2 ++ lib/notifier/index.js | 4 ++- lib/plugins.js | 7 ++-- lib/routes/pollingRoutes.js | 4 ++- .../1664325129303-allow-machine-disabling.js | 13 +++++++ .../machineActions/MachineActions.js | 22 +++++++++++- .../src/pages/Maintenance/MachineStatus.js | 1 + 12 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 migrations/1664325129303-allow-machine-disabling.js 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`} +