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!
cashInCommission: String!
cashOutCommission: String!
cryptoNetwork: Boolean!
cryptoNetwork: String!
cryptoUnits: String!
batchable: Boolean!
}
@ -64,7 +64,7 @@ type Trigger {
requirement: String!
triggerType: String!
suspensionDays: Int
suspensionDays: Float
threshold: Int
thresholdDays: Int
}

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import { isAfter, sub } from 'date-fns/fp'
import { isAfter } from 'date-fns/fp'
import gql from 'graphql-tag'
import * as R from 'ramda'
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 { java, neon } from 'src/styling/variables'
import { fromNamespace } from 'src/utils/config'
import { DAY, WEEK, MONTH } from 'src/utils/time'
import { timezones } from 'src/utils/timezone-list'
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 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`
query getData($excludeTestingCustomers: Boolean) {
transactions(excludeTestingCustomers: $excludeTestingCustomers) {
@ -61,6 +42,8 @@ const GET_DATA = gql`
txClass
error
profit
dispense
sendConfirmed
}
fiatRates {
code
@ -80,19 +63,41 @@ const SystemPerformance = () => {
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
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 => {
if (t.error !== null) return false
if (t.txClass === 'cashOut' && !t.dispense) return false
if (t.txClass === 'cashIn' && !t.sendConfirmed) return false
if (!getLastTimePeriod) {
return (
t.error === null &&
isAfter(ranges[selectedRange].right, toTimezone(t.created, timezone)) &&
isAfter(toTimezone(t.created, timezone), new Date())
isAfter(
toTimezone(t.created, timezone),
toTimezone(periodDomains[selectedRange][1], timezone)
) &&
isAfter(
toTimezone(periodDomains[selectedRange][0], timezone),
toTimezone(t.created, timezone)
)
)
}
return (
t.error === null &&
isAfter(ranges[selectedRange].left, toTimezone(t.created, timezone)) &&
isAfter(toTimezone(t.created, timezone), ranges[selectedRange].right)
isAfter(
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 =
Number.parseFloat(tx.commissionPercentage, 2) * 100
const cashInFee = isCashIn ? Number.parseFloat(tx.cashInFee) : 0

View file

@ -1,13 +1,13 @@
{
"files": {
"main.js": "/static/js/main.b7e0a067.chunk.js",
"main.js.map": "/static/js/main.b7e0a067.chunk.js.map",
"main.js": "/static/js/main.c41bff86.chunk.js",
"main.js.map": "/static/js/main.c41bff86.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
"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.cac754a9.chunk.js.map": "/static/js/2.cac754a9.chunk.js.map",
"static/js/2.539ca5ff.chunk.js": "/static/js/2.539ca5ff.chunk.js",
"static/js/2.539ca5ff.chunk.js.map": "/static/js/2.539ca5ff.chunk.js.map",
"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-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",
@ -153,7 +153,7 @@
},
"entrypoints": [
"static/js/runtime-main.5b925903.js",
"static/js/2.cac754a9.chunk.js",
"static/js/main.b7e0a067.chunk.js"
"static/js/2.539ca5ff.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