Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add machine physical location #1394

Open
wants to merge 5 commits into
base: future-stuff-old-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 79 additions & 9 deletions lib/machine-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ function toMachineObject (r) {
pairedAt: new Date(r.created),
lastPing: new Date(r.last_online),
name: r.name,
paired: r.paired
paired: r.paired,
location: r.machine_location
// TODO: we shall start using this JSON field at some point
// location: r.location,
}
Expand Down Expand Up @@ -85,13 +86,21 @@ function addName (pings, events, config) {
}

function getMachineNames (config) {
return Promise.all([getMachines(), getConfig(config), getNetworkHeartbeat(), getNetworkPerformance()])
.then(([rawMachines, config, heartbeat, performance]) => Promise.all(
[rawMachines, checkPings(rawMachines), dbm.machineEvents(), config, heartbeat, performance]
return Promise.all([getMachines(), getConfig(config), getNetworkHeartbeat(), getNetworkPerformance(), getMachineLocations()])
.then(([rawMachines, config, heartbeat, performance, locations]) => Promise.all(
[rawMachines, checkPings(rawMachines), dbm.machineEvents(), config, heartbeat, performance, locations]
))
.then(([rawMachines, pings, events, config, heartbeat, performance]) => {
.then(([rawMachines, pings, events, config, heartbeat, performance, locations]) => {
const mergeByDeviceId = (x, y) => _.values(_.merge(_.keyBy('deviceId', x), _.keyBy('deviceId', y)))
const machines = mergeByDeviceId(mergeByDeviceId(rawMachines, heartbeat), performance)
const mergeByLocationId = (x, y) => _.map(it => {
if (_.isNil(it.location)) return it
return { ...it, location: _.find(ite => it.location === ite.id, y) }
}, x)
const machines = _.flow([
x => mergeByDeviceId(x, heartbeat),
x => mergeByDeviceId(x, performance),
x => mergeByLocationId(x, locations)
])(rawMachines)

return machines.map(addName(pings, events, config))
})
Expand Down Expand Up @@ -121,13 +130,18 @@ function getMachine (machineId, config) {
})

return Promise.all([queryMachine, dbm.machineEvents(), config, getNetworkHeartbeatByDevice(machineId), getNetworkPerformanceByDevice(machineId)])
.then(([machine, events, config, heartbeat, performance]) => {
.then(([machine, events, config, heartbeat, performance]) => Promise.all([machine, events, config, heartbeat, performance, getMachineLocation(machine.location)]))
.then(([machine, events, config, heartbeat, performance, location]) => {
const pings = checkPings([machine])
const mergedMachine = {
...machine,
responseTime: _.get('responseTime', heartbeat),
packetLoss: _.get('packetLoss', heartbeat),
downloadSpeed: _.get('downloadSpeed', performance),
downloadSpeed: _.get('downloadSpeed', performance)
}

if (!_.isNil(location) && !_.isEmpty(location)) {
mergedMachine.location = location
}

return addName(pings, events, config)(mergedMachine)
Expand Down Expand Up @@ -204,6 +218,9 @@ function setMachine (rec, operatorId) {
case 'reboot': return reboot(rec)
case 'shutdown': return shutdown(rec)
case 'restartServices': return restartServices(rec)
case 'editLocation': return editMachineLocation(rec.location, rec.deviceId)
case 'deleteLocation': return deleteMachineLocation(rec.location.id)
case 'createLocation': return createMachineLocation(rec.location, rec.deviceId)
default: throw new Error('No such action: ' + rec.action)
}
}
Expand Down Expand Up @@ -266,6 +283,53 @@ function getNetworkHeartbeatByDevice (deviceId) {
.then(res => _.mapKeys(_.camelCase, _.find(it => it.device_id === deviceId, res)))
}

function getMachineLocations () {
const sql = `SELECT * FROM machine_locations`
return db.any(sql)
.then(_.map(_.mapKeys(_.camelCase)))
}

function getMachineLocation (locationId) {
const sql = `SELECT * FROM machine_locations WHERE id = $1`
return db.oneOrNone(sql, [locationId])
.then(_.mapKeys(_.camelCase))
}

function editMachineLocation ({ id, label, addressLine1, addressLine2, city, state, zipCode, country }, deviceId) {
return db.tx(t => {
const q1 = t.none(`UPDATE machine_locations SET label = $1, address_line_1 = $2, address_line_2 = $3, city = $4, state = $5, zip_code = $6, country = $7 WHERE id = $8`, [label, addressLine1, addressLine2, city, state, zipCode, country, id])
const q2 = t.none(`UPDATE devices SET machine_location = $1 WHERE device_id = $2`, [id, deviceId])

return t.batch(_.isNil(deviceId) ? [q1] : [q1, q2])
})
}

function createMachineLocation ({ id, label, addressLine1, addressLine2, city, state, zipCode, country }, deviceId) {
const _id = _.isEmpty(id) ? uuid.v4() : id
return db.tx(t => {
const q1 = t.none(`INSERT INTO machine_locations (id, label, address_line_1, address_line_2, city, state, zip_code, country) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT DO NOTHING`, [_id, label, addressLine1, addressLine2, city, state, zipCode, country])
const q2 = t.none(`UPDATE devices SET machine_location = $1 WHERE device_id = $2`, [_id, deviceId])

return t.batch(_.isNil(deviceId) ? [q1] : [q1, q2])
})
.then(() => _id)
}

function deleteMachineLocation (id) {
return db.tx(t => {
const q1 = t.none(`UPDATE devices SET machine_location = NULL WHERE machine_location = $1`, [id])
const q2 = t.none(`UPDATE pairing_tokens SET machine_location = NULL WHERE machine_location = $1`, [id])
const q3 = t.none(`DELETE FROM machine_locations WHERE id = $1`, [id])

return t.batch([q1, q2, q3])
})
}

function assignLocation (machineId, locationId) {
const sql = `UPDATE devices SET machine_location = $1 WHERE device_id = $2`
return db.none(sql, [locationId, machineId])
}

module.exports = {
getMachineName,
getMachines,
Expand All @@ -277,5 +341,11 @@ module.exports = {
updateNetworkHeartbeat,
getNetworkPerformance,
getNetworkHeartbeat,
getConfig
getConfig,
getMachineLocations,
getMachineLocation,
editMachineLocation,
createMachineLocation,
deleteMachineLocation,
assignLocation
}
7 changes: 4 additions & 3 deletions lib/new-admin/graphql/resolvers/machine.resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const resolvers = {
Query: {
machines: () => machineLoader.getMachineNames(),
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId),
unpairedMachines: () => machineLoader.getUnpairedMachines()
unpairedMachines: () => machineLoader.getUnpairedMachines(),
machineLocations: () => machineLoader.getMachineLocations()
},
Mutation: {
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context]) =>
machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context)
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName, location }, context]) =>
machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName, location }, context)
}
}

Expand Down
4 changes: 3 additions & 1 deletion lib/new-admin/graphql/resolvers/pairing.resolver.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const pairing = require('../../services/pairing')
const machine = require('../../../machine-loader')

const resolvers = {
Mutation: {
createPairingTotem: (parent, { name }, { res }, info) => pairing.totem(name, res.locals.operatorId)
createPairingTotem: (parent, { name, location }, { res }, info) => machine.createMachineLocation(location)
.then(locationId => pairing.totem(name, res.locals.operatorId, locationId))
}
}

Expand Down
18 changes: 17 additions & 1 deletion lib/new-admin/graphql/types/machine.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ const typeDef = gql`
downloadSpeed: String
responseTime: String
packetLoss: String
location: MachineLocation
}

type MachineLocation {
id: ID
label: String
addressLine1: String
addressLine2: String
city: String
state: String
zipCode: String
country: String
}

type UnpairedMachine {
Expand Down Expand Up @@ -55,16 +67,20 @@ const typeDef = gql`
reboot
shutdown
restartServices
editLocation
deleteLocation
createLocation
}

type Query {
machines: [Machine] @auth
machine(deviceId: ID!): Machine @auth
unpairedMachines: [UnpairedMachine!]! @auth
machineLocations: [MachineLocation] @auth
}

type Mutation {
machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, cassette3: Int, cassette4: Int, newName: String): Machine @auth
machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, cassette3: Int, cassette4: Int, newName: String, location: JSONObject): Machine @auth
}
`

Expand Down
2 changes: 1 addition & 1 deletion lib/new-admin/graphql/types/pairing.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { gql } = require('apollo-server-express')

const typeDef = gql`
type Mutation {
createPairingTotem(name: String!): String @auth
createPairingTotem(name: String!, location: JSONObject!): String @auth
}
`

Expand Down
6 changes: 3 additions & 3 deletions lib/new-admin/services/machines.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ function getMachine (machineId) {
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
}

function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context) {
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName, location }, context) {
const operatorId = context.res.locals.operatorId
return getMachine(deviceId)
.then(machine => {
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
return machine
})
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2, cassette3, cassette4], newName }, operatorId))
.then(getMachine(deviceId))
.then(() => machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2, cassette3, cassette4], newName, location }, operatorId))
.then(() => getMachine(deviceId))
}

module.exports = { machineAction }
6 changes: 3 additions & 3 deletions lib/new-admin/services/pairing.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ const encodeTotem = _.flow(
csexp.toCanonical,
)

function totem (name, operatorId) {
function totem (name, operatorId, locationId) {
return readFile(CA_PATH)
.then(data => {
const caHash = crypto.createHash('sha256').update(data).digest('hex')
const token = crypto.randomBytes(32)
const hexToken = token.toString('hex')
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
const sql = 'insert into pairing_tokens (token, name, machine_location) values ($1, $3, $4), ($2, $3, $4)'

return db.none(sql, [hexToken, caHexToken, name])
return db.none(sql, [hexToken, caHexToken, name, locationId])
.then(() => encodeTotem({
version: PAIRING_VERSION,
hostname: HOSTNAME,
Expand Down
6 changes: 3 additions & 3 deletions lib/pairing.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DEFAULT_NUMBER_OF_CASSETTES = 2
function pullToken (token) {
const sql = `delete from pairing_tokens
where token=$1
returning name, created < now() - interval '1 hour' as expired`
returning name, created < now() - interval '1 hour' as expired, machine_location`
return db.one(sql, [token])
}

Expand All @@ -41,11 +41,11 @@ function pair (token, deviceId, machineModel, numOfCassettes = DEFAULT_NUMBER_OF
.then(r => {
if (r.expired) return false

const insertSql = `insert into devices (device_id, name, number_of_cassettes) values ($1, $2, $3)
const insertSql = `insert into devices (device_id, name, number_of_cassettes, machine_location) values ($1, $2, $3, $4)
on conflict (device_id)
do update set paired=TRUE, display=TRUE`

return db.none(insertSql, [deviceId, r.name, numOfCassettes])
return db.none(insertSql, [deviceId, r.name, numOfCassettes, r.machine_location])
.then(() => true)
})
.catch(err => {
Expand Down
25 changes: 25 additions & 0 deletions migrations/1664411947220-machine-location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var db = require('./db')

exports.up = function (next) {
var sql = [
`CREATE TABLE machine_locations (
id UUID PRIMARY KEY,
label TEXT NOT NULL,
address_line_1 TEXT NOT NULL,
address_line_2 TEXT,
city TEXT NOT NULL,
state TEXT NOT NULL,
zip_code TEXT NOT NULL,
country TEXT NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now()
)`,
`ALTER TABLE devices ADD COLUMN machine_location UUID REFERENCES machine_locations(id)`,
`ALTER TABLE pairing_tokens ADD COLUMN machine_location UUID REFERENCES machine_locations(id)`
]

db.multi(sql, next)
}

exports.down = function (next) {
next()
}
8 changes: 7 additions & 1 deletion new-lamassu-admin/src/components/ConfirmDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ export const ConfirmDialog = memo(
onDismissed()
}

const innerOnConfirmed = value => {
setValue('')
setError(false)
onConfirmed(value)
}

const isOnErrorState =
(!saveButtonAlwaysEnabled && toBeConfirmed !== value) || value === ''

Expand Down Expand Up @@ -122,7 +128,7 @@ export const ConfirmDialog = memo(
<Button
color="green"
disabled={isOnErrorState}
onClick={() => onConfirmed(value)}>
onClick={() => innerOnConfirmed(value)}>
Confirm
</Button>
</DialogActions>
Expand Down
Loading