commit
ae1c9ba2fa
16 changed files with 84 additions and 63 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
) ?? []
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
2
public/static/js/main.c41bff86.chunk.js
Normal file
2
public/static/js/main.c41bff86.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
public/static/js/main.c41bff86.chunk.js.map
Normal file
1
public/static/js/main.c41bff86.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue