diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index d299dd1e..9aa3b63d 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -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, diff --git a/lib/new-admin/transactions.js b/lib/new-admin/transactions.js index 657ab4ea..79285e06 100644 --- a/lib/new-admin/transactions.js +++ b/lib/new-admin/transactions.js @@ -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 } diff --git a/new-lamassu-admin/src/App.js b/new-lamassu-admin/src/App.js index 0ff35c15..66e2ba1a 100644 --- a/new-lamassu-admin/src/App.js +++ b/new-lamassu-admin/src/App.js @@ -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 (
{!is404 && wizardTested &&
}
- + {sidebar && !is404 && wizardTested && ( + + )} + + + {sidebar && !is404 && wizardTested && ( + it.label} + onClick={onClick} + /> + )} +
+ +
+
) diff --git a/new-lamassu-admin/src/components/layout/Header.js b/new-lamassu-admin/src/components/layout/Header.js index 97ae6995..d21acb46 100644 --- a/new-lamassu-admin/src/components/layout/Header.js +++ b/new-lamassu-admin/src/components/layout/Header.js @@ -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 }) => { ))} - setOpen(true)}> - Add Machine - + setOpen(true)}> + Add machine + diff --git a/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js index d04dc8fa..4abaea31 100644 --- a/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js +++ b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js @@ -128,19 +128,19 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => { /> { 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 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 + + setAction({ + command: 'restartServices', + display: 'Restart services for' + }) + }> + Restart Services + diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js deleted file mode 100644 index 794530d2..00000000 --- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js +++ /dev/null @@ -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 }) => ( - - - - {innerRoutes.map(({ route, component: Page, key }) => ( - - - - ))} - -) - -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 ( - <> - - - it.label} - onClick={onClick} - /> -
- -
-
- - ) -} - -export default OperatorInfo diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 404ade02..de280ba2 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -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 () => + }, + 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 = () => { ) } -export { tree, Routes } +export { tree, getParent, hasSidebar, Routes } diff --git a/package-lock.json b/package-lock.json index cdc4c723..e14735c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index b2b7b9a5..9d79f3d3 100644 --- a/package.json +++ b/package.json @@ -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",