Merge branch 'dev' into releases/v10.1
This commit is contained in:
commit
320e76fdae
393 changed files with 11183 additions and 31834 deletions
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
nodejs 14
|
||||||
|
|
@ -25,8 +25,8 @@ EXPOSE 3000
|
||||||
ENTRYPOINT [ "/lamassu-server/bin/lamassu-server-entrypoint.sh" ]
|
ENTRYPOINT [ "/lamassu-server/bin/lamassu-server-entrypoint.sh" ]
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.14 AS build-ui
|
FROM node:22-alpine AS build-ui
|
||||||
RUN apk add --no-cache nodejs npm git curl build-base python3
|
RUN apk add --no-cache npm git curl build-base python3
|
||||||
|
|
||||||
COPY ["new-lamassu-admin/package.json", "new-lamassu-admin/package-lock.json", "./"]
|
COPY ["new-lamassu-admin/package.json", "new-lamassu-admin/package-lock.json", "./"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,61 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const { addressDetector } = require('@lamassu/coins')
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const notifierQueries = require('./notifier/queries')
|
const notifierQueries = require('./notifier/queries')
|
||||||
|
|
||||||
// Get all blacklist rows from the DB "blacklist" table that were manually inserted by the operator
|
const getBlacklist = () =>
|
||||||
const getBlacklist = () => {
|
db.any(
|
||||||
return db.any(`SELECT * FROM blacklist`).then(res =>
|
`SELECT blacklist.address AS address, blacklist_messages.content AS blacklistMessage
|
||||||
res.map(item => ({
|
FROM blacklist JOIN blacklist_messages
|
||||||
cryptoCode: item.crypto_code,
|
ON blacklist.blacklist_message_id = blacklist_messages.id`
|
||||||
address: item.address
|
|
||||||
}))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deleteFromBlacklist = address => {
|
||||||
|
const sql = `DELETE FROM blacklist WHERE address = $1`
|
||||||
|
notifierQueries.clearBlacklistNotification(address)
|
||||||
|
return db.none(sql, [address])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete row from blacklist table by crypto code and address
|
const isValidAddress = address => {
|
||||||
const deleteFromBlacklist = (cryptoCode, address) => {
|
try {
|
||||||
const sql = `DELETE FROM blacklist WHERE crypto_code = $1 AND address = $2`
|
return !_.isEmpty(addressDetector.getSupportedCoinsForAddress(address).matches)
|
||||||
notifierQueries.clearBlacklistNotification(cryptoCode, address)
|
} catch {
|
||||||
return db.none(sql, [cryptoCode, address])
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertIntoBlacklist = (cryptoCode, address) => {
|
const insertIntoBlacklist = address => {
|
||||||
|
if (!isValidAddress(address)) {
|
||||||
|
return Promise.reject(new Error('Invalid address'))
|
||||||
|
}
|
||||||
return db
|
return db
|
||||||
.none(
|
.none(
|
||||||
'INSERT INTO blacklist (crypto_code, address) VALUES ($1, $2);',
|
'INSERT INTO blacklist (address) VALUES ($1);',
|
||||||
[cryptoCode, address]
|
[address]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function blocked (address, cryptoCode) {
|
function blocked (address) {
|
||||||
const sql = `SELECT * FROM blacklist WHERE address = $1 AND crypto_code = $2`
|
const sql = `SELECT address, content FROM blacklist b LEFT OUTER JOIN blacklist_messages bm ON bm.id = b.blacklist_message_id WHERE address = $1`
|
||||||
return db.any(sql, [address, cryptoCode])
|
return db.oneOrNone(sql, [address])
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToUsedAddresses (address, cryptoCode) {
|
function getMessages () {
|
||||||
// ETH reuses addresses
|
const sql = `SELECT * FROM blacklist_messages`
|
||||||
if (cryptoCode === 'ETH') return Promise.resolve()
|
return db.any(sql)
|
||||||
|
}
|
||||||
|
|
||||||
const sql = `INSERT INTO blacklist (crypto_code, address) VALUES ($1, $2)`
|
function editBlacklistMessage (id, content) {
|
||||||
return db.oneOrNone(sql, [cryptoCode, address])
|
const sql = `UPDATE blacklist_messages SET content = $1 WHERE id = $2 RETURNING id`
|
||||||
|
return db.oneOrNone(sql, [content, id])
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
blocked,
|
blocked,
|
||||||
addToUsedAddresses,
|
|
||||||
getBlacklist,
|
getBlacklist,
|
||||||
deleteFromBlacklist,
|
deleteFromBlacklist,
|
||||||
insertIntoBlacklist
|
insertIntoBlacklist,
|
||||||
|
getMessages,
|
||||||
|
editBlacklistMessage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Bitcoin Core. This may take a minute...')
|
common.logger.info('Updating Bitcoin Core. This may take a minute...')
|
||||||
!isDevMode() && common.es(`sudo supervisorctl stop bitcoin`)
|
!isDevMode() && common.es(`sudo supervisorctl stop bitcoin`)
|
||||||
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
|
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/bitcoin.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Bitcoin Core: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
@ -55,6 +59,20 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.es(`echo "\nlistenonion=0" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
|
common.es(`echo "\nlistenonion=0" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (common.es(`grep "fallbackfee=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
|
||||||
|
common.logger.info(`fallbackfee already defined, skipping...`)
|
||||||
|
} else {
|
||||||
|
common.logger.info(`Setting 'fallbackfee=0.00005' in config file...`)
|
||||||
|
common.es(`echo "\nfallbackfee=0.00005" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common.es(`grep "rpcworkqueue=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
|
||||||
|
common.logger.info(`rpcworkqueue already defined, skipping...`)
|
||||||
|
} else {
|
||||||
|
common.logger.info(`Setting 'rpcworkqueue=2000' in config file...`)
|
||||||
|
common.es(`echo "\nrpcworkqueue=2000" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
|
||||||
|
}
|
||||||
|
|
||||||
if (isCurrentlyRunning && !isDevMode()) {
|
if (isCurrentlyRunning && !isDevMode()) {
|
||||||
common.logger.info('Starting wallet...')
|
common.logger.info('Starting wallet...')
|
||||||
common.es(`sudo supervisorctl start bitcoin`)
|
common.es(`sudo supervisorctl start bitcoin`)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Bitcoin Cash. This may take a minute...')
|
common.logger.info('Updating Bitcoin Cash. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop bitcoincash`)
|
common.es(`sudo supervisorctl stop bitcoincash`)
|
||||||
common.es(`curl -#Lo /tmp/bitcoincash.tar.gz ${coinRec.url}`)
|
common.es(`curl -#Lo /tmp/bitcoincash.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/bitcoincash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Bitcoin Cash: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/bitcoincash.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/bitcoincash.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -29,39 +29,49 @@ module.exports = {
|
||||||
const BINARIES = {
|
const BINARIES = {
|
||||||
BTC: {
|
BTC: {
|
||||||
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
|
||||||
|
defaultUrlHash: '376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397',
|
||||||
defaultDir: 'bitcoin-0.20.1/bin',
|
defaultDir: 'bitcoin-0.20.1/bin',
|
||||||
url: 'https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-28.0/bin'
|
dir: 'bitcoin-28.0/bin',
|
||||||
|
urlHash: '7fe294b02b25b51acb8e8e0a0eb5af6bbafa7cd0c5b0e5fcbb61263104a82fbc',
|
||||||
},
|
},
|
||||||
ETH: {
|
ETH: {
|
||||||
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.12-293a300d.tar.gz',
|
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.12-293a300d.tar.gz',
|
||||||
dir: 'geth-linux-amd64-1.14.12-293a300d'
|
dir: 'geth-linux-amd64-1.14.12-293a300d',
|
||||||
|
urlHash: 'e56216b9d179a66a8f71d3dee13ad554da5544d3d29dba33f64c9c0eda5a2237',
|
||||||
},
|
},
|
||||||
ZEC: {
|
ZEC: {
|
||||||
url: 'https://download.z.cash/downloads/zcash-6.0.0-linux64-debian-bullseye.tar.gz',
|
url: 'https://download.z.cash/downloads/zcash-6.0.0-linux64-debian-bullseye.tar.gz',
|
||||||
dir: 'zcash-6.0.0/bin'
|
dir: 'zcash-6.0.0/bin',
|
||||||
|
urlHash: '3cb82f490e9c8e88007a0216b5261b33ef0fda962b9258441b2def59cb272a4d',
|
||||||
},
|
},
|
||||||
DASH: {
|
DASH: {
|
||||||
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
||||||
|
defaultUrlHash: 'd89c2afd78183f3ee815adcccdff02098be0c982633889e7b1e9c9656fbef219',
|
||||||
defaultDir: 'dashcore-18.1.0/bin',
|
defaultDir: 'dashcore-18.1.0/bin',
|
||||||
url: 'https://github.com/dashpay/dash/releases/download/v21.1.1/dashcore-21.1.1-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/dashpay/dash/releases/download/v21.1.1/dashcore-21.1.1-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'dashcore-21.1.1/bin'
|
dir: 'dashcore-21.1.1/bin'
|
||||||
|
urlHash: 'c3157d4a82a3cb7c904a68e827bd1e629854fefcc0dcaf1de4343a810a190bf5',
|
||||||
},
|
},
|
||||||
LTC: {
|
LTC: {
|
||||||
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||||
|
defaultUrlHash: 'ca50936299e2c5a66b954c266dcaaeef9e91b2f5307069b9894048acf3eb5751',
|
||||||
defaultDir: 'litecoin-0.18.1/bin',
|
defaultDir: 'litecoin-0.18.1/bin',
|
||||||
url: 'https://download.litecoin.org/litecoin-0.21.4/linux/litecoin-0.21.4-x86_64-linux-gnu.tar.gz',
|
url: 'https://download.litecoin.org/litecoin-0.21.4/linux/litecoin-0.21.4-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'litecoin-0.21.4/bin'
|
dir: 'litecoin-0.21.4/bin',
|
||||||
|
urlHash: '857fc41091f2bae65c3bf0fd4d388fca915fc93a03f16dd2578ac3cc92898390',
|
||||||
},
|
},
|
||||||
BCH: {
|
BCH: {
|
||||||
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v28.0.0/bitcoin-cash-node-28.0.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v28.0.0/bitcoin-cash-node-28.0.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-cash-node-28.0.0/bin',
|
dir: 'bitcoin-cash-node-28.0.0/bin',
|
||||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']],
|
||||||
|
urlHash: 'ba735cd3b70fab35ac1496e38596cec1f8d34989924376de001d4a86198f7158',
|
||||||
},
|
},
|
||||||
XMR: {
|
XMR: {
|
||||||
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.4.tar.bz2',
|
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.4.tar.bz2',
|
||||||
dir: 'monero-x86_64-linux-gnu-v0.18.3.4',
|
dir: 'monero-x86_64-linux-gnu-v0.18.3.4',
|
||||||
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
|
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']],
|
||||||
|
urlHash: '51ba03928d189c1c11b5379cab17dd9ae8d2230056dc05c872d0f8dba4a87f1d',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,10 +143,15 @@ function fetchAndInstall (coinRec) {
|
||||||
if (!binaries) throw new Error(`No such coin: ${coinRec.code}`)
|
if (!binaries) throw new Error(`No such coin: ${coinRec.code}`)
|
||||||
|
|
||||||
const url = requiresUpdate ? binaries.defaultUrl : binaries.url
|
const url = requiresUpdate ? binaries.defaultUrl : binaries.url
|
||||||
|
const hash = requiresUpdate ? binaries.defaultUrlHash : binaries.urlHash
|
||||||
const downloadFile = path.basename(url)
|
const downloadFile = path.basename(url)
|
||||||
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
|
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
|
||||||
|
|
||||||
es(`wget -q ${url}`)
|
es(`wget -q ${url}`)
|
||||||
|
if (es(`sha256sum ${downloadFile} | awk '{print $1}'`).trim() !== hash) {
|
||||||
|
logger.info(`Failed to install ${coinRec.code}: Package signature do not match!`)
|
||||||
|
return
|
||||||
|
}
|
||||||
es(`tar -xf ${downloadFile}`)
|
es(`tar -xf ${downloadFile}`)
|
||||||
|
|
||||||
const usrBinDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'bin') : '/usr/local/bin'
|
const usrBinDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'bin') : '/usr/local/bin'
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Dash Core. This may take a minute...')
|
common.logger.info('Updating Dash Core. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop dash`)
|
common.es(`sudo supervisorctl stop dash`)
|
||||||
common.es(`curl -#Lo /tmp/dash.tar.gz ${coinRec.url}`)
|
common.es(`curl -#Lo /tmp/dash.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/dash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Dash Core: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/dash.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/dash.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating the Geth Ethereum wallet. This may take a minute...')
|
common.logger.info('Updating the Geth Ethereum wallet. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop ethereum`)
|
common.es(`sudo supervisorctl stop ethereum`)
|
||||||
common.es(`curl -#o /tmp/ethereum.tar.gz ${coinRec.url}`)
|
common.es(`curl -#o /tmp/ethereum.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/ethereum.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Geth: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/ethereum.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/ethereum.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Litecoin Core. This may take a minute...')
|
common.logger.info('Updating Litecoin Core. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop litecoin`)
|
common.es(`sudo supervisorctl stop litecoin`)
|
||||||
common.es(`curl -#o /tmp/litecoin.tar.gz ${coinRec.url}`)
|
common.es(`curl -#o /tmp/litecoin.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/litecoin.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Litecoin Core: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/litecoin.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/litecoin.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating Monero. This may take a minute...')
|
common.logger.info('Updating Monero. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop monero monero-wallet`)
|
common.es(`sudo supervisorctl stop monero monero-wallet`)
|
||||||
common.es(`curl -#o /tmp/monero.tar.gz ${coinRec.url}`)
|
common.es(`curl -#o /tmp/monero.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/monero.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Monero: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xf /tmp/monero.tar.gz -C /tmp/`)
|
common.es(`tar -xf /tmp/monero.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.logger.info('Updating your Zcash wallet. This may take a minute...')
|
common.logger.info('Updating your Zcash wallet. This may take a minute...')
|
||||||
common.es(`sudo supervisorctl stop zcash`)
|
common.es(`sudo supervisorctl stop zcash`)
|
||||||
common.es(`curl -#Lo /tmp/zcash.tar.gz ${coinRec.url}`)
|
common.es(`curl -#Lo /tmp/zcash.tar.gz ${coinRec.url}`)
|
||||||
|
if (common.es(`sha256sum /tmp/zcash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
|
||||||
|
common.logger.info('Failed to update Zcash: Package signature do not match!')
|
||||||
|
return
|
||||||
|
}
|
||||||
common.es(`tar -xzf /tmp/zcash.tar.gz -C /tmp/`)
|
common.es(`tar -xzf /tmp/zcash.tar.gz -C /tmp/`)
|
||||||
|
|
||||||
common.logger.info('Updating wallet...')
|
common.logger.info('Updating wallet...')
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const E = require('../error')
|
||||||
|
|
||||||
const PENDING_INTERVAL_MS = 60 * T.minutes
|
const PENDING_INTERVAL_MS = 60 * T.minutes
|
||||||
|
|
||||||
const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse', 'promoCodeApplied', 'validWalletScore', 'cashInFeeCrypto']
|
const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'blacklistMessage', 'addressReuse', 'promoCodeApplied', 'validWalletScore', 'cashInFeeCrypto']
|
||||||
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
|
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
|
||||||
|
|
||||||
const massage = _.flow(_.omit(massageFields),
|
const massage = _.flow(_.omit(massageFields),
|
||||||
|
|
|
||||||
|
|
@ -32,38 +32,41 @@ function post (machineTx, pi) {
|
||||||
return cashInAtomic.atomic(machineTx, pi)
|
return cashInAtomic.atomic(machineTx, pi)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
const updatedTx = r.tx
|
const updatedTx = r.tx
|
||||||
let blacklisted = false
|
|
||||||
let addressReuse = false
|
let addressReuse = false
|
||||||
let walletScore = {}
|
|
||||||
|
|
||||||
const promises = [settingsLoader.loadLatestConfig()]
|
const promises = [settingsLoader.loadLatestConfig()]
|
||||||
|
|
||||||
const isFirstPost = !r.tx.fiat || r.tx.fiat.isZero()
|
const isFirstPost = !r.tx.fiat || r.tx.fiat.isZero()
|
||||||
if (isFirstPost) {
|
if (isFirstPost) {
|
||||||
promises.push(checkForBlacklisted(updatedTx), doesTxReuseAddress(updatedTx), getWalletScore(updatedTx, pi))
|
promises.push(
|
||||||
|
checkForBlacklisted(updatedTx),
|
||||||
|
doesTxReuseAddress(updatedTx),
|
||||||
|
getWalletScore(updatedTx, pi)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
.then(([config, blacklistItems = false, isReusedAddress = false, fetchedWalletScore = null]) => {
|
.then(([config, blacklisted = false, isReusedAddress = false, walletScore = null]) => {
|
||||||
const rejectAddressReuse = configManager.getCompliance(config).rejectAddressReuse
|
const { rejectAddressReuse } = configManager.getCompliance(config)
|
||||||
|
const isBlacklisted = !!blacklisted
|
||||||
|
|
||||||
walletScore = fetchedWalletScore
|
if (isBlacklisted) {
|
||||||
|
|
||||||
if (_.some(it => it.address === updatedTx.toAddress)(blacklistItems)) {
|
|
||||||
blacklisted = true
|
|
||||||
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, false)
|
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, false)
|
||||||
} else if (isReusedAddress && rejectAddressReuse) {
|
} else if (isReusedAddress && rejectAddressReuse) {
|
||||||
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true)
|
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true)
|
||||||
addressReuse = true
|
addressReuse = true
|
||||||
}
|
}
|
||||||
return postProcess(r, pi, blacklisted, addressReuse, walletScore)
|
return postProcess(r, pi, isBlacklisted, addressReuse, walletScore)
|
||||||
|
.then(changes => _.set('walletScore', _.isNil(walletScore) ? null : walletScore.score, changes))
|
||||||
|
.then(changes => cashInLow.update(db, updatedTx, changes))
|
||||||
|
.then(_.flow(
|
||||||
|
_.set('bills', machineTx.bills),
|
||||||
|
_.set('blacklisted', isBlacklisted),
|
||||||
|
_.set('blacklistMessage', blacklisted?.content),
|
||||||
|
_.set('addressReuse', addressReuse),
|
||||||
|
_.set('validWalletScore', _.isNil(walletScore) || walletScore.isValid),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.then(changes => _.set('walletScore', _.isNil(walletScore) ? null : walletScore.score, changes))
|
|
||||||
.then(changes => cashInLow.update(db, updatedTx, changes))
|
|
||||||
.then(tx => _.set('bills', machineTx.bills, tx))
|
|
||||||
.then(tx => _.set('blacklisted', blacklisted, tx))
|
|
||||||
.then(tx => _.set('addressReuse', addressReuse, tx))
|
|
||||||
.then(tx => _.set('validWalletScore', _.isNil(walletScore) ? true : walletScore.isValid, tx))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +97,7 @@ function logActionById (action, _rec, txId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForBlacklisted (tx) {
|
function checkForBlacklisted (tx) {
|
||||||
return blacklist.blocked(tx.toAddress, tx.cryptoCode)
|
return blacklist.blocked(tx.toAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
||||||
|
|
@ -148,18 +151,17 @@ function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// Important: We don't know what kind of error this is
|
// Important: We don't know what kind of error this is
|
||||||
// so not safe to assume that funds weren't sent.
|
// so not safe to assume that funds weren't sent.
|
||||||
// Therefore, don't set sendPending to false except for
|
|
||||||
// errors (like InsufficientFundsError) that are guaranteed
|
// Setting sendPending to true ensures that the transaction gets
|
||||||
// not to send.
|
// silently terminated and no retries are done
|
||||||
const sendPending = err.name !== 'InsufficientFundsError'
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sendTime: 'now()^',
|
sendTime: 'now()^',
|
||||||
error: err.message,
|
error: err.message,
|
||||||
errorCode: err.name,
|
errorCode: err.name,
|
||||||
sendPending
|
sendPending: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(sendRec => {
|
.then(sendRec => {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const mapValuesWithKey = _.mapValues.convert({cap: false})
|
||||||
|
|
||||||
function convertBigNumFields (obj) {
|
function convertBigNumFields (obj) {
|
||||||
const convert = (value, key) => {
|
const convert = (value, key) => {
|
||||||
if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat' ])) {
|
if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat', 'fixedFee' ])) {
|
||||||
return value.toString()
|
return value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const camelize = require('./utils')
|
||||||
|
|
||||||
function createCashboxBatch (deviceId, cashboxCount) {
|
function createCashboxBatch (deviceId, cashboxCount) {
|
||||||
if (_.isEqual(0, cashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
|
if (_.isEqual(0, cashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
|
||||||
const sql = `INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty')`
|
const sql = `INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty') RETURNING *`
|
||||||
const sql2 = `
|
const sql2 = `
|
||||||
UPDATE bills SET cashbox_batch_id=$1
|
UPDATE bills SET cashbox_batch_id=$1
|
||||||
FROM cash_in_txs
|
FROM cash_in_txs
|
||||||
|
|
@ -25,6 +25,7 @@ function createCashboxBatch (deviceId, cashboxCount) {
|
||||||
const q2 = t.none(sql2, [batchId, deviceId])
|
const q2 = t.none(sql2, [batchId, deviceId])
|
||||||
const q3 = t.none(sql3, [batchId, deviceId])
|
const q3 = t.none(sql3, [batchId, deviceId])
|
||||||
return t.batch([q1, q2, q3])
|
return t.batch([q1, q2, q3])
|
||||||
|
.then(([it]) => it)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,14 +101,6 @@ function editBatchById (id, performedBy) {
|
||||||
return db.none(sql, [performedBy, id])
|
return db.none(sql, [performedBy, id])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBillsByBatchId (id) {
|
|
||||||
const sql = `SELECT bi.* FROM (
|
|
||||||
SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, cit.device_id AS device_id FROM bills b LEFT OUTER JOIN (SELECT id, device_id FROM cash_in_txs) AS cit ON cit.id = b.cash_in_txs_id UNION
|
|
||||||
SELECT id, fiat, fiat_code, created, cashbox_batch_id, device_id FROM empty_unit_bills
|
|
||||||
) AS bi WHERE bi.cashbox_batch_id=$1`
|
|
||||||
return db.any(sql, [id])
|
|
||||||
}
|
|
||||||
|
|
||||||
function logFormatter (data) {
|
function logFormatter (data) {
|
||||||
return _.map(
|
return _.map(
|
||||||
it => {
|
it => {
|
||||||
|
|
@ -124,11 +117,62 @@ function logFormatter (data) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMachineUnbatchedBills (deviceId) {
|
||||||
|
const sql = `
|
||||||
|
SELECT now() AS created, cash_in_txs.device_id, json_agg(b.*) AS bills FROM bills b LEFT OUTER JOIN cash_in_txs
|
||||||
|
ON b.cash_in_txs_id = cash_in_txs.id
|
||||||
|
WHERE b.cashbox_batch_id IS NULL AND cash_in_txs.device_id = $1
|
||||||
|
GROUP BY cash_in_txs.device_id
|
||||||
|
`
|
||||||
|
|
||||||
|
return db.oneOrNone(sql, [deviceId])
|
||||||
|
.then(res => _.mapKeys(it => _.camelCase(it), res))
|
||||||
|
.then(logFormatterSingle)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBatchById (id) {
|
||||||
|
const sql = `
|
||||||
|
SELECT cb.id, cb.device_id, cb.created, cb.operation_type, cb.bill_count_override, cb.performed_by, json_agg(b.*) AS bills
|
||||||
|
FROM cashbox_batches AS cb
|
||||||
|
LEFT JOIN bills AS b ON cb.id = b.cashbox_batch_id
|
||||||
|
WHERE cb.id = $1
|
||||||
|
GROUP BY cb.id
|
||||||
|
`
|
||||||
|
|
||||||
|
return db.oneOrNone(sql, [id]).then(res => _.mapKeys(it => _.camelCase(it), res))
|
||||||
|
.then(logFormatterSingle)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logFormatterSingle (data) {
|
||||||
|
const bills = _.filter(
|
||||||
|
it => !(_.isNil(it) || _.isNil(it.fiat_code) || _.isNil(it.fiat) || _.isNaN(it.fiat)),
|
||||||
|
data.bills
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
deviceId: data.deviceId,
|
||||||
|
created: data.created,
|
||||||
|
operationType: data.operationType,
|
||||||
|
billCount: _.size(bills),
|
||||||
|
fiatTotals: _.reduce(
|
||||||
|
(acc, value) => {
|
||||||
|
acc[value.fiat_code] = (acc[value.fiat_code] || 0) + value.fiat
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
bills
|
||||||
|
),
|
||||||
|
billsByDenomination: _.countBy(it => `${it.fiat} ${it.fiat_code}`, bills)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createCashboxBatch,
|
createCashboxBatch,
|
||||||
updateMachineWithBatch,
|
updateMachineWithBatch,
|
||||||
getBatches,
|
getBatches,
|
||||||
getBillsByBatchId,
|
|
||||||
editBatchById,
|
editBatchById,
|
||||||
|
getBatchById,
|
||||||
|
getMachineUnbatchedBills,
|
||||||
logFormatter
|
logFormatter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
|
||||||
const cashInFee = showCommissions ? commissions.cashIn / 100 : null
|
const cashInFee = showCommissions ? commissions.cashIn / 100 : null
|
||||||
const cashOutFee = showCommissions ? commissions.cashOut / 100 : null
|
const cashOutFee = showCommissions ? commissions.cashOut / 100 : null
|
||||||
const cashInFixedFee = showCommissions ? commissions.fixedFee : null
|
const cashInFixedFee = showCommissions ? commissions.fixedFee : null
|
||||||
|
const cashOutFixedFee = showCommissions ? commissions.cashOutFixedFee : null
|
||||||
const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null
|
const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null
|
||||||
const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null
|
const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null
|
||||||
|
|
||||||
|
|
@ -37,6 +38,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
|
||||||
cashInFee,
|
cashInFee,
|
||||||
cashOutFee,
|
cashOutFee,
|
||||||
cashInFixedFee,
|
cashInFixedFee,
|
||||||
|
cashOutFixedFee,
|
||||||
cashInRate,
|
cashInRate,
|
||||||
cashOutRate
|
cashOutRate
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ function getWithEmail (email) {
|
||||||
*
|
*
|
||||||
* @param {string} id Customer's id
|
* @param {string} id Customer's id
|
||||||
* @param {object} data Fields to update
|
* @param {object} data Fields to update
|
||||||
* @param {string} Acting user's token
|
* @param {string} userToken Acting user's token
|
||||||
*
|
*
|
||||||
* @returns {Promise} Newly updated Customer
|
* @returns {Promise} Newly updated Customer
|
||||||
*/
|
*/
|
||||||
|
|
@ -114,6 +114,7 @@ function update (id, data, userToken) {
|
||||||
async function updateCustomer (id, data, userToken) {
|
async function updateCustomer (id, data, userToken) {
|
||||||
const formattedData = _.pick(
|
const formattedData = _.pick(
|
||||||
[
|
[
|
||||||
|
'sanctions',
|
||||||
'authorized_override',
|
'authorized_override',
|
||||||
'id_card_photo_override',
|
'id_card_photo_override',
|
||||||
'id_card_data_override',
|
'id_card_data_override',
|
||||||
|
|
@ -229,7 +230,7 @@ function enhanceEditedPhotos (fields) {
|
||||||
/**
|
/**
|
||||||
* Remove the edited data from the db record
|
* Remove the edited data from the db record
|
||||||
*
|
*
|
||||||
* @name enhanceOverrideFields
|
* @name deleteEditedData
|
||||||
* @function
|
* @function
|
||||||
*
|
*
|
||||||
* @param {string} id Customer's id
|
* @param {string} id Customer's id
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const { ALL_CRYPTOS } = require('@lamassu/coins')
|
||||||
|
|
||||||
const configManager = require('./new-config-manager')
|
const configManager = require('./new-config-manager')
|
||||||
const ccxt = require('./plugins/exchange/ccxt')
|
const ccxt = require('./plugins/exchange/ccxt')
|
||||||
const mockExchange = require('./plugins/exchange/mock-exchange')
|
const mockExchange = require('./plugins/exchange/mock-exchange')
|
||||||
|
const accounts = require('./new-admin/config/accounts')
|
||||||
|
|
||||||
function lookupExchange (settings, cryptoCode) {
|
function lookupExchange (settings, cryptoCode) {
|
||||||
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
|
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
|
||||||
|
|
@ -45,8 +49,33 @@ function active (settings, cryptoCode) {
|
||||||
return !!lookupExchange(settings, cryptoCode)
|
return !!lookupExchange(settings, cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMarkets () {
|
||||||
|
const filterExchanges = _.filter(it => it.class === 'exchange' && !it.dev && it.code !== 'no-exchange')
|
||||||
|
const availableExchanges = _.map(it => it.code, filterExchanges(accounts.ACCOUNT_LIST))
|
||||||
|
|
||||||
|
const fetchMarketForExchange = exchange =>
|
||||||
|
ccxt.getMarkets(exchange, ALL_CRYPTOS)
|
||||||
|
.then(markets => ({ exchange, markets }))
|
||||||
|
.catch(error => ({
|
||||||
|
exchange,
|
||||||
|
markets: [],
|
||||||
|
error: error.message
|
||||||
|
}))
|
||||||
|
|
||||||
|
const transformToObject = _.reduce((acc, { exchange, markets }) => ({
|
||||||
|
...acc,
|
||||||
|
[exchange]: markets
|
||||||
|
}), {})
|
||||||
|
|
||||||
|
const promises = _.map(fetchMarketForExchange, availableExchanges)
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(transformToObject)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
fetchExchange,
|
||||||
buy,
|
buy,
|
||||||
sell,
|
sell,
|
||||||
active
|
active,
|
||||||
|
getMarkets
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ const addReceiptInfo = receiptInfo => ret => {
|
||||||
if (!receiptInfo) return ret
|
if (!receiptInfo) return ret
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
|
'automaticPrint',
|
||||||
'paper',
|
'paper',
|
||||||
'sms',
|
'sms',
|
||||||
'operatorWebsite',
|
'operatorWebsite',
|
||||||
|
|
@ -61,6 +62,18 @@ const addReceiptInfo = receiptInfo => ret => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const addMachineScreenOpts = smth => _.update(
|
||||||
|
'screenOptions',
|
||||||
|
_.flow(
|
||||||
|
addSmthInfo(
|
||||||
|
'rates',
|
||||||
|
[
|
||||||
|
'active'
|
||||||
|
]
|
||||||
|
)(smth.rates)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
/* TODO: Simplify this. */
|
/* TODO: Simplify this. */
|
||||||
const buildTriggers = allTriggers => {
|
const buildTriggers = allTriggers => {
|
||||||
const normalTriggers = []
|
const normalTriggers = []
|
||||||
|
|
@ -89,6 +102,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
'cashInCommission',
|
'cashInCommission',
|
||||||
'cashInFee',
|
'cashInFee',
|
||||||
'cashOutCommission',
|
'cashOutCommission',
|
||||||
|
'cashOutFee',
|
||||||
'cryptoCode',
|
'cryptoCode',
|
||||||
'cryptoCodeDisplay',
|
'cryptoCodeDisplay',
|
||||||
'cryptoNetwork',
|
'cryptoNetwork',
|
||||||
|
|
@ -102,7 +116,8 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
_.pick([
|
_.pick([
|
||||||
'coins',
|
'coins',
|
||||||
'configVersion',
|
'configVersion',
|
||||||
'timezone'
|
'timezone',
|
||||||
|
'screenOptions'
|
||||||
]),
|
]),
|
||||||
_.update('coins', massageCoins),
|
_.update('coins', massageCoins),
|
||||||
_.set('serverVersion', VERSION),
|
_.set('serverVersion', VERSION),
|
||||||
|
|
@ -116,6 +131,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
configManager.getLocale(deviceId, settings.config),
|
configManager.getLocale(deviceId, settings.config),
|
||||||
configManager.getOperatorInfo(settings.config),
|
configManager.getOperatorInfo(settings.config),
|
||||||
configManager.getReceipt(settings.config),
|
configManager.getReceipt(settings.config),
|
||||||
|
configManager.getAllMachineScreenOpts(settings.config),
|
||||||
!!configManager.getCashOut(deviceId, settings.config).active,
|
!!configManager.getCashOut(deviceId, settings.config).active,
|
||||||
getMachine(deviceId, currentConfigVersion),
|
getMachine(deviceId, currentConfigVersion),
|
||||||
configManager.getCustomerAuthenticationMethod(settings.config)
|
configManager.getCustomerAuthenticationMethod(settings.config)
|
||||||
|
|
@ -128,6 +144,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
localeInfo,
|
localeInfo,
|
||||||
operatorInfo,
|
operatorInfo,
|
||||||
receiptInfo,
|
receiptInfo,
|
||||||
|
machineScreenOpts,
|
||||||
twoWayMode,
|
twoWayMode,
|
||||||
{ numberOfCassettes, numberOfRecyclers },
|
{ numberOfCassettes, numberOfRecyclers },
|
||||||
customerAuthentication,
|
customerAuthentication,
|
||||||
|
|
@ -152,7 +169,8 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
urlsToPing,
|
urlsToPing,
|
||||||
}),
|
}),
|
||||||
addOperatorInfo(operatorInfo),
|
addOperatorInfo(operatorInfo),
|
||||||
addReceiptInfo(receiptInfo)
|
addReceiptInfo(receiptInfo),
|
||||||
|
addMachineScreenOpts(machineScreenOpts)
|
||||||
)(staticConf))
|
)(staticConf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ type Coin {
|
||||||
display: String!
|
display: String!
|
||||||
minimumTx: String!
|
minimumTx: String!
|
||||||
cashInFee: String!
|
cashInFee: String!
|
||||||
|
cashOutFee: String!
|
||||||
cashInCommission: String!
|
cashInCommission: String!
|
||||||
cashOutCommission: String!
|
cashOutCommission: String!
|
||||||
cryptoNetwork: String!
|
cryptoNetwork: String!
|
||||||
|
|
@ -37,6 +38,7 @@ type MachineInfo {
|
||||||
|
|
||||||
type ReceiptInfo {
|
type ReceiptInfo {
|
||||||
paper: Boolean!
|
paper: Boolean!
|
||||||
|
automaticPrint: Boolean!
|
||||||
sms: Boolean!
|
sms: Boolean!
|
||||||
operatorWebsite: Boolean!
|
operatorWebsite: Boolean!
|
||||||
operatorEmail: Boolean!
|
operatorEmail: Boolean!
|
||||||
|
|
@ -48,6 +50,14 @@ type ReceiptInfo {
|
||||||
addressQRCode: Boolean!
|
addressQRCode: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MachineScreenOptions {
|
||||||
|
rates: RateScreenOptions!
|
||||||
|
}
|
||||||
|
|
||||||
|
type RateScreenOptions {
|
||||||
|
active: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type SpeedtestFile {
|
type SpeedtestFile {
|
||||||
url: String!
|
url: String!
|
||||||
size: Int!
|
size: Int!
|
||||||
|
|
@ -146,6 +156,7 @@ type StaticConfig {
|
||||||
operatorInfo: OperatorInfo
|
operatorInfo: OperatorInfo
|
||||||
machineInfo: MachineInfo!
|
machineInfo: MachineInfo!
|
||||||
receiptInfo: ReceiptInfo
|
receiptInfo: ReceiptInfo
|
||||||
|
screenOptions: MachineScreenOptions
|
||||||
|
|
||||||
speedtestFiles: [SpeedtestFile!]!
|
speedtestFiles: [SpeedtestFile!]!
|
||||||
urlsToPing: [String!]!
|
urlsToPing: [String!]!
|
||||||
|
|
|
||||||
15
lib/middlewares/addRWBytes.js
Normal file
15
lib/middlewares/addRWBytes.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
const addRWBytes = () => (req, res, next) => {
|
||||||
|
const handle = () => {
|
||||||
|
res.removeListener('finish', handle)
|
||||||
|
res.removeListener('close', handle)
|
||||||
|
res.bytesRead = req.connection.bytesRead
|
||||||
|
res.bytesWritten = req.connection.bytesWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
res.on('finish', handle)
|
||||||
|
res.on('close', handle)
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addRWBytes
|
||||||
|
|
@ -14,6 +14,7 @@ const { ApolloServer } = require('apollo-server-express')
|
||||||
require('../environment-helper')
|
require('../environment-helper')
|
||||||
const { asyncLocalStorage, defaultStore } = require('../async-storage')
|
const { asyncLocalStorage, defaultStore } = require('../async-storage')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
const exchange = require('../exchange')
|
||||||
|
|
||||||
const { AuthDirective } = require('./graphql/directives')
|
const { AuthDirective } = require('./graphql/directives')
|
||||||
const { typeDefs, resolvers } = require('./graphql/schema')
|
const { typeDefs, resolvers } = require('./graphql/schema')
|
||||||
|
|
@ -98,6 +99,9 @@ function run () {
|
||||||
|
|
||||||
const serverLog = `lamassu-admin-server listening on port ${serverPort}`
|
const serverLog = `lamassu-admin-server listening on port ${serverPort}`
|
||||||
|
|
||||||
|
// cache markets on startup
|
||||||
|
exchange.getMarkets().catch(console.error)
|
||||||
|
|
||||||
const webServer = https.createServer(certOptions, app)
|
const webServer = https.createServer(certOptions, app)
|
||||||
webServer.listen(serverPort, () => logger.info(serverLog))
|
webServer.listen(serverPort, () => logger.info(serverLog))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@ const blacklist = require('../../../blacklist')
|
||||||
|
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
blacklist: () => blacklist.getBlacklist()
|
blacklist: () => blacklist.getBlacklist(),
|
||||||
|
blacklistMessages: () => blacklist.getMessages()
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
deleteBlacklistRow: (...[, { cryptoCode, address }]) =>
|
deleteBlacklistRow: (...[, { address }]) =>
|
||||||
blacklist.deleteFromBlacklist(cryptoCode, address),
|
blacklist.deleteFromBlacklist(address),
|
||||||
insertBlacklistRow: (...[, { cryptoCode, address }]) =>
|
insertBlacklistRow: (...[, { address }]) =>
|
||||||
blacklist.insertIntoBlacklist(cryptoCode, address)
|
blacklist.insertIntoBlacklist(address),
|
||||||
|
editBlacklistMessage: (...[, { id, content }]) =>
|
||||||
|
blacklist.editBlacklistMessage(id, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ const funding = require('./funding.resolver')
|
||||||
const log = require('./log.resolver')
|
const log = require('./log.resolver')
|
||||||
const loyalty = require('./loyalty.resolver')
|
const loyalty = require('./loyalty.resolver')
|
||||||
const machine = require('./machine.resolver')
|
const machine = require('./machine.resolver')
|
||||||
|
const market = require('./market.resolver')
|
||||||
const notification = require('./notification.resolver')
|
const notification = require('./notification.resolver')
|
||||||
const pairing = require('./pairing.resolver')
|
const pairing = require('./pairing.resolver')
|
||||||
const rates = require('./rates.resolver')
|
const rates = require('./rates.resolver')
|
||||||
|
const sanctions = require('./sanctions.resolver')
|
||||||
const scalar = require('./scalar.resolver')
|
const scalar = require('./scalar.resolver')
|
||||||
const settings = require('./settings.resolver')
|
const settings = require('./settings.resolver')
|
||||||
const sms = require('./sms.resolver')
|
const sms = require('./sms.resolver')
|
||||||
|
|
@ -34,9 +36,11 @@ const resolvers = [
|
||||||
log,
|
log,
|
||||||
loyalty,
|
loyalty,
|
||||||
machine,
|
machine,
|
||||||
|
market,
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
sanctions,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
sms,
|
sms,
|
||||||
|
|
|
||||||
9
lib/new-admin/graphql/resolvers/market.resolver.js
Normal file
9
lib/new-admin/graphql/resolvers/market.resolver.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const exchange = require('../../../exchange')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
getMarkets: () => exchange.getMarkets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
13
lib/new-admin/graphql/resolvers/sanctions.resolver.js
Normal file
13
lib/new-admin/graphql/resolvers/sanctions.resolver.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const sanctions = require('../../../sanctions')
|
||||||
|
const authentication = require('../modules/userManagement')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
checkAgainstSanctions: (...[, { customerId }, context]) => {
|
||||||
|
const token = authentication.getToken(context)
|
||||||
|
return sanctions.checkByUser(customerId, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
|
|
@ -2,17 +2,26 @@ const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
const typeDef = gql`
|
const typeDef = gql`
|
||||||
type Blacklist {
|
type Blacklist {
|
||||||
cryptoCode: String!
|
|
||||||
address: String!
|
address: String!
|
||||||
|
blacklistMessage: BlacklistMessage!
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlacklistMessage {
|
||||||
|
id: ID
|
||||||
|
label: String
|
||||||
|
content: String
|
||||||
|
allowToggle: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
blacklist: [Blacklist] @auth
|
blacklist: [Blacklist] @auth
|
||||||
|
blacklistMessages: [BlacklistMessage] @auth
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
deleteBlacklistRow(cryptoCode: String!, address: String!): Blacklist @auth
|
deleteBlacklistRow(address: String!): Blacklist @auth
|
||||||
insertBlacklistRow(cryptoCode: String!, address: String!): Blacklist @auth
|
insertBlacklistRow(address: String!): Blacklist @auth
|
||||||
|
editBlacklistMessage(id: ID, content: String): BlacklistMessage @auth
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ const funding = require('./funding.type')
|
||||||
const log = require('./log.type')
|
const log = require('./log.type')
|
||||||
const loyalty = require('./loyalty.type')
|
const loyalty = require('./loyalty.type')
|
||||||
const machine = require('./machine.type')
|
const machine = require('./machine.type')
|
||||||
|
const market = require('./market.type')
|
||||||
const notification = require('./notification.type')
|
const notification = require('./notification.type')
|
||||||
const pairing = require('./pairing.type')
|
const pairing = require('./pairing.type')
|
||||||
const rates = require('./rates.type')
|
const rates = require('./rates.type')
|
||||||
|
const sanctions = require('./sanctions.type')
|
||||||
const scalar = require('./scalar.type')
|
const scalar = require('./scalar.type')
|
||||||
const settings = require('./settings.type')
|
const settings = require('./settings.type')
|
||||||
const sms = require('./sms.type')
|
const sms = require('./sms.type')
|
||||||
|
|
@ -34,9 +36,11 @@ const types = [
|
||||||
log,
|
log,
|
||||||
loyalty,
|
loyalty,
|
||||||
machine,
|
machine,
|
||||||
|
market,
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
sanctions,
|
||||||
scalar,
|
scalar,
|
||||||
settings,
|
settings,
|
||||||
sms,
|
sms,
|
||||||
|
|
|
||||||
9
lib/new-admin/graphql/types/market.type.js
Normal file
9
lib/new-admin/graphql/types/market.type.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type Query {
|
||||||
|
getMarkets: JSONObject @auth
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
13
lib/new-admin/graphql/types/sanctions.type.js
Normal file
13
lib/new-admin/graphql/types/sanctions.type.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type SanctionMatches {
|
||||||
|
ofacSanctioned: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
checkAgainstSanctions(customerId: ID): SanctionMatches @auth
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
|
|
@ -23,7 +23,7 @@ const typeDef = gql`
|
||||||
errorCode: String
|
errorCode: String
|
||||||
operatorCompleted: Boolean
|
operatorCompleted: Boolean
|
||||||
sendPending: Boolean
|
sendPending: Boolean
|
||||||
cashInFee: String
|
fixedFee: String
|
||||||
minimumTx: Float
|
minimumTx: Float
|
||||||
customerId: ID
|
customerId: ID
|
||||||
isAnonymous: Boolean
|
isAnonymous: Boolean
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ const buildApolloContext = async ({ req, res }) => {
|
||||||
req.session.user.role = user.role
|
req.session.user.role = user.role
|
||||||
|
|
||||||
res.set('lamassu_role', user.role)
|
res.set('lamassu_role', user.role)
|
||||||
res.cookie('pazuz_operatoridentifier', base64.encode(user.username))
|
|
||||||
res.set('Access-Control-Expose-Headers', 'lamassu_role')
|
res.set('Access-Control-Expose-Headers', 'lamassu_role')
|
||||||
|
|
||||||
return { req, res }
|
return { req, res }
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,20 @@ function batch (
|
||||||
excludeTestingCustomers = false,
|
excludeTestingCustomers = false,
|
||||||
simplified
|
simplified
|
||||||
) {
|
) {
|
||||||
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addProfits, addNames)
|
const isCsvExport = _.isBoolean(simplified)
|
||||||
|
const packager = _.flow(
|
||||||
|
_.flatten,
|
||||||
|
_.orderBy(_.property('created'), ['desc']),
|
||||||
|
_.map(_.flow(
|
||||||
|
camelize,
|
||||||
|
_.mapKeys(k =>
|
||||||
|
k == 'cashInFee' ? 'fixedFee' :
|
||||||
|
k
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
addProfits,
|
||||||
|
addNames
|
||||||
|
)
|
||||||
|
|
||||||
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
const cashInSql = `SELECT 'cashIn' AS tx_class, txs.*,
|
||||||
c.phone AS customer_phone,
|
c.phone AS customer_phone,
|
||||||
|
|
@ -80,7 +93,7 @@ function batch (
|
||||||
AND ($12 is null or txs.to_address = $12)
|
AND ($12 is null or txs.to_address = $12)
|
||||||
AND ($13 is null or txs.txStatus = $13)
|
AND ($13 is null or txs.txStatus = $13)
|
||||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||||
AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)
|
${isCsvExport && !simplified ? '' : 'AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)'}
|
||||||
ORDER BY created DESC limit $4 offset $5`
|
ORDER BY created DESC limit $4 offset $5`
|
||||||
|
|
||||||
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
const cashOutSql = `SELECT 'cashOut' AS tx_class,
|
||||||
|
|
@ -114,7 +127,7 @@ function batch (
|
||||||
AND ($13 is null or txs.txStatus = $13)
|
AND ($13 is null or txs.txStatus = $13)
|
||||||
AND ($14 is null or txs.swept = $14)
|
AND ($14 is null or txs.swept = $14)
|
||||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||||
AND (fiat > 0)
|
${isCsvExport ? '' : 'AND fiat > 0'}
|
||||||
ORDER BY created DESC limit $4 offset $5`
|
ORDER BY created DESC limit $4 offset $5`
|
||||||
|
|
||||||
// The swept filter is cash-out only, so omit the cash-in query entirely
|
// The swept filter is cash-out only, so omit the cash-in query entirely
|
||||||
|
|
@ -140,20 +153,20 @@ function batch (
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
.then(packager)
|
.then(packager)
|
||||||
.then(res => {
|
.then(res =>
|
||||||
if (simplified) return simplifiedBatch(res)
|
!isCsvExport ? res :
|
||||||
// GQL transactions and transactionsCsv both use this function and
|
// GQL transactions and transactionsCsv both use this function and
|
||||||
// if we don't check for the correct simplified value, the Transactions page polling
|
// if we don't check for the correct simplified value, the Transactions page polling
|
||||||
// will continuously build a csv in the background
|
// will continuously build a csv in the background
|
||||||
else if (simplified === false) return advancedBatch(res)
|
simplified ? simplifiedBatch(res) :
|
||||||
return res
|
advancedBatch(res)
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function advancedBatch (data) {
|
function advancedBatch (data) {
|
||||||
const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms',
|
const fields = ['txClass', 'id', 'deviceId', 'toAddress', 'cryptoAtoms',
|
||||||
'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount',
|
'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount',
|
||||||
'dispense', 'notified', 'redeem', 'phone', 'error',
|
'dispense', 'notified', 'redeem', 'phone', 'error', 'fixedFee',
|
||||||
'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout',
|
'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout',
|
||||||
'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4',
|
'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4',
|
||||||
'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6',
|
'provisionedRecycler1', 'provisionedRecycler2', 'provisionedRecycler3', 'provisionedRecycler4', 'provisionedRecycler5', 'provisionedRecycler6',
|
||||||
|
|
@ -169,7 +182,9 @@ function advancedBatch (data) {
|
||||||
...it,
|
...it,
|
||||||
status: getStatus(it),
|
status: getStatus(it),
|
||||||
fiatProfit: getProfit(it).toString(),
|
fiatProfit: getProfit(it).toString(),
|
||||||
cryptoAmount: getCryptoAmount(it).toString()
|
cryptoAmount: getCryptoAmount(it).toString(),
|
||||||
|
fixedFee: it.fixedFee ?? null,
|
||||||
|
fee: it.fee ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return _.compose(_.map(_.pick(fields)), addAdvancedFields)(data)
|
return _.compose(_.map(_.pick(fields)), addAdvancedFields)(data)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@ const namespaces = {
|
||||||
TERMS_CONDITIONS: 'termsConditions',
|
TERMS_CONDITIONS: 'termsConditions',
|
||||||
CASH_OUT: 'cashOut',
|
CASH_OUT: 'cashOut',
|
||||||
CASH_IN: 'cashIn',
|
CASH_IN: 'cashIn',
|
||||||
COMPLIANCE: 'compliance'
|
COMPLIANCE: 'compliance',
|
||||||
|
MACHINE_SCREENS: 'machineScreens'
|
||||||
|
}
|
||||||
|
|
||||||
|
const machineScreens = {
|
||||||
|
RATES: 'rates'
|
||||||
}
|
}
|
||||||
|
|
||||||
const stripl = _.curry((q, str) => _.startsWith(q, str) ? str.slice(q.length) : str)
|
const stripl = _.curry((q, str) => _.startsWith(q, str) ? str.slice(q.length) : str)
|
||||||
|
|
@ -72,6 +77,8 @@ const getCoinAtmRadar = fromNamespace(namespaces.COIN_ATM_RADAR)
|
||||||
const getTermsConditions = fromNamespace(namespaces.TERMS_CONDITIONS)
|
const getTermsConditions = fromNamespace(namespaces.TERMS_CONDITIONS)
|
||||||
const getReceipt = fromNamespace(namespaces.RECEIPT)
|
const getReceipt = fromNamespace(namespaces.RECEIPT)
|
||||||
const getCompliance = fromNamespace(namespaces.COMPLIANCE)
|
const getCompliance = fromNamespace(namespaces.COMPLIANCE)
|
||||||
|
const getMachineScreenOpts = (screenName, config) => _.compose(fromNamespace(screenName), fromNamespace(namespaces.MACHINE_SCREENS))(config)
|
||||||
|
const getAllMachineScreenOpts = config => _.reduce((acc, value) => ({ ...acc, [value]: getMachineScreenOpts(value, config) }), {}, _.values(machineScreens))
|
||||||
|
|
||||||
const getAllCryptoCurrencies = (config) => {
|
const getAllCryptoCurrencies = (config) => {
|
||||||
const locale = fromNamespace(namespaces.LOCALE)(config)
|
const locale = fromNamespace(namespaces.LOCALE)(config)
|
||||||
|
|
@ -180,6 +187,8 @@ module.exports = {
|
||||||
getWalletSettings,
|
getWalletSettings,
|
||||||
getCashInSettings,
|
getCashInSettings,
|
||||||
getOperatorInfo,
|
getOperatorInfo,
|
||||||
|
getMachineScreenOpts,
|
||||||
|
getAllMachineScreenOpts,
|
||||||
getNotifications,
|
getNotifications,
|
||||||
getGlobalNotifications,
|
getGlobalNotifications,
|
||||||
getLocale,
|
getLocale,
|
||||||
|
|
|
||||||
|
|
@ -100,16 +100,19 @@ function loadAccounts (schemaVersion) {
|
||||||
.then(_.compose(_.defaultTo({}), _.get('data.accounts')))
|
.then(_.compose(_.defaultTo({}), _.get('data.accounts')))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideSecretFields (accounts) {
|
||||||
|
return _.flow(
|
||||||
|
_.filter(path => !_.isEmpty(_.get(path, accounts))),
|
||||||
|
_.reduce(
|
||||||
|
(accounts, path) => _.assoc(path, PASSWORD_FILLED, accounts),
|
||||||
|
accounts
|
||||||
|
)
|
||||||
|
)(SECRET_FIELDS)
|
||||||
|
}
|
||||||
|
|
||||||
function showAccounts (schemaVersion) {
|
function showAccounts (schemaVersion) {
|
||||||
return loadAccounts(schemaVersion)
|
return loadAccounts(schemaVersion)
|
||||||
.then(accounts => {
|
.then(hideSecretFields)
|
||||||
const filledSecretPaths = _.compact(_.map(path => {
|
|
||||||
if (!_.isEmpty(_.get(path, accounts))) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}, SECRET_FIELDS))
|
|
||||||
return _.compose(_.map(path => _.assoc(path, PASSWORD_FILLED), filledSecretPaths))(accounts)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertConfigRow = (dbOrTx, data) =>
|
const insertConfigRow = (dbOrTx, data) =>
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ function plugins (settings, deviceId) {
|
||||||
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
|
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
|
||||||
const minimumTx = new BN(commissions.minimumTx)
|
const minimumTx = new BN(commissions.minimumTx)
|
||||||
const cashInFee = new BN(commissions.fixedFee)
|
const cashInFee = new BN(commissions.fixedFee)
|
||||||
|
const cashOutFee = new BN(commissions.cashOutFixedFee)
|
||||||
const cashInCommission = new BN(commissions.cashIn)
|
const cashInCommission = new BN(commissions.cashIn)
|
||||||
const cashOutCommission = _.isNumber(commissions.cashOut) ? new BN(commissions.cashOut) : null
|
const cashOutCommission = _.isNumber(commissions.cashOut) ? new BN(commissions.cashOut) : null
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||||
|
|
@ -261,6 +262,7 @@ function plugins (settings, deviceId) {
|
||||||
isCashInOnly: Boolean(cryptoRec.isCashinOnly),
|
isCashInOnly: Boolean(cryptoRec.isCashinOnly),
|
||||||
minimumTx: BN.max(minimumTx, cashInFee),
|
minimumTx: BN.max(minimumTx, cashInFee),
|
||||||
cashInFee,
|
cashInFee,
|
||||||
|
cashOutFee,
|
||||||
cashInCommission,
|
cashInCommission,
|
||||||
cashOutCommission,
|
cashOutCommission,
|
||||||
cryptoNetwork,
|
cryptoNetwork,
|
||||||
|
|
@ -276,6 +278,7 @@ function plugins (settings, deviceId) {
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
const fiatCode = localeConfig.fiatCurrency
|
||||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||||
|
const machineScreenOpts = configManager.getAllMachineScreenOpts(settings.config)
|
||||||
|
|
||||||
const tickerPromises = cryptoCodes.map(c => getTickerRates(fiatCode, c))
|
const tickerPromises = cryptoCodes.map(c => getTickerRates(fiatCode, c))
|
||||||
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
|
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
|
||||||
|
|
@ -325,7 +328,8 @@ function plugins (settings, deviceId) {
|
||||||
coins,
|
coins,
|
||||||
configVersion,
|
configVersion,
|
||||||
areThereAvailablePromoCodes: numberOfAvailablePromoCodes > 0,
|
areThereAvailablePromoCodes: numberOfAvailablePromoCodes > 0,
|
||||||
timezone
|
timezone,
|
||||||
|
screenOptions: machineScreenOpts
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -473,25 +477,28 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
function buyAndSell (rec, doBuy, tx) {
|
function buyAndSell (rec, doBuy, tx) {
|
||||||
const cryptoCode = rec.cryptoCode
|
const cryptoCode = rec.cryptoCode
|
||||||
const fiatCode = rec.fiatCode
|
return exchange.fetchExchange(settings, cryptoCode)
|
||||||
const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
|
.then(_exchange => {
|
||||||
|
const fiatCode = _exchange.account.currencyMarket
|
||||||
|
const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
|
||||||
|
|
||||||
const market = [fiatCode, cryptoCode].join('')
|
const market = [fiatCode, cryptoCode].join('')
|
||||||
|
|
||||||
if (!exchange.active(settings, cryptoCode)) return
|
if (!exchange.active(settings, cryptoCode)) return
|
||||||
|
|
||||||
const direction = doBuy ? 'cashIn' : 'cashOut'
|
const direction = doBuy ? 'cashIn' : 'cashOut'
|
||||||
const internalTxId = tx ? tx.id : rec.id
|
const internalTxId = tx ? tx.id : rec.id
|
||||||
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
||||||
if (!tradesQueues[market]) tradesQueues[market] = []
|
if (!tradesQueues[market]) tradesQueues[market] = []
|
||||||
tradesQueues[market].push({
|
tradesQueues[market].push({
|
||||||
direction,
|
direction,
|
||||||
internalTxId,
|
internalTxId,
|
||||||
fiatCode,
|
fiatCode,
|
||||||
cryptoAtoms,
|
cryptoAtoms,
|
||||||
cryptoCode,
|
cryptoCode,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function consolidateTrades (cryptoCode, fiatCode) {
|
function consolidateTrades (cryptoCode, fiatCode) {
|
||||||
|
|
@ -548,19 +555,22 @@ function plugins (settings, deviceId) {
|
||||||
const deviceIds = devices.map(device => device.deviceId)
|
const deviceIds = devices.map(device => device.deviceId)
|
||||||
const lists = deviceIds.map(deviceId => {
|
const lists = deviceIds.map(deviceId => {
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
|
||||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||||
|
|
||||||
return cryptoCodes.map(cryptoCode => ({
|
return Promise.all(cryptoCodes.map(cryptoCode => {
|
||||||
fiatCode,
|
return exchange.fetchExchange(settings, cryptoCode)
|
||||||
cryptoCode
|
.then(exchange => ({
|
||||||
|
fiatCode: exchange.account.currencyMarket,
|
||||||
|
cryptoCode
|
||||||
|
}))
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tradesPromises = _.uniq(_.flatten(lists))
|
return Promise.all(lists)
|
||||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
})
|
||||||
|
.then(lists => {
|
||||||
return Promise.all(tradesPromises)
|
return Promise.all(_.uniq(_.flatten(lists))
|
||||||
|
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)))
|
||||||
})
|
})
|
||||||
.catch(logger.error)
|
.catch(logger.error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ const ALL = {
|
||||||
bitpay: bitpay,
|
bitpay: bitpay,
|
||||||
coinbase: {
|
coinbase: {
|
||||||
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, TRX, LN],
|
CRYPTO: [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, TRX, LN],
|
||||||
FIAT: 'ALL_CURRENCIES'
|
FIAT: 'ALL_CURRENCIES',
|
||||||
|
DEFAULT_FIAT_MARKET: 'EUR'
|
||||||
},
|
},
|
||||||
binance: binance,
|
binance: binance,
|
||||||
bitfinex: bitfinex
|
bitfinex: bitfinex
|
||||||
|
|
@ -33,11 +34,8 @@ function buildMarket (fiatCode, cryptoCode, serviceName) {
|
||||||
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
|
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
|
||||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||||
}
|
}
|
||||||
const fiatSupported = ALL[serviceName].FIAT
|
|
||||||
if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
|
if (_.isNil(fiatCode)) throw new Error('Market pair building failed: Missing fiat code')
|
||||||
logger.info('Building a market for an unsupported fiat. Defaulting to EUR market')
|
|
||||||
return cryptoCode + '/' + 'EUR'
|
|
||||||
}
|
|
||||||
return cryptoCode + '/' + fiatCode
|
return cryptoCode + '/' + fiatCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,4 +49,8 @@ function isConfigValid (config, fields) {
|
||||||
return _.every(it => it || it === 0)(values)
|
return _.every(it => it || it === 0)(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { buildMarket, ALL, verifyFiatSupport, isConfigValid }
|
function defaultFiatMarket (serviceName) {
|
||||||
|
return ALL[serviceName].DEFAULT_FIAT_MARKET
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { buildMarket, ALL, verifyFiatSupport, isConfigValid, defaultFiatMarket }
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ const { ORDER_TYPES } = require('./consts')
|
||||||
const ORDER_TYPE = ORDER_TYPES.MARKET
|
const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
|
const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
|
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['EUR']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
|
||||||
const FIAT = ['USD']
|
const FIAT = ['USD']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'USD'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 8
|
const AMOUNT_PRECISION = 8
|
||||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
|
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
|
||||||
|
|
||||||
|
|
@ -18,4 +19,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, DEFAULT_FIAT_MARKET, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 8
|
const AMOUNT_PRECISION = 8
|
||||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId']
|
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -19,4 +20,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
const { utils: coinUtils } = require('@lamassu/coins')
|
const { utils: coinUtils } = require('@lamassu/coins')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const ccxt = require('ccxt')
|
const ccxt = require('ccxt')
|
||||||
|
const mem = require('mem')
|
||||||
|
|
||||||
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
|
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
|
||||||
const { ORDER_TYPES } = require('./consts')
|
const { ORDER_TYPES } = require('./consts')
|
||||||
|
const logger = require('../../logger')
|
||||||
|
const { currencies } = require('../../new-admin/config')
|
||||||
|
const T = require('../../time')
|
||||||
|
|
||||||
const DEFAULT_PRICE_PRECISION = 2
|
const DEFAULT_PRICE_PRECISION = 2
|
||||||
const DEFAULT_AMOUNT_PRECISION = 8
|
const DEFAULT_AMOUNT_PRECISION = 8
|
||||||
|
|
@ -18,7 +22,8 @@ function trade (side, account, tradeEntry, exchangeName) {
|
||||||
const { USER_REF, loadOptions, loadConfig = _.noop, REQUIRED_CONFIG_FIELDS, ORDER_TYPE, AMOUNT_PRECISION } = exchangeConfig
|
const { USER_REF, loadOptions, loadConfig = _.noop, REQUIRED_CONFIG_FIELDS, ORDER_TYPE, AMOUNT_PRECISION } = exchangeConfig
|
||||||
if (!isConfigValid(account, REQUIRED_CONFIG_FIELDS)) throw Error('Invalid config')
|
if (!isConfigValid(account, REQUIRED_CONFIG_FIELDS)) throw Error('Invalid config')
|
||||||
|
|
||||||
const symbol = buildMarket(fiatCode, cryptoCode, exchangeName)
|
const selectedFiatMarket = account.currencyMarket
|
||||||
|
const symbol = buildMarket(selectedFiatMarket, cryptoCode, exchangeName)
|
||||||
const precision = _.defaultTo(DEFAULT_AMOUNT_PRECISION, AMOUNT_PRECISION)
|
const precision = _.defaultTo(DEFAULT_AMOUNT_PRECISION, AMOUNT_PRECISION)
|
||||||
const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
|
const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
|
||||||
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
|
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
|
||||||
|
|
@ -50,4 +55,38 @@ function calculatePrice (side, amount, orderBook) {
|
||||||
throw new Error('Insufficient market depth')
|
throw new Error('Insufficient market depth')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { trade }
|
function _getMarkets (exchangeName, availableCryptos) {
|
||||||
|
const prunedCryptos = _.compose(_.uniq, _.map(coinUtils.getEquivalentCode))(availableCryptos)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const exchange = new ccxt[exchangeName]()
|
||||||
|
const cryptosToQuoteAgainst = ['USDT']
|
||||||
|
const currencyCodes = _.concat(_.map(it => it.code, currencies), cryptosToQuoteAgainst)
|
||||||
|
|
||||||
|
return exchange.fetchMarkets()
|
||||||
|
.then(_.filter(it => (it.type === 'spot' || it.spot)))
|
||||||
|
.then(res =>
|
||||||
|
_.reduce((acc, value) => {
|
||||||
|
if (_.includes(value.base, prunedCryptos) && _.includes(value.quote, currencyCodes)) {
|
||||||
|
if (value.quote === value.base) return acc
|
||||||
|
|
||||||
|
if (_.isNil(acc[value.quote])) {
|
||||||
|
return { ...acc, [value.quote]: [value.base] }
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[value.quote].push(value.base)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {}, res)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`No CCXT exchange found for ${exchangeName}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMarkets = mem(_getMarkets, {
|
||||||
|
maxAge: T.week,
|
||||||
|
cacheKey: (exchangeName, availableCryptos) => exchangeName
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = { trade, getMarkets }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, USDT, TRX, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, USDT, TRX, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.LIMIT
|
||||||
const { BTC, ETH, USDT, LN } = COINS
|
const { BTC, ETH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, USDT, LN]
|
const CRYPTO = [BTC, ETH, USDT, LN]
|
||||||
const FIAT = ['USD']
|
const FIAT = ['USD']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'USD'
|
||||||
const AMOUNT_PRECISION = 4
|
const AMOUNT_PRECISION = 4
|
||||||
const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId']
|
const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -21,4 +22,4 @@ const loadConfig = (account) => {
|
||||||
}
|
}
|
||||||
const loadOptions = ({ walletId }) => ({ walletId })
|
const loadOptions = ({ walletId }) => ({ walletId })
|
||||||
|
|
||||||
module.exports = { loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT, TRX, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT, TRX, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 6
|
const AMOUNT_PRECISION = 6
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
const USER_REF = 'userref'
|
const USER_REF = 'userref'
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
|
|
@ -26,4 +27,4 @@ const loadConfig = (account) => {
|
||||||
|
|
||||||
const loadOptions = () => ({ expiretm: '+60' })
|
const loadOptions = () => ({ expiretm: '+60' })
|
||||||
|
|
||||||
module.exports = { USER_REF, loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { USER_REF, loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const ccxt = require('ccxt')
|
const ccxt = require('ccxt')
|
||||||
|
|
||||||
const BN = require('../../bn')
|
const BN = require('../../bn')
|
||||||
const { buildMarket, verifyFiatSupport } = require('../common/ccxt')
|
const { buildMarket, verifyFiatSupport, defaultFiatMarket } = require('../common/ccxt')
|
||||||
const { getRate } = require('../../../lib/forex')
|
const { getRate } = require('../../../lib/forex')
|
||||||
|
|
||||||
const RETRIES = 2
|
const RETRIES = 2
|
||||||
|
|
@ -33,7 +33,7 @@ function ticker (fiatCode, cryptoCode, tickerName) {
|
||||||
return getRate(RETRIES, fiatCode)
|
return getRate(RETRIES, fiatCode)
|
||||||
.then(({ fxRate }) => {
|
.then(({ fxRate }) => {
|
||||||
try {
|
try {
|
||||||
return getCurrencyRates(ticker, 'USD', cryptoCode)
|
return getCurrencyRates(ticker, defaultFiatMarket(tickerName), cryptoCode)
|
||||||
.then(res => ({
|
.then(res => ({
|
||||||
rates: {
|
rates: {
|
||||||
ask: res.rates.ask.times(fxRate),
|
ask: res.rates.ask.times(fxRate),
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ const nocache = require('nocache')
|
||||||
|
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
|
|
||||||
|
const addRWBytes = require('./middlewares/addRWBytes')
|
||||||
const authorize = require('./middlewares/authorize')
|
const authorize = require('./middlewares/authorize')
|
||||||
|
const computeSchema = require('./middlewares/compute-schema')
|
||||||
const errorHandler = require('./middlewares/errorHandler')
|
const errorHandler = require('./middlewares/errorHandler')
|
||||||
const filterOldRequests = require('./middlewares/filterOldRequests')
|
const filterOldRequests = require('./middlewares/filterOldRequests')
|
||||||
const computeSchema = require('./middlewares/compute-schema')
|
|
||||||
const findOperatorId = require('./middlewares/operatorId')
|
const findOperatorId = require('./middlewares/operatorId')
|
||||||
const populateDeviceId = require('./middlewares/populateDeviceId')
|
const populateDeviceId = require('./middlewares/populateDeviceId')
|
||||||
const populateSettings = require('./middlewares/populateSettings')
|
const populateSettings = require('./middlewares/populateSettings')
|
||||||
|
|
@ -50,11 +51,24 @@ const configRequiredRoutes = [
|
||||||
]
|
]
|
||||||
|
|
||||||
// middleware setup
|
// middleware setup
|
||||||
|
app.use(addRWBytes())
|
||||||
app.use(compression({ threshold: 500 }))
|
app.use(compression({ threshold: 500 }))
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
app.use(nocache())
|
app.use(nocache())
|
||||||
app.use(express.json({ limit: '2mb' }))
|
app.use(express.json({ limit: '2mb' }))
|
||||||
app.use(morgan(':method :url :status :response-time ms -- :req[content-length]/:res[content-length] b', { stream: logger.stream }))
|
|
||||||
|
morgan.token('bytesRead', (_req, res) => res.bytesRead)
|
||||||
|
morgan.token('bytesWritten', (_req, res) => res.bytesWritten)
|
||||||
|
app.use(morgan(':method :url :status :response-time ms -- :bytesRead/:bytesWritten B', { stream: logger.stream }))
|
||||||
|
|
||||||
|
app.use('/robots.txt', (req, res) => {
|
||||||
|
res.type('text/plain')
|
||||||
|
res.send("User-agent: *\nDisallow: /")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.sendStatus(404)
|
||||||
|
})
|
||||||
|
|
||||||
// app /pair and /ca routes
|
// app /pair and /ca routes
|
||||||
app.use('/', pairingRoutes)
|
app.use('/', pairingRoutes)
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,41 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
const cashbox = require('../cashbox-batches')
|
const cashbox = require('../cashbox-batches')
|
||||||
const notifier = require('../notifier')
|
const notifier = require('../notifier')
|
||||||
const { getMachine, setMachine } = require('../machine-loader')
|
const { getMachine, setMachine, getMachineName } = require('../machine-loader')
|
||||||
const { loadLatestConfig } = require('../new-settings-loader')
|
const { loadLatestConfig } = require('../new-settings-loader')
|
||||||
const { getCashInSettings } = require('../new-config-manager')
|
const { getCashInSettings } = require('../new-config-manager')
|
||||||
const { AUTOMATIC } = require('../constants')
|
const { AUTOMATIC } = require('../constants')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
function notifyCashboxRemoval (req, res, next) {
|
|
||||||
|
function cashboxRemoval (req, res, next) {
|
||||||
const operatorId = res.locals.operatorId
|
const operatorId = res.locals.operatorId
|
||||||
|
|
||||||
logger.info(`** DEBUG ** - Cashbox removal - Received a cashbox opening request from device ${req.deviceId}`)
|
notifier.cashboxNotify(req.deviceId).catch(logger.error)
|
||||||
|
|
||||||
return notifier.cashboxNotify(req.deviceId)
|
return Promise.all([getMachine(req.deviceId), loadLatestConfig()])
|
||||||
.then(() => Promise.all([getMachine(req.deviceId), loadLatestConfig()]))
|
|
||||||
.then(([machine, config]) => {
|
.then(([machine, config]) => {
|
||||||
logger.info('** DEBUG ** - Cashbox removal - Retrieving system options for cash-in')
|
|
||||||
const cashInSettings = getCashInSettings(config)
|
const cashInSettings = getCashInSettings(config)
|
||||||
if (cashInSettings.cashboxReset !== AUTOMATIC) {
|
if (cashInSettings.cashboxReset !== AUTOMATIC) {
|
||||||
logger.info('** DEBUG ** - Cashbox removal - Cashbox reset is set to manual. A cashbox batch will NOT be created')
|
return Promise.all([
|
||||||
logger.info(`** DEBUG ** - Cashbox removal - Process finished`)
|
cashbox.getMachineUnbatchedBills(req.deviceId),
|
||||||
return res.status(200).send({ status: 'OK' })
|
getMachineName(req.deviceId)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
logger.info('** DEBUG ** - Cashbox removal - Cashbox reset is set to automatic. A cashbox batch WILL be created')
|
return cashbox.createCashboxBatch(req.deviceId, machine.cashbox)
|
||||||
logger.info('** DEBUG ** - Cashbox removal - Creating new batch...')
|
.then(batch => Promise.all([
|
||||||
return cashbox.createCashboxBatch(req.deviceId, machine.cashUnits.cashbox)
|
cashbox.getBatchById(batch.id),
|
||||||
.then(() => {
|
getMachineName(batch.device_id),
|
||||||
logger.info(`** DEBUG ** - Cashbox removal - Process finished`)
|
setMachine({ deviceId: req.deviceId, action: 'emptyCashInBills' }, operatorId)
|
||||||
return res.status(200).send({ status: 'OK' })
|
]))
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
.then(([batch, machineName]) => res.status(200).send({ batch: _.merge(batch, { machineName }), status: 'OK' }))
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post('/removal', notifyCashboxRemoval)
|
router.post('/removal', cashboxRemoval)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ function updateCustomer (req, res, next) {
|
||||||
.then(_.merge(patch))
|
.then(_.merge(patch))
|
||||||
.then(newPatch => customers.updatePhotoCard(id, newPatch))
|
.then(newPatch => customers.updatePhotoCard(id, newPatch))
|
||||||
.then(newPatch => customers.updateFrontCamera(id, newPatch))
|
.then(newPatch => customers.updateFrontCamera(id, newPatch))
|
||||||
.then(newPatch => customers.update(id, newPatch, null, txId))
|
.then(newPatch => customers.update(id, newPatch, null))
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
createPendingManualComplianceNotifs(settings, customer, deviceId)
|
createPendingManualComplianceNotifs(settings, customer, deviceId)
|
||||||
respond(req, res, { customer })
|
respond(req, res, { customer })
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ function poll (req, res, next) {
|
||||||
locale,
|
locale,
|
||||||
version,
|
version,
|
||||||
receiptPrintingActive: receipt.active,
|
receiptPrintingActive: receipt.active,
|
||||||
|
automaticReceiptPrint: receipt.automaticPrint,
|
||||||
smsReceiptActive: receipt.sms,
|
smsReceiptActive: receipt.sms,
|
||||||
enablePaperWalletOnly,
|
enablePaperWalletOnly,
|
||||||
twoWayMode: cashOutConfig.active,
|
twoWayMode: cashOutConfig.active,
|
||||||
|
|
|
||||||
44
lib/sanctions.js
Normal file
44
lib/sanctions.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const ofac = require('./ofac')
|
||||||
|
const T = require('./time')
|
||||||
|
const logger = require('./logger')
|
||||||
|
const customers = require('./customers')
|
||||||
|
|
||||||
|
const sanctionStatus = {
|
||||||
|
loaded: false,
|
||||||
|
timestamp: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadOrUpdateSanctions = () => {
|
||||||
|
if (!sanctionStatus.loaded || (sanctionStatus.timestamp && Date.now() > sanctionStatus.timestamp + T.day)) {
|
||||||
|
logger.info('No sanction lists loaded. Loading sanctions...')
|
||||||
|
return ofac.load()
|
||||||
|
.then(() => {
|
||||||
|
logger.info('OFAC sanction list loaded!')
|
||||||
|
sanctionStatus.loaded = true
|
||||||
|
sanctionStatus.timestamp = Date.now()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
logger.error('Couldn\'t load OFAC sanction list!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkByUser = (customerId, userToken) => {
|
||||||
|
return Promise.all([loadOrUpdateSanctions(), customers.getCustomerById(customerId)])
|
||||||
|
.then(([, customer]) => {
|
||||||
|
const { firstName, lastName, dateOfBirth } = customer?.idCardData
|
||||||
|
const birthdate = _.replace(/-/g, '')(dateOfBirth)
|
||||||
|
const ofacMatches = ofac.match({ firstName, lastName }, birthdate, { threshold: 0.85, fullNameThreshold: 0.95, debug: false })
|
||||||
|
const isOfacSanctioned = _.size(ofacMatches) > 0
|
||||||
|
customers.updateCustomer(customerId, { sanctions: !isOfacSanctioned }, userToken)
|
||||||
|
|
||||||
|
return { ofacSanctioned: isOfacSanctioned }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
checkByUser
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ function massage (tx, pi) {
|
||||||
: {
|
: {
|
||||||
cryptoAtoms: new BN(r.cryptoAtoms),
|
cryptoAtoms: new BN(r.cryptoAtoms),
|
||||||
fiat: new BN(r.fiat),
|
fiat: new BN(r.fiat),
|
||||||
|
fixedFee: r.cashOutFee ? new BN(r.cashOutFee) : null,
|
||||||
rawTickerPrice: r.rawTickerPrice ? new BN(r.rawTickerPrice) : null,
|
rawTickerPrice: r.rawTickerPrice ? new BN(r.rawTickerPrice) : null,
|
||||||
commissionPercentage: new BN(r.commissionPercentage)
|
commissionPercentage: new BN(r.commissionPercentage)
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +51,9 @@ function massage (tx, pi) {
|
||||||
const mapper = _.flow(
|
const mapper = _.flow(
|
||||||
transformDates,
|
transformDates,
|
||||||
mapBN,
|
mapBN,
|
||||||
_.unset('dirty'))
|
_.unset('dirty'),
|
||||||
|
_.unset('cashOutFee')
|
||||||
|
)
|
||||||
|
|
||||||
return mapper(tx)
|
return mapper(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +72,7 @@ function cancel (txId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function customerHistory (customerId, thresholdDays) {
|
function customerHistory (customerId, thresholdDays) {
|
||||||
const sql = `SELECT * FROM (
|
const sql = `SELECT ch.id, ch.created, ch.fiat, ch.direction FROM (
|
||||||
SELECT txIn.id, txIn.created, txIn.fiat, 'cashIn' AS direction,
|
SELECT txIn.id, txIn.created, txIn.fiat, 'cashIn' AS direction,
|
||||||
((NOT txIn.send_confirmed) AND (txIn.created <= now() - interval $3)) AS expired
|
((NOT txIn.send_confirmed) AND (txIn.created <= now() - interval $3)) AS expired
|
||||||
FROM cash_in_txs txIn
|
FROM cash_in_txs txIn
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
const db = require('./db')
|
||||||
|
|
||||||
|
exports.up = next => db.multi([
|
||||||
|
'ALTER TABLE cash_out_txs ADD COLUMN fixed_fee numeric(14, 5) NOT NULL DEFAULT 0;'
|
||||||
|
], next)
|
||||||
|
|
||||||
|
exports.down = next => next()
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
const { saveConfig } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = next => saveConfig({ 'commissions_cashOutFixedFee': 0 })
|
||||||
|
.then(next)
|
||||||
|
.catch(next)
|
||||||
|
|
||||||
|
exports.down = next => next()
|
||||||
30
migrations/1732874039534-market-currency.js
Normal file
30
migrations/1732874039534-market-currency.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const { loadLatest, saveAccounts } = require('../lib/new-settings-loader')
|
||||||
|
const { ACCOUNT_LIST } = require('../lib/new-admin/config/accounts')
|
||||||
|
const { ALL } = require('../lib/plugins/common/ccxt')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
return loadLatest()
|
||||||
|
.then(({ accounts }) => {
|
||||||
|
const allExchanges = _.map(it => it.code)(_.filter(it => it.class === 'exchange', ACCOUNT_LIST))
|
||||||
|
const configuredExchanges = _.intersection(allExchanges, _.keys(accounts))
|
||||||
|
|
||||||
|
const newAccounts = _.reduce(
|
||||||
|
(acc, value) => {
|
||||||
|
if (!_.isNil(accounts[value].currencyMarket)) return acc
|
||||||
|
if (_.includes('EUR', ALL[value].FIAT)) return { ...acc, [value]: { currencyMarket: 'EUR' } }
|
||||||
|
return { ...acc, [value]: { currencyMarket: ALL[value].DEFAULT_FIAT_CURRENCY } }
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
configuredExchanges
|
||||||
|
)
|
||||||
|
|
||||||
|
return saveAccounts(newAccounts)
|
||||||
|
})
|
||||||
|
.then(next)
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
18
migrations/1732881489395-coin-agnostic-blacklist.js
Normal file
18
migrations/1732881489395-coin-agnostic-blacklist.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
var sql = [
|
||||||
|
`CREATE TABLE blacklist_temp (
|
||||||
|
address TEXT NOT NULL UNIQUE
|
||||||
|
)`,
|
||||||
|
`INSERT INTO blacklist_temp (address) SELECT DISTINCT address FROM blacklist`,
|
||||||
|
`DROP TABLE blacklist`,
|
||||||
|
`ALTER TABLE blacklist_temp RENAME TO blacklist`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
24
migrations/1732881489396-advanced-blacklisting.js
Normal file
24
migrations/1732881489396-advanced-blacklisting.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
const uuid = require('uuid')
|
||||||
|
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const defaultMessageId = uuid.v4()
|
||||||
|
|
||||||
|
var sql = [
|
||||||
|
`CREATE TABLE blacklist_messages (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
allow_toggle BOOLEAN NOT NULL DEFAULT true
|
||||||
|
)`,
|
||||||
|
`INSERT INTO blacklist_messages (id, label, content, allow_toggle) VALUES ('${defaultMessageId}', 'Suspicious address', 'This address may be associated with a deceptive offer or a prohibited group. Please make sure you''re using an address from your own wallet.', false)`,
|
||||||
|
`ALTER TABLE blacklist ADD COLUMN blacklist_message_id UUID REFERENCES blacklist_messages(id) NOT NULL DEFAULT '${defaultMessageId}'`
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
20
migrations/1732881659436-rates-screen.js
Normal file
20
migrations/1732881659436-rates-screen.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const newConfig = {}
|
||||||
|
return loadLatest()
|
||||||
|
.then(({ config }) => {
|
||||||
|
if (!_.isNil(config.machineScreens_rates_active)) return
|
||||||
|
newConfig[`machineScreens_rates_active`] = true
|
||||||
|
return saveConfig(newConfig)
|
||||||
|
})
|
||||||
|
.then(next)
|
||||||
|
.catch(err => {
|
||||||
|
return next(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,3 @@ SKIP_PREFLIGHT_CHECK=true
|
||||||
HTTPS=true
|
HTTPS=true
|
||||||
REACT_APP_TYPE_CHECK_SANCTUARY=false
|
REACT_APP_TYPE_CHECK_SANCTUARY=false
|
||||||
PORT=3001
|
PORT=3001
|
||||||
REACT_APP_BUILD_TARGET=LAMASSU
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['react-app', 'prettier-standard', 'prettier/react'],
|
|
||||||
plugins: ['import'],
|
|
||||||
settings: {
|
|
||||||
'import/resolver': {
|
|
||||||
alias: [['src', './src']]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'import/no-anonymous-default-export': [2, { allowObject: true }],
|
|
||||||
'import/order': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
groups: [
|
|
||||||
'builtin',
|
|
||||||
'external',
|
|
||||||
'internal',
|
|
||||||
'parent',
|
|
||||||
'sibling',
|
|
||||||
'index'
|
|
||||||
],
|
|
||||||
alphabetize: {
|
|
||||||
order: 'asc'
|
|
||||||
},
|
|
||||||
'newlines-between': 'always'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
new-lamassu-admin/.prettierrc
Normal file
8
new-lamassu-admin/.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSameLine": true
|
||||||
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
stories: ['../src/stories/index.js'],
|
|
||||||
addons: [
|
|
||||||
'@storybook/addon-actions',
|
|
||||||
'@storybook/addon-links',
|
|
||||||
'@storybook/addon-knobs',
|
|
||||||
'@storybook/addon-backgrounds',
|
|
||||||
'@storybook/preset-create-react-app'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1
new-lamassu-admin/.tool-versions
Normal file
1
new-lamassu-admin/.tool-versions
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
nodejs 22
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
|
||||||
|
|
||||||
## Dev Environment
|
## Dev Environment
|
||||||
|
|
||||||
### formatting
|
### formatting
|
||||||
|
|
@ -10,16 +8,6 @@ The configuration for vscode is already on the repo, all you need to do is insta
|
||||||
This project has a husky pre commit hook to format the staged changes using our styleguide.
|
This project has a husky pre commit hook to format the staged changes using our styleguide.
|
||||||
To take advantage of that make sure to run `git commit` from within this folder.
|
To take advantage of that make sure to run `git commit` from within this folder.
|
||||||
|
|
||||||
### Sanctuary
|
|
||||||
|
|
||||||
Sanctuary has a runtime typechecker that can make be quite slow, but its turned off by default.
|
|
||||||
|
|
||||||
To turn it on add the following line to a `.env.local` file.
|
|
||||||
|
|
||||||
```
|
|
||||||
REACT_APP_TYPE_CHECK_SANCTUARY=true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Available Scripts
|
## Available Scripts
|
||||||
|
|
||||||
In the project directory, you can run:
|
In the project directory, you can run:
|
||||||
|
|
@ -36,10 +24,6 @@ You will also see any lint errors in the console.
|
||||||
|
|
||||||
Runs eslint --fix on the src folder
|
Runs eslint --fix on the src folder
|
||||||
|
|
||||||
### `npm storybook`
|
|
||||||
|
|
||||||
Runs the storybook server
|
|
||||||
|
|
||||||
### `npm test`
|
### `npm test`
|
||||||
|
|
||||||
Launches the test runner in the interactive watch mode.<br>
|
Launches the test runner in the interactive watch mode.<br>
|
||||||
|
|
|
||||||
39
new-lamassu-admin/eslint.config.js
Normal file
39
new-lamassu-admin/eslint.config.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import globals from 'globals'
|
||||||
|
import pluginJs from '@eslint/js'
|
||||||
|
import pluginReact from 'eslint-plugin-react'
|
||||||
|
import reactCompiler from 'eslint-plugin-react-compiler'
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs,jsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
...pluginReact.configs.flat.recommended.languageOptions,
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
process: 'readonly'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: '16'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-compiler': reactCompiler
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
pluginReact.configs.flat.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react/display-name': 'off',
|
||||||
|
'react/no-unescaped-entities': 'off',
|
||||||
|
'react-compiler/react-compiler': 'warn'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
eslintConfigPrettier
|
||||||
|
]
|
||||||
20
new-lamassu-admin/index.html
Normal file
20
new-lamassu-admin/index.html
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
|
/>
|
||||||
|
<meta name="robots" content="noindex"/>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<title>Lamassu Admin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root" class="root"></div>
|
||||||
|
<script type="module" src="/src/index.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33530
new-lamassu-admin/package-lock.json
generated
33530
new-lamassu-admin/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,12 +2,13 @@
|
||||||
"name": "lamassu-admin",
|
"name": "lamassu-admin",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"license": "../LICENSE",
|
"license": "../LICENSE",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/react-hooks": "^3.1.3",
|
"@apollo/react-hooks": "^3.1.3",
|
||||||
"@lamassu/coins": "v1.4.0-beta.4",
|
"@lamassu/coins": "v1.5.3",
|
||||||
"@material-ui/core": "4.11.0",
|
"@material-ui/core": "4.12.4",
|
||||||
"@material-ui/icons": "4.9.1",
|
"@material-ui/icons": "4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.56",
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||||
"@simplewebauthn/browser": "^3.0.0",
|
"@simplewebauthn/browser": "^3.0.0",
|
||||||
"@use-hooks/axios": "1.3.0",
|
"@use-hooks/axios": "1.3.0",
|
||||||
"apollo-cache-inmemory": "^1.6.6",
|
"apollo-cache-inmemory": "^1.6.6",
|
||||||
|
|
@ -27,12 +28,11 @@
|
||||||
"downshift": "3.3.4",
|
"downshift": "3.3.4",
|
||||||
"file-saver": "2.0.2",
|
"file-saver": "2.0.2",
|
||||||
"formik": "2.2.0",
|
"formik": "2.2.0",
|
||||||
"google-libphonenumber": "^3.2.22",
|
|
||||||
"graphql": "^14.5.8",
|
"graphql": "^14.5.8",
|
||||||
"graphql-tag": "^2.10.3",
|
"graphql-tag": "^2.12.6",
|
||||||
"jss-plugin-extend": "^10.0.0",
|
"jss-plugin-extend": "^10.0.0",
|
||||||
"jszip": "^3.6.0",
|
"jszip": "^3.6.0",
|
||||||
"libphonenumber-js": "^1.7.50",
|
"libphonenumber-js": "^1.11.15",
|
||||||
"match-sorter": "^4.2.0",
|
"match-sorter": "^4.2.0",
|
||||||
"pretty-ms": "^2.1.0",
|
"pretty-ms": "^2.1.0",
|
||||||
"qrcode.react": "0.9.3",
|
"qrcode.react": "0.9.3",
|
||||||
|
|
@ -41,69 +41,34 @@
|
||||||
"react-copy-to-clipboard": "^5.0.2",
|
"react-copy-to-clipboard": "^5.0.2",
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-dropzone": "^11.4.2",
|
"react-dropzone": "^11.4.2",
|
||||||
"react-material-ui-carousel": "^2.2.7",
|
"react-material-ui-carousel": "^2.3.11",
|
||||||
"react-number-format": "^4.4.1",
|
"react-number-format": "^4.4.1",
|
||||||
"react-otp-input": "^2.3.0",
|
"react-otp-input": "^2.3.0",
|
||||||
"react-router-dom": "5.1.2",
|
"react-router-dom": "5.1.2",
|
||||||
"react-use": "15.3.2",
|
"react-use": "15.3.2",
|
||||||
"react-virtualized": "^9.21.2",
|
"react-virtualized": "^9.21.2",
|
||||||
"sanctuary": "^2.0.1",
|
|
||||||
"ua-parser-js": "^1.0.2",
|
"ua-parser-js": "^1.0.2",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"yup": "0.32.9"
|
"yup": "1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-actions": "6.0.26",
|
"@eslint/js": "^9.16.0",
|
||||||
"@storybook/addon-backgrounds": "6.0.26",
|
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||||
"@storybook/addon-knobs": "6.0.26",
|
"esbuild-plugin-react-virtualized": "^1.0.4",
|
||||||
"@storybook/addon-links": "6.0.26",
|
"eslint": "^9.16.0",
|
||||||
"@storybook/addons": "6.0.26",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"@storybook/preset-create-react-app": "^3.1.4",
|
"eslint-plugin-react": "^7.37.2",
|
||||||
"@storybook/react": "6.0.26",
|
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
||||||
"@welldone-software/why-did-you-render": "^3.3.9",
|
"globals": "^15.13.0",
|
||||||
"eslint": "^7.19.0",
|
"lint-staged": "^15.2.10",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"prettier": "3.4.1",
|
||||||
"eslint-config-prettier-standard": "^3.0.1",
|
"vite": "^6.0.1",
|
||||||
"eslint-config-standard": "^14.1.0",
|
"vite-plugin-svgr": "^4.3.0"
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
|
||||||
"eslint-plugin-import": "^2.22.1",
|
|
||||||
"eslint-plugin-node": "^10.0.0",
|
|
||||||
"eslint-plugin-prettier": "^3.1.2",
|
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
|
||||||
"eslint-plugin-react": "^7.20.3",
|
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
|
||||||
"husky": "^3.1.0",
|
|
||||||
"lint-staged": "^9.5.0",
|
|
||||||
"patch-package": "^6.2.2",
|
|
||||||
"prettier": "1.19.1",
|
|
||||||
"prettier-config-standard": "^1.0.1",
|
|
||||||
"react-scripts": "4.0.0",
|
|
||||||
"serve": "^11.3.2",
|
|
||||||
"source-map-explorer": "^2.4.2"
|
|
||||||
},
|
|
||||||
"husky": {
|
|
||||||
"hooks": {
|
|
||||||
"pre-commit": "lint-staged"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
|
||||||
"eslint --fix",
|
|
||||||
"git add"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "vite",
|
||||||
"fix": "eslint --fix --ext .js,.md,.json src/",
|
"build": "vite build",
|
||||||
"build": "react-scripts build",
|
"preview": "vite preview"
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"storybook": "start-storybook -p 9009 -s public",
|
|
||||||
"postinstall": "patch-package",
|
|
||||||
"build-storybook": "build-storybook -s public",
|
|
||||||
"lamassu": "REACT_APP_BUILD_TARGET=LAMASSU react-scripts start",
|
|
||||||
"pazuz": "REACT_APP_BUILD_TARGET=PAZUZ react-scripts start"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
@ -116,5 +81,9 @@
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx,md,json}": "eslint --cache --fix",
|
||||||
|
"*.{js,jsx,css,md,json}": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
||||||
/>
|
|
||||||
<meta name="robots" content="noindex"/>
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>Lamassu Admin</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root" class="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
2
new-lamassu-admin/public/robots.txt
Normal file
2
new-lamassu-admin/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -17,21 +17,16 @@ import {
|
||||||
useHistory,
|
useHistory,
|
||||||
BrowserRouter as Router
|
BrowserRouter as Router
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
|
|
||||||
import AppContext from 'src/AppContext'
|
|
||||||
import Header from 'src/components/layout/Header'
|
import Header from 'src/components/layout/Header'
|
||||||
import Sidebar from 'src/components/layout/Sidebar'
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { tree, hasSidebar, Routes, getParent } from 'src/routing/routes'
|
import { tree, hasSidebar, Routes, getParent } from 'src/routing/routes'
|
||||||
|
import ApolloProvider from 'src/utils/apollo'
|
||||||
|
|
||||||
|
import AppContext from 'src/AppContext'
|
||||||
import global from 'src/styling/global'
|
import global from 'src/styling/global'
|
||||||
import theme from 'src/styling/theme'
|
import theme from 'src/styling/theme'
|
||||||
import { backgroundColor, mainWidth } from 'src/styling/variables'
|
import { backgroundColor, mainWidth } from 'src/styling/variables'
|
||||||
import ApolloProvider from 'src/utils/apollo'
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const whyDidYouRender = require('@welldone-software/why-did-you-render')
|
|
||||||
whyDidYouRender(React)
|
|
||||||
}
|
|
||||||
|
|
||||||
const jss = create({
|
const jss = create({
|
||||||
plugins: [extendJss(), ...jssPreset().plugins]
|
plugins: [extendJss(), ...jssPreset().plugins]
|
||||||
|
|
@ -120,17 +115,11 @@ const Main = () => {
|
||||||
)}
|
)}
|
||||||
<main className={classes.wrapper}>
|
<main className={classes.wrapper}>
|
||||||
{sidebar && !is404 && wizardTested && (
|
{sidebar && !is404 && wizardTested && (
|
||||||
<Slide
|
<Slide direction="left" in={true} mountOnEnter unmountOnExit>
|
||||||
direction="left"
|
<div>
|
||||||
in={true}
|
<TitleSection title={parent.title}></TitleSection>
|
||||||
mountOnEnter
|
</div>
|
||||||
unmountOnExit
|
</Slide>
|
||||||
children={
|
|
||||||
<div>
|
|
||||||
<TitleSection title={parent.title}></TitleSection>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Grid container className={classes.grid}>
|
<Grid container className={classes.grid}>
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
import ReactCarousel from 'react-material-ui-carousel'
|
import ReactCarousel from 'react-material-ui-carousel'
|
||||||
|
import LeftArrow from 'src/styling/icons/arrow/carousel-left-arrow.svg?react'
|
||||||
import { ReactComponent as LeftArrow } from 'src/styling/icons/arrow/carousel-left-arrow.svg'
|
import RightArrow from 'src/styling/icons/arrow/carousel-right-arrow.svg?react'
|
||||||
import { ReactComponent as RightArrow } from 'src/styling/icons/arrow/carousel-right-arrow.svg'
|
|
||||||
import { URI } from 'src/utils/apollo'
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
imgWrapper: {
|
imgWrapper: {
|
||||||
|
|
@ -13,9 +11,10 @@ const useStyles = makeStyles({
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
},
|
||||||
imgInner: {
|
imgInner: {
|
||||||
objectFit: 'cover',
|
objectFit: 'contain',
|
||||||
objectPosition: 'center',
|
objectPosition: 'center',
|
||||||
width: 500,
|
width: 500,
|
||||||
|
height: 400,
|
||||||
marginBottom: 40
|
marginBottom: 40
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -48,11 +47,11 @@ export const Carousel = memo(({ photosData, slidePhoto }) => {
|
||||||
next={activeIndex => slidePhoto(activeIndex)}
|
next={activeIndex => slidePhoto(activeIndex)}
|
||||||
prev={activeIndex => slidePhoto(activeIndex)}>
|
prev={activeIndex => slidePhoto(activeIndex)}>
|
||||||
{photosData.map((item, i) => (
|
{photosData.map((item, i) => (
|
||||||
<div>
|
<div key={i}>
|
||||||
<div className={classes.imgWrapper}>
|
<div className={classes.imgWrapper}>
|
||||||
<img
|
<img
|
||||||
className={classes.imgInner}
|
className={classes.imgInner}
|
||||||
src={`${URI}/${item?.photoDir}/${item?.path}`}
|
src={`/${item?.photoDir}/${item?.path}`}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -6,11 +6,11 @@ import {
|
||||||
InputLabel
|
InputLabel
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import React, { memo, useState } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
|
import { H4, P } from 'src/components/typography'
|
||||||
|
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
|
||||||
|
|
||||||
import { Button, IconButton } from 'src/components/buttons'
|
import { Button, IconButton } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs'
|
import { TextInput } from 'src/components/inputs'
|
||||||
import { H4, P } from 'src/components/typography'
|
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
|
||||||
import { spacer } from 'src/styling/variables'
|
import { spacer } from 'src/styling/variables'
|
||||||
|
|
||||||
import ErrorMessage from './ErrorMessage'
|
import ErrorMessage from './ErrorMessage'
|
||||||
|
|
@ -1,98 +1,98 @@
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
makeStyles
|
makeStyles
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { H4, P } from 'src/components/typography'
|
||||||
import { Button, IconButton } from 'src/components/buttons'
|
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
|
||||||
import { H4, P } from 'src/components/typography'
|
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
import { Button, IconButton } from 'src/components/buttons'
|
||||||
import { spacer } from 'src/styling/variables'
|
import { spacer } from 'src/styling/variables'
|
||||||
|
|
||||||
import ErrorMessage from './ErrorMessage'
|
import ErrorMessage from './ErrorMessage'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
content: {
|
content: {
|
||||||
width: 434,
|
width: 434,
|
||||||
padding: spacer * 2,
|
padding: spacer * 2,
|
||||||
paddingRight: spacer * 3.5
|
paddingRight: spacer * 3.5
|
||||||
},
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
padding: spacer * 2,
|
padding: spacer * 2,
|
||||||
paddingRight: spacer * 1.5,
|
paddingRight: spacer * 1.5,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
margin: 0
|
margin: 0
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
padding: spacer * 4,
|
padding: spacer * 4,
|
||||||
paddingTop: spacer * 2
|
paddingTop: spacer * 2
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
margin: 0
|
margin: 0
|
||||||
},
|
},
|
||||||
closeButton: {
|
closeButton: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
marginTop: -(spacer / 2)
|
marginTop: -(spacer / 2)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const DialogTitle = ({ children, close }) => {
|
export const DialogTitle = ({ children, close }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
return (
|
return (
|
||||||
<div className={classes.titleSection}>
|
<div className={classes.titleSection}>
|
||||||
{children}
|
{children}
|
||||||
{close && (
|
{close && (
|
||||||
<IconButton
|
<IconButton
|
||||||
size={16}
|
size={16}
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={close}
|
onClick={close}
|
||||||
className={classes.closeButton}>
|
className={classes.closeButton}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDialog = ({
|
export const DeleteDialog = ({
|
||||||
title = 'Confirm Delete',
|
title = 'Confirm Delete',
|
||||||
open = false,
|
open = false,
|
||||||
onConfirmed,
|
onConfirmed,
|
||||||
onDismissed,
|
onDismissed,
|
||||||
item = 'item',
|
item = 'item',
|
||||||
confirmationMessage = `Are you sure you want to delete this ${item}?`,
|
confirmationMessage = `Are you sure you want to delete this ${item}?`,
|
||||||
extraMessage,
|
extraMessage,
|
||||||
errorMessage = ''
|
errorMessage = ''
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} aria-labelledby="form-dialog-title">
|
<Dialog open={open} aria-labelledby="form-dialog-title">
|
||||||
<DialogTitle close={() => onDismissed()}>
|
<DialogTitle close={() => onDismissed()}>
|
||||||
<H4 className={classes.title}>{title}</H4>
|
<H4 className={classes.title}>{title}</H4>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<ErrorMessage>
|
<ErrorMessage>
|
||||||
{errorMessage.split(':').map(error => (
|
{errorMessage.split(':').map(error => (
|
||||||
<>
|
<>
|
||||||
{error}
|
{error}
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</ErrorMessage>
|
</ErrorMessage>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
)}
|
)}
|
||||||
<DialogContent className={classes.content}>
|
<DialogContent className={classes.content}>
|
||||||
{confirmationMessage && <P>{confirmationMessage}</P>}
|
{confirmationMessage && <P>{confirmationMessage}</P>}
|
||||||
{extraMessage}
|
{extraMessage}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions className={classes.actions}>
|
<DialogActions className={classes.actions}>
|
||||||
<Button onClick={onConfirmed}>Confirm</Button>
|
<Button onClick={onConfirmed}>Confirm</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import ErrorIcon from 'src/styling/icons/warning-icon/tomato.svg?react'
|
||||||
|
|
||||||
import { ReactComponent as ErrorIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
|
||||||
import { errorColor } from 'src/styling/variables'
|
import { errorColor } from 'src/styling/variables'
|
||||||
|
|
||||||
import { Info3 } from './typography'
|
import { Info3 } from './typography'
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { makeStyles, ClickAwayListener } from '@material-ui/core'
|
import { makeStyles, ClickAwayListener } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React, { memo, useState } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
import Popper from 'src/components/Popper'
|
import Popper from 'src/components/Popper'
|
||||||
|
import ZoomIconInverse from 'src/styling/icons/circle buttons/search/white.svg?react'
|
||||||
|
import ZoomIcon from 'src/styling/icons/circle buttons/search/zodiac.svg?react'
|
||||||
|
|
||||||
import { FeatureButton } from 'src/components/buttons'
|
import { FeatureButton } from 'src/components/buttons'
|
||||||
import { ReactComponent as ZoomIconInverse } from 'src/styling/icons/circle buttons/search/white.svg'
|
|
||||||
import { ReactComponent as ZoomIcon } from 'src/styling/icons/circle buttons/search/zodiac.svg'
|
|
||||||
|
|
||||||
import imagePopperStyles from './ImagePopper.styles'
|
import imagePopperStyles from './ImagePopper.styles'
|
||||||
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Box, makeStyles } from '@material-ui/core'
|
import { Box, makeStyles } from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Label1 } from 'src/components/typography'
|
import { Label1 } from 'src/components/typography'
|
||||||
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
|
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
message: ({ width }) => ({
|
message: ({ width }) => ({
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Dialog, DialogContent, makeStyles } from '@material-ui/core'
|
import { Dialog, DialogContent, makeStyles } from '@material-ui/core'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
import { H1 } from 'src/components/typography'
|
||||||
|
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
|
||||||
|
|
||||||
import { IconButton } from 'src/components/buttons'
|
import { IconButton } from 'src/components/buttons'
|
||||||
import { H1 } from 'src/components/typography'
|
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
|
||||||
import { spacer } from 'src/styling/variables'
|
import { spacer } from 'src/styling/variables'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
|
|
@ -5,11 +5,11 @@ import { format, set } from 'date-fns/fp'
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState, useCallback } from 'react'
|
import React, { useState, useCallback } from 'react'
|
||||||
|
import Arrow from 'src/styling/icons/arrow/download_logs.svg?react'
|
||||||
|
import DownloadInverseIcon from 'src/styling/icons/button/download/white.svg?react'
|
||||||
|
import Download from 'src/styling/icons/button/download/zodiac.svg?react'
|
||||||
|
|
||||||
import { FeatureButton, Link } from 'src/components/buttons'
|
import { FeatureButton, Link } from 'src/components/buttons'
|
||||||
import { ReactComponent as Arrow } from 'src/styling/icons/arrow/download_logs.svg'
|
|
||||||
import { ReactComponent as DownloadInverseIcon } from 'src/styling/icons/button/download/white.svg'
|
|
||||||
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
|
|
||||||
import { primaryColor, offColor, zircon } from 'src/styling/variables'
|
import { primaryColor, offColor, zircon } from 'src/styling/variables'
|
||||||
import { formatDate } from 'src/utils/timezones'
|
import { formatDate } from 'src/utils/timezones'
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { makeStyles, Modal as MaterialModal, Paper } from '@material-ui/core'
|
import { makeStyles, Modal as MaterialModal, Paper } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { H1, H4 } from 'src/components/typography'
|
||||||
|
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
|
||||||
|
|
||||||
import { IconButton } from 'src/components/buttons'
|
import { IconButton } from 'src/components/buttons'
|
||||||
import { H1, H4 } from 'src/components/typography'
|
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
modal: {
|
modal: {
|
||||||
|
|
@ -55,8 +55,8 @@ const styles = {
|
||||||
margin: xl
|
margin: xl
|
||||||
? [[0, 0, 'auto', 'auto']]
|
? [[0, 0, 'auto', 'auto']]
|
||||||
: small
|
: small
|
||||||
? [[12, 12, 'auto', 'auto']]
|
? [[12, 12, 'auto', 'auto']]
|
||||||
: [[16, 16, 'auto', 'auto']]
|
: [[16, 16, 'auto', 'auto']]
|
||||||
}),
|
}),
|
||||||
header: {
|
header: {
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
|
|
@ -3,13 +3,12 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import ActionButton from 'src/components/buttons/ActionButton'
|
import ActionButton from 'src/components/buttons/ActionButton'
|
||||||
import { H5 } from 'src/components/typography'
|
import { H5 } from 'src/components/typography'
|
||||||
import { ReactComponent as NotificationIconZodiac } from 'src/styling/icons/menu/notification-zodiac.svg'
|
import NotificationIconZodiac from 'src/styling/icons/menu/notification-zodiac.svg?react'
|
||||||
import { ReactComponent as ClearAllIconInverse } from 'src/styling/icons/stage/spring/empty.svg'
|
import ClearAllIconInverse from 'src/styling/icons/stage/spring/empty.svg?react'
|
||||||
import { ReactComponent as ClearAllIcon } from 'src/styling/icons/stage/zodiac/empty.svg'
|
import ClearAllIcon from 'src/styling/icons/stage/zodiac/empty.svg?react'
|
||||||
import { ReactComponent as ShowUnreadIcon } from 'src/styling/icons/stage/zodiac/full.svg'
|
import ShowUnreadIcon from 'src/styling/icons/stage/zodiac/full.svg?react'
|
||||||
|
|
||||||
import styles from './NotificationCenter.styles'
|
import styles from './NotificationCenter.styles'
|
||||||
import NotificationRow from './NotificationRow'
|
import NotificationRow from './NotificationRow'
|
||||||
|
|
@ -3,11 +3,10 @@ import classnames from 'classnames'
|
||||||
import prettyMs from 'pretty-ms'
|
import prettyMs from 'pretty-ms'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Label1, Label2, TL2 } from 'src/components/typography'
|
import { Label1, Label2, TL2 } from 'src/components/typography'
|
||||||
import { ReactComponent as Wrench } from 'src/styling/icons/action/wrench/zodiac.svg'
|
import Wrench from 'src/styling/icons/action/wrench/zodiac.svg?react'
|
||||||
import { ReactComponent as Transaction } from 'src/styling/icons/arrow/transaction.svg'
|
import Transaction from 'src/styling/icons/arrow/transaction.svg?react'
|
||||||
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
import WarningIcon from 'src/styling/icons/warning-icon/tomato.svg?react'
|
||||||
|
|
||||||
import styles from './NotificationCenter.styles'
|
import styles from './NotificationCenter.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
@ -61,8 +60,8 @@ const NotificationRow = ({
|
||||||
typeDisplay && deviceName
|
typeDisplay && deviceName
|
||||||
? `${typeDisplay} - ${deviceName}`
|
? `${typeDisplay} - ${deviceName}`
|
||||||
: !typeDisplay && deviceName
|
: !typeDisplay && deviceName
|
||||||
? `${deviceName}`
|
? `${deviceName}`
|
||||||
: `${typeDisplay}`
|
: `${typeDisplay}`
|
||||||
|
|
||||||
const iconClass = {
|
const iconClass = {
|
||||||
[classes.readIcon]: read,
|
[classes.readIcon]: read,
|
||||||
|
|
@ -105,20 +105,29 @@ const Popover = ({
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const arrowClasses = {
|
const getArrowClasses = placement => ({
|
||||||
[classes.arrow]: true,
|
[classes.arrow]: true,
|
||||||
[classes.arrowBottom]: props.placement === 'bottom',
|
[classes.arrowBottom]: placement === 'bottom',
|
||||||
[classes.arrowTop]: props.placement === 'top',
|
[classes.arrowTop]: placement === 'top',
|
||||||
[classes.arrowRight]: props.placement === 'right',
|
[classes.arrowRight]: placement === 'right',
|
||||||
[classes.arrowLeft]: props.placement === 'left'
|
[classes.arrowLeft]: placement === 'left'
|
||||||
|
})
|
||||||
|
|
||||||
|
const flipPlacements = {
|
||||||
|
top: ['bottom'],
|
||||||
|
bottom: ['top'],
|
||||||
|
left: ['right'],
|
||||||
|
right: ['left']
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifiers = R.merge(props.modifiers, {
|
const modifiers = R.mergeDeepLeft(props.modifiers, {
|
||||||
flip: {
|
flip: {
|
||||||
enabled: false
|
enabled: R.defaultTo(false, props.flip),
|
||||||
|
allowedAutoPlacements: flipPlacements[props.placement],
|
||||||
|
boundary: 'clippingParents'
|
||||||
},
|
},
|
||||||
preventOverflow: {
|
preventOverflow: {
|
||||||
enabled: true,
|
enabled: R.defaultTo(true, props.preventOverflow),
|
||||||
boundariesElement: 'scrollParent'
|
boundariesElement: 'scrollParent'
|
||||||
},
|
},
|
||||||
offset: {
|
offset: {
|
||||||
|
|
@ -126,7 +135,7 @@ const Popover = ({
|
||||||
offset: '0, 10'
|
offset: '0, 10'
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: {
|
||||||
enabled: true,
|
enabled: R.defaultTo(true, props.showArrow),
|
||||||
element: arrowRef
|
element: arrowRef
|
||||||
},
|
},
|
||||||
computeStyle: {
|
computeStyle: {
|
||||||
|
|
@ -134,6 +143,12 @@ const Popover = ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (props.preventOverflow === false) {
|
||||||
|
modifiers.hide = {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MaterialPopper
|
<MaterialPopper
|
||||||
|
|
@ -141,10 +156,15 @@ const Popover = ({
|
||||||
modifiers={modifiers}
|
modifiers={modifiers}
|
||||||
className={classes.popover}
|
className={classes.popover}
|
||||||
{...props}>
|
{...props}>
|
||||||
<Paper className={classnames(classes.root, className)}>
|
{({ placement }) => (
|
||||||
<span className={classnames(arrowClasses)} ref={setArrowRef} />
|
<Paper className={classnames(classes.root, className)}>
|
||||||
{children}
|
<span
|
||||||
</Paper>
|
className={classnames(getArrowClasses(placement))}
|
||||||
|
ref={setArrowRef}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
</MaterialPopper>
|
</MaterialPopper>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
@ -4,9 +4,8 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import MAutocomplete from '@material-ui/lab/Autocomplete'
|
import MAutocomplete from '@material-ui/lab/Autocomplete'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React, { memo, useState } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
import { P } from 'src/components/typography'
|
import { P } from 'src/components/typography'
|
||||||
import { ReactComponent as SearchIcon } from 'src/styling/icons/circle buttons/search/zodiac.svg'
|
import SearchIcon from 'src/styling/icons/circle buttons/search/zodiac.svg?react'
|
||||||
|
|
||||||
import styles from './SearchBox.styles'
|
import styles from './SearchBox.styles'
|
||||||
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Chip from 'src/components/Chip'
|
import Chip from 'src/components/Chip'
|
||||||
import { ActionButton } from 'src/components/buttons'
|
|
||||||
import { P, Label3 } from 'src/components/typography'
|
import { P, Label3 } from 'src/components/typography'
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
|
||||||
import { ReactComponent as FilterIcon } from 'src/styling/icons/button/filter/white.svg'
|
import FilterIcon from 'src/styling/icons/button/filter/white.svg?react'
|
||||||
import { ReactComponent as ReverseFilterIcon } from 'src/styling/icons/button/filter/zodiac.svg'
|
import ReverseFilterIcon from 'src/styling/icons/button/filter/zodiac.svg?react'
|
||||||
|
|
||||||
|
import { ActionButton } from 'src/components/buttons'
|
||||||
import { onlyFirstToUpper, singularOrPlural } from 'src/utils/string'
|
import { onlyFirstToUpper, singularOrPlural } from 'src/utils/string'
|
||||||
|
|
||||||
import { chipStyles, styles } from './SearchFilter.styles'
|
import { chipStyles, styles } from './SearchFilter.styles'
|
||||||
|
|
@ -2,13 +2,13 @@ import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
import CompleteStageIconSpring from 'src/styling/icons/stage/spring/complete.svg?react'
|
||||||
|
import CurrentStageIconSpring from 'src/styling/icons/stage/spring/current.svg?react'
|
||||||
|
import EmptyStageIconSpring from 'src/styling/icons/stage/spring/empty.svg?react'
|
||||||
|
import CompleteStageIconZodiac from 'src/styling/icons/stage/zodiac/complete.svg?react'
|
||||||
|
import CurrentStageIconZodiac from 'src/styling/icons/stage/zodiac/current.svg?react'
|
||||||
|
import EmptyStageIconZodiac from 'src/styling/icons/stage/zodiac/empty.svg?react'
|
||||||
|
|
||||||
import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg'
|
|
||||||
import { ReactComponent as CurrentStageIconSpring } from 'src/styling/icons/stage/spring/current.svg'
|
|
||||||
import { ReactComponent as EmptyStageIconSpring } from 'src/styling/icons/stage/spring/empty.svg'
|
|
||||||
import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg'
|
|
||||||
import { ReactComponent as CurrentStageIconZodiac } from 'src/styling/icons/stage/zodiac/current.svg'
|
|
||||||
import { ReactComponent as EmptyStageIconZodiac } from 'src/styling/icons/stage/zodiac/empty.svg'
|
|
||||||
import {
|
import {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
|
|
@ -2,13 +2,13 @@ import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
import CompleteStageIconSpring from 'src/styling/icons/stage/spring/complete.svg?react'
|
||||||
|
import CurrentStageIconSpring from 'src/styling/icons/stage/spring/current.svg?react'
|
||||||
|
import EmptyStageIconSpring from 'src/styling/icons/stage/spring/empty.svg?react'
|
||||||
|
import CompleteStageIconZodiac from 'src/styling/icons/stage/zodiac/complete.svg?react'
|
||||||
|
import CurrentStageIconZodiac from 'src/styling/icons/stage/zodiac/current.svg?react'
|
||||||
|
import EmptyStageIconZodiac from 'src/styling/icons/stage/zodiac/empty.svg?react'
|
||||||
|
|
||||||
import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg'
|
|
||||||
import { ReactComponent as CurrentStageIconSpring } from 'src/styling/icons/stage/spring/current.svg'
|
|
||||||
import { ReactComponent as EmptyStageIconSpring } from 'src/styling/icons/stage/spring/empty.svg'
|
|
||||||
import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg'
|
|
||||||
import { ReactComponent as CurrentStageIconZodiac } from 'src/styling/icons/stage/zodiac/current.svg'
|
|
||||||
import { ReactComponent as EmptyStageIconZodiac } from 'src/styling/icons/stage/zodiac/empty.svg'
|
|
||||||
import {
|
import {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { makeStyles, ClickAwayListener } from '@material-ui/core'
|
import { makeStyles, ClickAwayListener } from '@material-ui/core'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState, memo } from 'react'
|
import React, { useState, memo } from 'react'
|
||||||
|
|
||||||
import Popper from 'src/components/Popper'
|
import Popper from 'src/components/Popper'
|
||||||
import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg'
|
import HelpIcon from 'src/styling/icons/action/help/zodiac.svg?react'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
transparentButton: {
|
transparentButton: {
|
||||||
|
|
@ -13,6 +12,16 @@ const useStyles = makeStyles({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
marginTop: 4
|
marginTop: 4
|
||||||
},
|
},
|
||||||
|
relativelyPositioned: {
|
||||||
|
position: 'relative'
|
||||||
|
},
|
||||||
|
safeSpace: {
|
||||||
|
position: 'absolute',
|
||||||
|
backgroundColor: '#0000',
|
||||||
|
height: 40,
|
||||||
|
left: '-50%',
|
||||||
|
width: '200%'
|
||||||
|
},
|
||||||
popoverContent: ({ width }) => ({
|
popoverContent: ({ width }) => ({
|
||||||
width,
|
width,
|
||||||
padding: [[10, 15]]
|
padding: [[10, 15]]
|
||||||
|
|
@ -27,6 +36,10 @@ const usePopperHandler = width => {
|
||||||
setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget)
|
setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openHelpPopper = event => {
|
||||||
|
setHelpPopperAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
|
||||||
const handleCloseHelpPopper = () => {
|
const handleCloseHelpPopper = () => {
|
||||||
setHelpPopperAnchorEl(null)
|
setHelpPopperAnchorEl(null)
|
||||||
}
|
}
|
||||||
|
|
@ -38,25 +51,32 @@ const usePopperHandler = width => {
|
||||||
helpPopperAnchorEl,
|
helpPopperAnchorEl,
|
||||||
helpPopperOpen,
|
helpPopperOpen,
|
||||||
handleOpenHelpPopper,
|
handleOpenHelpPopper,
|
||||||
|
openHelpPopper,
|
||||||
handleCloseHelpPopper
|
handleCloseHelpPopper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tooltip = memo(({ children, width, Icon = HelpIcon }) => {
|
const HelpTooltip = memo(({ children, width }) => {
|
||||||
const handler = usePopperHandler(width)
|
const handler = usePopperHandler(width)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickAwayListener onClickAway={handler.handleCloseHelpPopper}>
|
<ClickAwayListener onClickAway={handler.handleCloseHelpPopper}>
|
||||||
<div>
|
<div
|
||||||
|
className={handler.classes.relativelyPositioned}
|
||||||
|
onMouseLeave={handler.handleCloseHelpPopper}>
|
||||||
|
{handler.helpPopperOpen && (
|
||||||
|
<div className={handler.classes.safeSpace}></div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={handler.classes.transparentButton}
|
className={handler.classes.transparentButton}
|
||||||
onClick={handler.handleOpenHelpPopper}>
|
onMouseEnter={handler.openHelpPopper}>
|
||||||
<Icon />
|
<HelpIcon />
|
||||||
</button>
|
</button>
|
||||||
<Popper
|
<Popper
|
||||||
open={handler.helpPopperOpen}
|
open={handler.helpPopperOpen}
|
||||||
anchorEl={handler.helpPopperAnchorEl}
|
anchorEl={handler.helpPopperAnchorEl}
|
||||||
|
arrowEnabled={true}
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
<div className={handler.classes.popoverContent}>{children}</div>
|
<div className={handler.classes.popoverContent}>{children}</div>
|
||||||
</Popper>
|
</Popper>
|
||||||
|
|
@ -69,31 +89,33 @@ const HoverableTooltip = memo(({ parentElements, children, width }) => {
|
||||||
const handler = usePopperHandler(width)
|
const handler = usePopperHandler(width)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<ClickAwayListener onClickAway={handler.handleCloseHelpPopper}>
|
||||||
{!R.isNil(parentElements) && (
|
<div>
|
||||||
<div
|
{!R.isNil(parentElements) && (
|
||||||
onMouseEnter={handler.handleOpenHelpPopper}
|
<div
|
||||||
onMouseLeave={handler.handleCloseHelpPopper}>
|
onMouseLeave={handler.handleCloseHelpPopper}
|
||||||
{parentElements}
|
onMouseEnter={handler.handleOpenHelpPopper}>
|
||||||
</div>
|
{parentElements}
|
||||||
)}
|
</div>
|
||||||
{R.isNil(parentElements) && (
|
)}
|
||||||
<button
|
{R.isNil(parentElements) && (
|
||||||
type="button"
|
<button
|
||||||
onMouseEnter={handler.handleOpenHelpPopper}
|
type="button"
|
||||||
onMouseLeave={handler.handleCloseHelpPopper}
|
onMouseEnter={handler.handleOpenHelpPopper}
|
||||||
className={handler.classes.transparentButton}>
|
onMouseLeave={handler.handleCloseHelpPopper}
|
||||||
<HelpIcon />
|
className={handler.classes.transparentButton}>
|
||||||
</button>
|
<HelpIcon />
|
||||||
)}
|
</button>
|
||||||
<Popper
|
)}
|
||||||
open={handler.helpPopperOpen}
|
<Popper
|
||||||
anchorEl={handler.helpPopperAnchorEl}
|
open={handler.helpPopperOpen}
|
||||||
placement="bottom">
|
anchorEl={handler.helpPopperAnchorEl}
|
||||||
<div className={handler.classes.popoverContent}>{children}</div>
|
placement="bottom">
|
||||||
</Popper>
|
<div className={handler.classes.popoverContent}>{children}</div>
|
||||||
</div>
|
</Popper>
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export { Tooltip, HoverableTooltip }
|
export { HoverableTooltip, HelpTooltip }
|
||||||
|
|
@ -3,17 +3,17 @@ import classnames from 'classnames'
|
||||||
import { useFormikContext, Form, Formik, Field as FormikField } from 'formik'
|
import { useFormikContext, Form, Formik, Field as FormikField } from 'formik'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState, memo } from 'react'
|
import React, { useState, memo } from 'react'
|
||||||
|
import PromptWhenDirty from 'src/components/PromptWhenDirty'
|
||||||
|
import { H4 } from 'src/components/typography'
|
||||||
|
import EditIconDisabled from 'src/styling/icons/action/edit/disabled.svg?react'
|
||||||
|
import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
|
||||||
|
import FalseIcon from 'src/styling/icons/table/false.svg?react'
|
||||||
|
import TrueIcon from 'src/styling/icons/table/true.svg?react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import PromptWhenDirty from 'src/components/PromptWhenDirty'
|
|
||||||
import { Link, IconButton } from 'src/components/buttons'
|
import { Link, IconButton } from 'src/components/buttons'
|
||||||
import { RadioGroup } from 'src/components/inputs/formik'
|
import { RadioGroup } from 'src/components/inputs/formik'
|
||||||
import { Table, TableBody, TableRow, TableCell } from 'src/components/table'
|
import { Table, TableBody, TableRow, TableCell } from 'src/components/table'
|
||||||
import { H4 } from 'src/components/typography'
|
|
||||||
import { ReactComponent as EditIconDisabled } from 'src/styling/icons/action/edit/disabled.svg'
|
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
|
||||||
import { ReactComponent as FalseIcon } from 'src/styling/icons/table/false.svg'
|
|
||||||
import { ReactComponent as TrueIcon } from 'src/styling/icons/table/true.svg'
|
|
||||||
|
|
||||||
import { booleanPropertiesTableStyles } from './BooleanPropertiesTable.styles'
|
import { booleanPropertiesTableStyles } from './BooleanPropertiesTable.styles'
|
||||||
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
import AddIcon from 'src/styling/icons/button/add/zodiac.svg?react'
|
||||||
|
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
import { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg'
|
|
||||||
import { zircon, zircon2, comet, fontColor, white } from 'src/styling/variables'
|
import { zircon, zircon2, comet, fontColor, white } from 'src/styling/variables'
|
||||||
|
|
||||||
const { p } = typographyStyles
|
const { p } = typographyStyles
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue