Merge pull request #1294 from lamassu/dev

Dev into release 8.1
This commit is contained in:
Rafael Taranto 2022-07-13 12:11:54 +01:00 committed by GitHub
commit ae1c9ba2fa
16 changed files with 84 additions and 63 deletions

View file

@ -7,7 +7,7 @@ type Coin {
cashInFee: String! cashInFee: String!
cashInCommission: String! cashInCommission: String!
cashOutCommission: String! cashOutCommission: String!
cryptoNetwork: Boolean! cryptoNetwork: String!
cryptoUnits: String! cryptoUnits: String!
batchable: Boolean! batchable: Boolean!
} }
@ -64,7 +64,7 @@ type Trigger {
requirement: String! requirement: String!
triggerType: String! triggerType: String!
suspensionDays: Int suspensionDays: Float
threshold: Int threshold: Int
thresholdDays: Int thresholdDays: Int
} }

View file

@ -1,11 +1,12 @@
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const semver = require('semver') const semver = require('semver')
const sms = require('../sms')
const _ = require('lodash/fp') const _ = require('lodash/fp')
const BN = require('../bn')
const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz/fp') const { zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz/fp')
const { add, intervalToDuration } = require('date-fns/fp')
const sms = require('../sms')
const BN = require('../bn')
const compliance = require('../compliance') const compliance = require('../compliance')
const complianceTriggers = require('../compliance-triggers') const complianceTriggers = require('../compliance-triggers')
const configManager = require('../new-config-manager') const configManager = require('../new-config-manager')
@ -18,6 +19,7 @@ const { getTx } = require('../new-admin/services/transactions.js')
const machineLoader = require('../machine-loader') const machineLoader = require('../machine-loader')
const { loadLatestConfig } = require('../new-settings-loader') const { loadLatestConfig } = require('../new-settings-loader')
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests') const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
const T = require('../time')
function updateCustomerCustomInfoRequest (customerId, patch, req, res) { function updateCustomerCustomInfoRequest (customerId, patch, req, res) {
if (_.isNil(patch.data)) { if (_.isNil(patch.data)) {
@ -112,9 +114,9 @@ function triggerSuspend (req, res, next) {
const days = triggerId === 'no-ff-camera' ? 1 : getSuspendDays(triggers) const days = triggerId === 'no-ff-camera' ? 1 : getSuspendDays(triggers)
const date = new Date() const suspensionDuration = intervalToDuration({ start: 0, end: T.day * days })
date.setDate(date.getDate() + days)
customers.update(id, { suspendedUntil: date }) customers.update(id, { suspendedUntil: add(suspensionDuration, new Date()) })
.then(customer => { .then(customer => {
notifier.complianceNotify(customer, req.deviceId, 'SUSPENDED', days) notifier.complianceNotify(customer, req.deviceId, 'SUSPENDED', days)
return respond(req, res, { customer }) return respond(req, res, { customer })

View file

@ -174,7 +174,9 @@ const Analytics = () => {
R.map(convertFiatToLocale)( R.map(convertFiatToLocale)(
transactions?.filter( transactions?.filter(
tx => tx =>
(!tx.dispensed || !tx.expired) && (tx.sendConfirmed || tx.dispense) (!tx.dispensed || !tx.expired) &&
(tx.sendConfirmed || tx.dispense) &&
!tx.hasError
) )
) ?? [] ) ?? []

View file

@ -34,7 +34,7 @@ const Graph = ({
const GRAPH_MARGIN = useMemo( const GRAPH_MARGIN = useMemo(
() => ({ () => ({
top: 25, top: 25,
right: 0.5, right: 3.5,
bottom: 27, bottom: 27,
left: 36.5 left: 36.5
}), }),
@ -158,6 +158,12 @@ const Graph = ({
.domain(periodDomains[period.code]) .domain(periodDomains[period.code])
.range([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right]) .range([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right])
// Create a second X axis for mouseover events to be placed correctly across the entire graph width and not limited by X's domain
const x2 = d3
.scaleUtc()
.domain(periodDomains[period.code])
.range([GRAPH_MARGIN.left, GRAPH_WIDTH])
const y = d3 const y = d3
.scaleLinear() .scaleLinear()
.domain([ .domain([
@ -167,11 +173,11 @@ const Graph = ({
.nice() .nice()
.range([GRAPH_HEIGHT - GRAPH_MARGIN.bottom, GRAPH_MARGIN.top]) .range([GRAPH_HEIGHT - GRAPH_MARGIN.bottom, GRAPH_MARGIN.top])
const getAreaInterval = (breakpoints, limits) => { const getAreaInterval = (breakpoints, dataLimits, graphLimits) => {
const fullBreakpoints = [ const fullBreakpoints = [
limits[1], graphLimits[1],
...R.filter(it => it > limits[0] && it < limits[1], breakpoints), ...R.filter(it => it > dataLimits[0] && it < dataLimits[1], breakpoints),
limits[0] dataLimits[0]
] ]
const intervals = [] const intervals = []
@ -238,7 +244,7 @@ const Graph = ({
.selectAll('.tick line') .selectAll('.tick line')
.filter(d => d === 0) .filter(d => d === 0)
.clone() .clone()
.attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.right - GRAPH_MARGIN.left) .attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.left)
.attr('stroke-width', 1) .attr('stroke-width', 1)
.attr('stroke', primaryColor) .attr('stroke', primaryColor)
), ),
@ -276,7 +282,7 @@ const Graph = ({
.attr('y1', d => 0.5 + y(d)) .attr('y1', d => 0.5 + y(d))
.attr('y2', d => 0.5 + y(d)) .attr('y2', d => 0.5 + y(d))
.attr('x1', GRAPH_MARGIN.left) .attr('x1', GRAPH_MARGIN.left)
.attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.right) .attr('x2', GRAPH_WIDTH)
) )
// Vertical transparent rectangles for events // Vertical transparent rectangles for events
.call(g => .call(g =>
@ -291,7 +297,8 @@ const Graph = ({
const xValue = Math.round(x(d) * 100) / 100 const xValue = Math.round(x(d) * 100) / 100
const intervals = getAreaInterval( const intervals = getAreaInterval(
buildAreas(x.domain()).map(it => Math.round(x(it) * 100) / 100), buildAreas(x.domain()).map(it => Math.round(x(it) * 100) / 100),
x.range() x.range(),
x2.range()
) )
const interval = getAreaIntervalByX(intervals, xValue) const interval = getAreaIntervalByX(intervals, xValue)
return Math.round((interval[0] - interval[1]) * 100) / 100 return Math.round((interval[0] - interval[1]) * 100) / 100
@ -307,10 +314,12 @@ const Graph = ({
const areas = buildAreas(x.domain()) const areas = buildAreas(x.domain())
const intervals = getAreaInterval( const intervals = getAreaInterval(
buildAreas(x.domain()).map(it => Math.round(x(it) * 100) / 100), buildAreas(x.domain()).map(it => Math.round(x(it) * 100) / 100),
x.range() x.range(),
x2.range()
) )
const dateInterval = getDateIntervalByX(areas, intervals, xValue) const dateInterval = getDateIntervalByX(areas, intervals, xValue)
if (!dateInterval) return
const filteredData = data.filter(it => { const filteredData = data.filter(it => {
const created = new Date(it.created) const created = new Date(it.created)
const tzCreated = created.setTime(created.getTime() + offset) const tzCreated = created.setTime(created.getTime() + offset)
@ -426,6 +435,7 @@ const Graph = ({
buildTicks, buildTicks,
getPastAndCurrentDayLabels, getPastAndCurrentDayLabels,
x, x,
x2,
y, y,
period, period,
buildAreas, buildAreas,
@ -482,7 +492,7 @@ const Graph = ({
0.5 + y(d3.mean(data, d => new BigNumber(d.fiat).toNumber()) ?? 0) 0.5 + y(d3.mean(data, d => new BigNumber(d.fiat).toNumber()) ?? 0)
) )
.attr('x1', GRAPH_MARGIN.left) .attr('x1', GRAPH_MARGIN.left)
.attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.right) .attr('x2', GRAPH_WIDTH)
) )
}, },
[GRAPH_MARGIN, y, data] [GRAPH_MARGIN, y, data]

View file

@ -22,9 +22,9 @@ const Graph = ({ data, timeFrame, timezone }) => {
const GRAPH_MARGIN = useMemo( const GRAPH_MARGIN = useMemo(
() => ({ () => ({
top: 20, top: 20,
right: 0.5, right: 3.5,
bottom: 27, bottom: 27,
left: 43.5 left: 33.5
}), }),
[] []
) )
@ -63,7 +63,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
) )
const filterDay = useCallback( const filterDay = useCallback(
x => (timeFrame === 'day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1), x => (timeFrame === 'Day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1),
[timeFrame] [timeFrame]
) )
@ -211,7 +211,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
.attr('y1', d => 0.5 + y(d)) .attr('y1', d => 0.5 + y(d))
.attr('y2', d => 0.5 + y(d)) .attr('y2', d => 0.5 + y(d))
.attr('x1', GRAPH_MARGIN.left) .attr('x1', GRAPH_MARGIN.left)
.attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.right) .attr('x2', GRAPH_WIDTH)
) )
// Thick vertical lines // Thick vertical lines
.call(g => .call(g =>

View file

@ -3,7 +3,7 @@ import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classnames from 'classnames' import classnames from 'classnames'
import { isAfter, sub } from 'date-fns/fp' import { isAfter } from 'date-fns/fp'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
@ -15,6 +15,7 @@ import { ReactComponent as PercentNeutralIcon } from 'src/styling/icons/dashboar
import { ReactComponent as PercentUpIcon } from 'src/styling/icons/dashboard/up.svg' import { ReactComponent as PercentUpIcon } from 'src/styling/icons/dashboard/up.svg'
import { java, neon } from 'src/styling/variables' import { java, neon } from 'src/styling/variables'
import { fromNamespace } from 'src/utils/config' import { fromNamespace } from 'src/utils/config'
import { DAY, WEEK, MONTH } from 'src/utils/time'
import { timezones } from 'src/utils/timezone-list' import { timezones } from 'src/utils/timezone-list'
import { toTimezone } from 'src/utils/timezones' import { toTimezone } from 'src/utils/timezones'
@ -30,26 +31,6 @@ BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })
const getFiats = R.map(R.prop('fiat')) const getFiats = R.map(R.prop('fiat'))
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const getDateSecondsAgo = (seconds = 0, startDate = null) => {
const date = startDate ? new Date(startDate) : new Date()
return sub({ seconds: seconds }, date)
}
const ranges = {
Day: {
left: getDateSecondsAgo(2 * 24 * 3600, new Date()),
right: getDateSecondsAgo(24 * 3600, new Date())
},
Week: {
left: getDateSecondsAgo(14 * 24 * 3600, new Date()),
right: getDateSecondsAgo(7 * 24 * 3600, new Date())
},
Month: {
left: getDateSecondsAgo(60 * 24 * 3600, new Date()),
right: getDateSecondsAgo(30 * 24 * 3600, new Date())
}
}
const GET_DATA = gql` const GET_DATA = gql`
query getData($excludeTestingCustomers: Boolean) { query getData($excludeTestingCustomers: Boolean) {
transactions(excludeTestingCustomers: $excludeTestingCustomers) { transactions(excludeTestingCustomers: $excludeTestingCustomers) {
@ -61,6 +42,8 @@ const GET_DATA = gql`
txClass txClass
error error
profit profit
dispense
sendConfirmed
} }
fiatRates { fiatRates {
code code
@ -80,19 +63,41 @@ const SystemPerformance = () => {
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
const timezone = fromNamespace('locale')(data?.config).timezone const timezone = fromNamespace('locale')(data?.config).timezone
const NOW = Date.now()
const periodDomains = {
Day: [NOW - DAY, NOW],
Week: [NOW - WEEK, NOW],
Month: [NOW - MONTH, NOW]
}
const isInRangeAndNoError = getLastTimePeriod => t => { const isInRangeAndNoError = getLastTimePeriod => t => {
if (t.error !== null) return false if (t.error !== null) return false
if (t.txClass === 'cashOut' && !t.dispense) return false
if (t.txClass === 'cashIn' && !t.sendConfirmed) return false
if (!getLastTimePeriod) { if (!getLastTimePeriod) {
return ( return (
t.error === null && t.error === null &&
isAfter(ranges[selectedRange].right, toTimezone(t.created, timezone)) && isAfter(
isAfter(toTimezone(t.created, timezone), new Date()) toTimezone(t.created, timezone),
toTimezone(periodDomains[selectedRange][1], timezone)
) &&
isAfter(
toTimezone(periodDomains[selectedRange][0], timezone),
toTimezone(t.created, timezone)
)
) )
} }
return ( return (
t.error === null && t.error === null &&
isAfter(ranges[selectedRange].left, toTimezone(t.created, timezone)) && isAfter(
isAfter(toTimezone(t.created, timezone), ranges[selectedRange].right) toTimezone(periodDomains[selectedRange][1], timezone),
toTimezone(t.created, timezone)
) &&
isAfter(
toTimezone(t.created, timezone),
toTimezone(periodDomains[selectedRange][0], timezone)
)
) )
} }

View file

@ -136,7 +136,9 @@ const DetailsRow = ({ it: tx, timezone }) => {
} }
) )
const commission = getCommission(tx).toFixed(2) const commission = BigNumber(getCommission(tx))
.abs()
.toFixed(2, 1) // ROUND_DOWN
const commissionPercentage = const commissionPercentage =
Number.parseFloat(tx.commissionPercentage, 2) * 100 Number.parseFloat(tx.commissionPercentage, 2) * 100
const cashInFee = isCashIn ? Number.parseFloat(tx.cashInFee) : 0 const cashInFee = isCashIn ? Number.parseFloat(tx.cashInFee) : 0

View file

@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.js": "/static/js/main.b7e0a067.chunk.js", "main.js": "/static/js/main.c41bff86.chunk.js",
"main.js.map": "/static/js/main.b7e0a067.chunk.js.map", "main.js.map": "/static/js/main.c41bff86.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.5b925903.js", "runtime-main.js": "/static/js/runtime-main.5b925903.js",
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map", "runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
"static/js/2.cac754a9.chunk.js": "/static/js/2.cac754a9.chunk.js", "static/js/2.539ca5ff.chunk.js": "/static/js/2.539ca5ff.chunk.js",
"static/js/2.cac754a9.chunk.js.map": "/static/js/2.cac754a9.chunk.js.map", "static/js/2.539ca5ff.chunk.js.map": "/static/js/2.539ca5ff.chunk.js.map",
"index.html": "/index.html", "index.html": "/index.html",
"static/js/2.cac754a9.chunk.js.LICENSE.txt": "/static/js/2.cac754a9.chunk.js.LICENSE.txt", "static/js/2.539ca5ff.chunk.js.LICENSE.txt": "/static/js/2.539ca5ff.chunk.js.LICENSE.txt",
"static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg", "static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg",
"static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg", "static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg",
"static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg", "static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg",
@ -153,7 +153,7 @@
}, },
"entrypoints": [ "entrypoints": [
"static/js/runtime-main.5b925903.js", "static/js/runtime-main.5b925903.js",
"static/js/2.cac754a9.chunk.js", "static/js/2.539ca5ff.chunk.js",
"static/js/main.b7e0a067.chunk.js" "static/js/main.c41bff86.chunk.js"
] ]
} }

View file

@ -1 +1 @@
<!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>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.cac754a9.chunk.js"></script><script src="/static/js/main.b7e0a067.chunk.js"></script></body></html> <!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>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.539ca5ff.chunk.js"></script><script src="/static/js/main.c41bff86.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long