Merge branch 'dev' into fix/coinatmradar-fix-7.5
This commit is contained in:
commit
bb3c9ef2ec
9 changed files with 185 additions and 131 deletions
|
|
@ -3,6 +3,7 @@ const { parseAsync } = require('json2csv')
|
|||
const { GraphQLDateTime } = require('graphql-iso-date')
|
||||
const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json')
|
||||
const got = require('got')
|
||||
const DataLoader = require('dataloader')
|
||||
|
||||
const machineLoader = require('../../machine-loader')
|
||||
const customers = require('../../customers')
|
||||
|
|
@ -265,6 +266,8 @@ const typeDefs = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const transactionsLoader = new DataLoader(ids => transactions.getCustomerTransactionsBatch(ids))
|
||||
|
||||
const notify = () => got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
|
||||
|
|
@ -273,7 +276,7 @@ const resolvers = {
|
|||
JSONObject: GraphQLJSONObject,
|
||||
Date: GraphQLDateTime,
|
||||
Customer: {
|
||||
transactions: parent => transactions.getCustomerTransactions(parent.id)
|
||||
transactions: parent => transactionsLoader.load(parent.id)
|
||||
},
|
||||
Query: {
|
||||
countries: () => countries,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const _ = require('lodash/fp')
|
||||
const pgp = require('pg-promise')()
|
||||
|
||||
const db = require('../db')
|
||||
const machineLoader = require('../machine-loader')
|
||||
|
|
@ -65,9 +66,8 @@ function batch (from = new Date(0).toISOString(), until = new Date().toISOString
|
|||
.then(packager)
|
||||
}
|
||||
|
||||
function getCustomerTransactions (customerId) {
|
||||
function getCustomerTransactionsBatch (ids) {
|
||||
const packager = _.flow(it => {
|
||||
console.log()
|
||||
return it
|
||||
}, _.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ function getCustomerTransactions (customerId) {
|
|||
((not txs.send_confirmed) and (txs.created <= now() - interval $2)) as expired
|
||||
from cash_in_txs as txs
|
||||
left outer join customers c on txs.customer_id = c.id
|
||||
where c.id = $1
|
||||
where c.id IN ($1^)
|
||||
order by created desc limit $3`
|
||||
|
||||
const cashOutSql = `select 'cashOut' as tx_class,
|
||||
|
|
@ -100,14 +100,16 @@ function getCustomerTransactions (customerId) {
|
|||
inner join cash_out_actions actions on txs.id = actions.tx_id
|
||||
and actions.action = 'provisionAddress'
|
||||
left outer join customers c on txs.customer_id = c.id
|
||||
where c.id = $1
|
||||
where c.id IN ($1^)
|
||||
order by created desc limit $2`
|
||||
|
||||
return Promise.all([
|
||||
db.any(cashInSql, [customerId, cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
|
||||
db.any(cashOutSql, [customerId, NUM_RESULTS, REDEEMABLE_AGE])
|
||||
db.any(cashInSql, [_.map(pgp.as.text, ids).join(','), cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
|
||||
db.any(cashOutSql, [_.map(pgp.as.text, ids).join(','), NUM_RESULTS, REDEEMABLE_AGE])
|
||||
])
|
||||
.then(packager)
|
||||
.then(packager).then(transactions => {
|
||||
const transactionMap = _.groupBy('customerId', transactions)
|
||||
return ids.map(id => transactionMap[id])
|
||||
})
|
||||
}
|
||||
|
||||
function single (txId) {
|
||||
|
|
@ -156,4 +158,4 @@ function cancel (txId) {
|
|||
.then(() => single(txId))
|
||||
}
|
||||
|
||||
module.exports = { batch, getCustomerTransactions, single, cancel }
|
||||
module.exports = { batch, single, cancel, getCustomerTransactionsBatch }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import {
|
||||
StylesProvider,
|
||||
jssPreset,
|
||||
|
|
@ -8,12 +9,18 @@ import {
|
|||
import { create } from 'jss'
|
||||
import extendJss from 'jss-plugin-extend'
|
||||
import React, { createContext, useContext, useState } from 'react'
|
||||
import { useLocation, BrowserRouter as Router } from 'react-router-dom'
|
||||
import {
|
||||
useLocation,
|
||||
useHistory,
|
||||
BrowserRouter as Router
|
||||
} from 'react-router-dom'
|
||||
|
||||
import Sidebar from 'src/components/layout/Sidebar'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import ApolloProvider from 'src/utils/apollo'
|
||||
|
||||
import Header from './components/layout/Header'
|
||||
import { tree, Routes } from './routing/routes'
|
||||
import { tree, hasSidebar, Routes, getParent } from './routing/routes'
|
||||
import global from './styling/global'
|
||||
import theme from './styling/theme'
|
||||
import { backgroundColor, mainWidth } from './styling/variables'
|
||||
|
|
@ -46,6 +53,18 @@ const useStyles = makeStyles({
|
|||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection
|
||||
},
|
||||
grid: {
|
||||
flex: 1,
|
||||
height: '100%'
|
||||
},
|
||||
contentWithSidebar: {
|
||||
flex: 1,
|
||||
marginLeft: 48,
|
||||
paddingTop: 15
|
||||
},
|
||||
contentWithoutSidebar: {
|
||||
width: mainWidth
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -54,15 +73,45 @@ const AppContext = createContext()
|
|||
const Main = () => {
|
||||
const classes = useStyles()
|
||||
const location = useLocation()
|
||||
const history = useHistory()
|
||||
const { wizardTested } = useContext(AppContext)
|
||||
|
||||
const route = location.pathname
|
||||
|
||||
const sidebar = hasSidebar(route)
|
||||
const parent = sidebar ? getParent(route) : {}
|
||||
|
||||
const is404 = location.pathname === '/404'
|
||||
|
||||
const isSelected = it => location.pathname === it.route
|
||||
|
||||
const onClick = it => history.push(it.route)
|
||||
|
||||
const contentClassName = sidebar
|
||||
? classes.contentWithSidebar
|
||||
: classes.contentWithoutSidebar
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{!is404 && wizardTested && <Header tree={tree} />}
|
||||
<main className={classes.wrapper}>
|
||||
<Routes />
|
||||
{sidebar && !is404 && wizardTested && (
|
||||
<TitleSection title={parent.title}></TitleSection>
|
||||
)}
|
||||
|
||||
<Grid container className={classes.grid}>
|
||||
{sidebar && !is404 && wizardTested && (
|
||||
<Sidebar
|
||||
data={parent.children}
|
||||
isSelected={isSelected}
|
||||
displayName={it => it.label}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
<div className={contentClassName}>
|
||||
<Routes />
|
||||
</div>
|
||||
</Grid>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import classnames from 'classnames'
|
|||
import React, { memo, useState } from 'react'
|
||||
import { NavLink, useHistory } from 'react-router-dom'
|
||||
|
||||
import { Link } from 'src/components/buttons'
|
||||
import ActionButton from 'src/components/buttons/ActionButton'
|
||||
import { H4 } from 'src/components/typography'
|
||||
import AddMachine from 'src/pages/AddMachine'
|
||||
import { ReactComponent as AddIconReverse } from 'src/styling/icons/button/add/white.svg'
|
||||
import { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg'
|
||||
import { ReactComponent as Logo } from 'src/styling/icons/menu/logo.svg'
|
||||
|
||||
import styles from './Header.styles'
|
||||
|
|
@ -76,9 +78,13 @@ const Header = memo(({ tree }) => {
|
|||
</NavLink>
|
||||
))}
|
||||
</ul>
|
||||
<Link color="action" onClick={() => setOpen(true)}>
|
||||
Add Machine
|
||||
</Link>
|
||||
<ActionButton
|
||||
color="secondary"
|
||||
Icon={AddIcon}
|
||||
InverseIcon={AddIconReverse}
|
||||
onClick={() => setOpen(true)}>
|
||||
Add machine
|
||||
</ActionButton>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -128,19 +128,19 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
/>
|
||||
<ConfirmDialog
|
||||
open={confirmDialogOpen}
|
||||
title={`${action?.command} this machine?`}
|
||||
title={`${action?.display} this machine?`}
|
||||
errorMessage={errorMessage}
|
||||
toBeConfirmed={machine.name}
|
||||
message={action?.message}
|
||||
confirmationMessage={action?.confirmationMessage}
|
||||
saveButtonAlwaysEnabled={action?.command === 'Rename'}
|
||||
saveButtonAlwaysEnabled={action?.command === 'rename'}
|
||||
onConfirmed={value => {
|
||||
setErrorMessage(null)
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: machine.deviceId,
|
||||
action: `${action?.command}`.toLowerCase(),
|
||||
...(action?.command === 'Rename' && { newName: value })
|
||||
action: `${action?.command}`,
|
||||
...(action?.command === 'rename' && { newName: value })
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
|
@ -174,7 +174,8 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
InverseIcon={EditReversedIcon}
|
||||
onClick={() =>
|
||||
setAction({
|
||||
command: 'Rename',
|
||||
command: 'rename',
|
||||
display: 'Rename',
|
||||
confirmationMessage: 'Write the new name for this machine'
|
||||
})
|
||||
}>
|
||||
|
|
@ -188,7 +189,8 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
disabled={loading}
|
||||
onClick={() =>
|
||||
setAction({
|
||||
command: 'Unpair'
|
||||
command: 'unpair',
|
||||
display: 'Unpair'
|
||||
})
|
||||
}>
|
||||
Unpair
|
||||
|
|
@ -201,26 +203,42 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
|
|||
disabled={loading}
|
||||
onClick={() =>
|
||||
setAction({
|
||||
command: 'Reboot'
|
||||
command: 'reboot',
|
||||
display: 'Reboot'
|
||||
})
|
||||
}>
|
||||
Reboot
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={classes.inlineChip}
|
||||
className={classes.mr}
|
||||
disabled={loading}
|
||||
color="primary"
|
||||
Icon={ShutdownIcon}
|
||||
InverseIcon={ShutdownReversedIcon}
|
||||
onClick={() =>
|
||||
setAction({
|
||||
command: 'Shutdown',
|
||||
command: 'shutdown',
|
||||
display: 'Shutdown',
|
||||
message:
|
||||
'In order to bring it back online, the machine will need to be visited and its power reset.'
|
||||
})
|
||||
}>
|
||||
Shutdown
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.inlineChip}
|
||||
Icon={RebootIcon}
|
||||
InverseIcon={RebootReversedIcon}
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
setAction({
|
||||
command: 'restartServices',
|
||||
display: 'Restart services for'
|
||||
})
|
||||
}>
|
||||
Restart Services
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Item>
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
import { makeStyles } from '@material-ui/core'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import React from 'react'
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
Redirect,
|
||||
useLocation,
|
||||
useHistory
|
||||
} from 'react-router-dom'
|
||||
|
||||
import Sidebar from 'src/components/layout/Sidebar'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
|
||||
import CoinAtmRadar from './CoinATMRadar'
|
||||
import ContactInfo from './ContactInfo'
|
||||
import ReceiptPrinting from './ReceiptPrinting'
|
||||
import TermsConditions from './TermsConditions'
|
||||
|
||||
const styles = {
|
||||
grid: {
|
||||
flex: 1,
|
||||
height: '100%'
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
marginLeft: 48,
|
||||
paddingTop: 15
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const innerRoutes = [
|
||||
{
|
||||
label: 'Contact information',
|
||||
route: '/settings/operator-info/contact-info',
|
||||
component: ContactInfo
|
||||
},
|
||||
{
|
||||
label: 'Receipt',
|
||||
route: '/settings/operator-info/receipt-printing',
|
||||
component: ReceiptPrinting
|
||||
},
|
||||
{
|
||||
label: 'Coin ATM Radar',
|
||||
route: '/settings/operator-info/coin-atm-radar',
|
||||
component: CoinAtmRadar
|
||||
},
|
||||
{
|
||||
label: 'Terms & Conditions',
|
||||
route: '/settings/operator-info/terms-conditions',
|
||||
component: TermsConditions
|
||||
}
|
||||
]
|
||||
|
||||
const Routes = ({ wizard }) => (
|
||||
<Switch>
|
||||
<Redirect
|
||||
exact
|
||||
from="/settings/operator-info"
|
||||
to="/settings/operator-info/contact-info"
|
||||
/>
|
||||
<Route exact path="/" />
|
||||
{innerRoutes.map(({ route, component: Page, key }) => (
|
||||
<Route path={route} key={key}>
|
||||
<Page name={key} wizard={wizard} />
|
||||
</Route>
|
||||
))}
|
||||
</Switch>
|
||||
)
|
||||
|
||||
const OperatorInfo = ({ wizard = false }) => {
|
||||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
|
||||
const isSelected = it => location.pathname === it.route
|
||||
|
||||
const onClick = it => history.push(it.route)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleSection title="Operator information"></TitleSection>
|
||||
<Grid container className={classes.grid}>
|
||||
<Sidebar
|
||||
data={innerRoutes}
|
||||
isSelected={isSelected}
|
||||
displayName={it => it.label}
|
||||
onClick={onClick}
|
||||
/>
|
||||
<div className={classes.content}>
|
||||
<Routes wizard={wizard} />
|
||||
</div>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OperatorInfo
|
||||
|
|
@ -20,7 +20,10 @@ import MachineLogs from 'src/pages/MachineLogs'
|
|||
import CashCassettes from 'src/pages/Maintenance/CashCassettes'
|
||||
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
||||
import Notifications from 'src/pages/Notifications/Notifications'
|
||||
import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo'
|
||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
|
||||
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
|
||||
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
|
||||
import ServerLogs from 'src/pages/ServerLogs'
|
||||
import Services from 'src/pages/Services/Services'
|
||||
import TokenManagement from 'src/pages/TokenManagement/TokenManagement'
|
||||
|
|
@ -125,7 +128,36 @@ const tree = [
|
|||
key: namespaces.OPERATOR_INFO,
|
||||
label: 'Operator Info',
|
||||
route: '/settings/operator-info',
|
||||
component: OperatorInfo
|
||||
title: 'Operator Information',
|
||||
get component() {
|
||||
return () => <Redirect to={this.children[0].route} />
|
||||
},
|
||||
children: [
|
||||
{
|
||||
key: 'contact-info',
|
||||
label: 'Contact information',
|
||||
route: '/settings/operator-info/contact-info',
|
||||
component: ContactInfo
|
||||
},
|
||||
{
|
||||
key: 'receipt-printing',
|
||||
label: 'Receipt',
|
||||
route: '/settings/operator-info/receipt-printing',
|
||||
component: ReceiptPrinting
|
||||
},
|
||||
{
|
||||
key: 'coin-atm-radar',
|
||||
label: 'Coin ATM Radar',
|
||||
route: '/settings/operator-info/coin-atm-radar',
|
||||
component: CoinAtmRadar
|
||||
},
|
||||
{
|
||||
key: 'terms-conditions',
|
||||
label: 'Terms & Conditions',
|
||||
route: '/settings/operator-info/terms-conditions',
|
||||
component: TermsConditions
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -181,10 +213,34 @@ const tree = [
|
|||
]
|
||||
|
||||
const map = R.map(R.when(R.has('children'), R.prop('children')))
|
||||
const leafRoutes = R.compose(R.flatten, map)(tree)
|
||||
const parentRoutes = R.filter(R.has('children'))(tree)
|
||||
const mappedRoutes = R.compose(R.flatten, map)(tree)
|
||||
const parentRoutes = R.filter(R.has('children'))(mappedRoutes).concat(
|
||||
R.filter(R.has('children'))(tree)
|
||||
)
|
||||
const leafRoutes = R.compose(R.flatten, map)(mappedRoutes)
|
||||
|
||||
const flattened = R.concat(leafRoutes, parentRoutes)
|
||||
|
||||
const hasSidebar = route =>
|
||||
R.any(r => r.route === route)(
|
||||
R.compose(
|
||||
R.flatten,
|
||||
R.map(R.prop('children')),
|
||||
R.filter(R.has('children'))
|
||||
)(mappedRoutes)
|
||||
)
|
||||
|
||||
const getParent = route =>
|
||||
R.find(
|
||||
R.propEq(
|
||||
'route',
|
||||
R.dropLast(
|
||||
1,
|
||||
R.dropLastWhile(x => x !== '/', route)
|
||||
)
|
||||
)
|
||||
)(flattened)
|
||||
|
||||
const Routes = () => {
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
|
|
@ -215,4 +271,4 @@ const Routes = () => {
|
|||
</Switch>
|
||||
)
|
||||
}
|
||||
export { tree, Routes }
|
||||
export { tree, getParent, hasSidebar, Routes }
|
||||
|
|
|
|||
19
package-lock.json
generated
19
package-lock.json
generated
|
|
@ -6131,6 +6131,25 @@
|
|||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
|
||||
"dasherize": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
|
||||
"integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
|
||||
"integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
|
||||
},
|
||||
"dataloader": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz",
|
||||
"integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ=="
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
|
||||
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"expand-brackets": {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"console-log-level": "^1.4.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dataloader": "^2.0.0",
|
||||
"ethereumjs-tx": "^1.3.3",
|
||||
"ethereumjs-util": "^5.2.0",
|
||||
"ethereumjs-wallet": "^0.6.3",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue