fix: added timestamp parameters for a date range on the gql queries for
machineLogs, serverLogs and transactions feat: added optional limit and offset variables for the logs queries, for filtering and pagination feat: adapted the LogsDownloaderPopper to download the logs by whats set on the filters fix: improved code readability fix: avoid errors when the range option is selected and no range is actually selected
This commit is contained in:
parent
37ea3a04c3
commit
f641e605a4
7 changed files with 109 additions and 68 deletions
17
lib/logs.js
17
lib/logs.js
|
|
@ -81,28 +81,31 @@ function getUnlimitedMachineLogs (deviceId, until = new Date().toISOString()) {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMachineLogs (deviceId, until = new Date().toISOString()) {
|
function getMachineLogs (deviceId, until = new Date().toISOString(), limit = null, offset = 0) {
|
||||||
const sql = `select id, log_level, timestamp, message from logs
|
const sql = `select id, log_level, timestamp, message from logs
|
||||||
where device_id=$1
|
where device_id=$1
|
||||||
and timestamp <= $3
|
and timestamp <= $2
|
||||||
order by timestamp desc, serial desc
|
order by timestamp desc, serial desc
|
||||||
limit $2`
|
limit $3
|
||||||
|
offset $4`
|
||||||
|
|
||||||
return Promise.all([db.any(sql, [ deviceId, NUM_RESULTS, until ]), getMachineName(deviceId)])
|
return Promise.all([db.any(sql, [ deviceId, until, limit, offset ]), getMachineName(deviceId)])
|
||||||
.then(([logs, machineName]) => ({
|
.then(([logs, machineName]) => ({
|
||||||
logs: _.map(_.mapKeys(_.camelCase), logs),
|
logs: _.map(_.mapKeys(_.camelCase), logs),
|
||||||
currentMachine: {deviceId, name: machineName}
|
currentMachine: {deviceId, name: machineName}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function simpleGetMachineLogs (deviceId, until = new Date().toISOString()) {
|
function simpleGetMachineLogs (deviceId, from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
|
||||||
const sql = `select id, log_level, timestamp, message from logs
|
const sql = `select id, log_level, timestamp, message from logs
|
||||||
where device_id=$1
|
where device_id=$1
|
||||||
|
and timestamp >= $2
|
||||||
and timestamp <= $3
|
and timestamp <= $3
|
||||||
order by timestamp desc, serial desc
|
order by timestamp desc, serial desc
|
||||||
limit $2`
|
limit $4
|
||||||
|
offset $5`
|
||||||
|
|
||||||
return db.any(sql, [ deviceId, NUM_RESULTS, until ])
|
return db.any(sql, [ deviceId, from, until, limit, offset ])
|
||||||
.then(_.map(_.mapKeys(_.camelCase)))
|
.then(_.map(_.mapKeys(_.camelCase)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,12 @@ const typeDefs = gql`
|
||||||
machines: [Machine]
|
machines: [Machine]
|
||||||
customers: [Customer]
|
customers: [Customer]
|
||||||
customer(customerId: ID!): Customer
|
customer(customerId: ID!): Customer
|
||||||
machineLogs(deviceId: ID!): [MachineLog]
|
machineLogs(deviceId: ID!, from: Date, until: Date, limit: Int, offset: Int): [MachineLog]
|
||||||
funding: [CoinFunds]
|
funding: [CoinFunds]
|
||||||
serverVersion: String!
|
serverVersion: String!
|
||||||
uptime: [ProcessStatus]
|
uptime: [ProcessStatus]
|
||||||
serverLogs: [ServerLog]
|
serverLogs(from: Date, until: Date, limit: Int, offset: Int): [ServerLog]
|
||||||
transactions: [Transaction]
|
transactions(from: Date, until: Date, limit: Int, offset: Int): [Transaction]
|
||||||
accounts: JSONObject
|
accounts: JSONObject
|
||||||
config: JSONObject
|
config: JSONObject
|
||||||
}
|
}
|
||||||
|
|
@ -250,11 +250,14 @@ const resolvers = {
|
||||||
customers: () => customers.getCustomersList(),
|
customers: () => customers.getCustomersList(),
|
||||||
customer: (...[, { customerId }]) => customers.getCustomerById(customerId),
|
customer: (...[, { customerId }]) => customers.getCustomerById(customerId),
|
||||||
funding: () => funding.getFunding(),
|
funding: () => funding.getFunding(),
|
||||||
machineLogs: (...[, { deviceId }]) => logs.simpleGetMachineLogs(deviceId),
|
machineLogs: (...[, { deviceId, from, until, limit, offset }]) =>
|
||||||
|
logs.simpleGetMachineLogs(deviceId, from, until, limit, offset),
|
||||||
serverVersion: () => serverVersion,
|
serverVersion: () => serverVersion,
|
||||||
uptime: () => supervisor.getAllProcessInfo(),
|
uptime: () => supervisor.getAllProcessInfo(),
|
||||||
serverLogs: () => serverLogs.getServerLogs(),
|
serverLogs: (...[, { from, until, limit, offset }]) =>
|
||||||
transactions: () => transactions.batch(),
|
serverLogs.getServerLogs(from, until, limit, offset),
|
||||||
|
transactions: (...[, { from, until, limit, offset }]) =>
|
||||||
|
transactions.batch(from, until, limit, offset),
|
||||||
config: () => settingsLoader.getConfig(),
|
config: () => settingsLoader.getConfig(),
|
||||||
accounts: () => settingsLoader.getAccounts()
|
accounts: () => settingsLoader.getAccounts()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ const db = require('../db')
|
||||||
|
|
||||||
const NUM_RESULTS = 500
|
const NUM_RESULTS = 500
|
||||||
|
|
||||||
function getServerLogs (until = new Date().toISOString()) {
|
function getServerLogs (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
|
||||||
const sql = `select id, log_level, timestamp, message from server_logs
|
const sql = `select id, log_level, timestamp, message from server_logs
|
||||||
|
where timestamp >= $1 and timestamp <= $2
|
||||||
order by timestamp desc
|
order by timestamp desc
|
||||||
limit $1`
|
limit $3
|
||||||
|
offset $4`
|
||||||
|
|
||||||
return db.any(sql, [ NUM_RESULTS ])
|
return db.any(sql, [ from, until, limit, offset ])
|
||||||
.then(_.map(_.mapKeys(_.camelCase)))
|
.then(_.map(_.mapKeys(_.camelCase)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,8 @@ function addNames (txs) {
|
||||||
|
|
||||||
const camelize = _.mapKeys(_.camelCase)
|
const camelize = _.mapKeys(_.camelCase)
|
||||||
|
|
||||||
function batch () {
|
function batch (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
|
||||||
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']),
|
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
|
||||||
_.take(NUM_RESULTS), _.map(camelize), addNames)
|
|
||||||
|
|
||||||
const cashInSql = `select 'cashIn' as tx_class, txs.*,
|
const cashInSql = `select 'cashIn' as tx_class, txs.*,
|
||||||
c.phone as customer_phone,
|
c.phone as customer_phone,
|
||||||
|
|
@ -38,7 +37,8 @@ function batch () {
|
||||||
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
|
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
|
||||||
from cash_in_txs as txs
|
from cash_in_txs as txs
|
||||||
left outer join customers c on txs.customer_id = c.id
|
left outer join customers c on txs.customer_id = c.id
|
||||||
order by created desc limit $2`
|
where txs.created >= $2 and txs.created <= $3
|
||||||
|
order by created desc limit $4 offset $5`
|
||||||
|
|
||||||
const cashOutSql = `select 'cashOut' as tx_class,
|
const cashOutSql = `select 'cashOut' as tx_class,
|
||||||
txs.*,
|
txs.*,
|
||||||
|
|
@ -50,14 +50,18 @@ function batch () {
|
||||||
c.name as customer_name,
|
c.name as customer_name,
|
||||||
c.front_camera_path as customer_front_camera_path,
|
c.front_camera_path as customer_front_camera_path,
|
||||||
c.id_card_photo_path as customer_id_card_photo_path,
|
c.id_card_photo_path as customer_id_card_photo_path,
|
||||||
(extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $2 as expired
|
(extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $1 as expired
|
||||||
from cash_out_txs txs
|
from cash_out_txs txs
|
||||||
inner join cash_out_actions actions on txs.id = actions.tx_id
|
inner join cash_out_actions actions on txs.id = actions.tx_id
|
||||||
and actions.action = 'provisionAddress'
|
and actions.action = 'provisionAddress'
|
||||||
left outer join customers c on txs.customer_id = c.id
|
left outer join customers c on txs.customer_id = c.id
|
||||||
order by created desc limit $1`
|
where txs.created >= $2 and txs.created <= $3
|
||||||
|
order by created desc limit $4 offset $5`
|
||||||
|
|
||||||
return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS, REDEEMABLE_AGE])])
|
return Promise.all([
|
||||||
|
db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset]),
|
||||||
|
db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset])
|
||||||
|
])
|
||||||
.then(packager)
|
.then(packager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,8 +69,7 @@ function getCustomerTransactions (customerId) {
|
||||||
const packager = _.flow(it => {
|
const packager = _.flow(it => {
|
||||||
console.log()
|
console.log()
|
||||||
return it
|
return it
|
||||||
}, _.flatten, _.orderBy(_.property('created'), ['desc']),
|
}, _.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
|
||||||
_.take(NUM_RESULTS), _.map(camelize), addNames)
|
|
||||||
|
|
||||||
const cashInSql = `select 'cashIn' as tx_class, txs.*,
|
const cashInSql = `select 'cashIn' as tx_class, txs.*,
|
||||||
c.phone as customer_phone,
|
c.phone as customer_phone,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
|
|
@ -128,10 +129,13 @@ const useStyles = makeStyles(styles)
|
||||||
const ALL = 'all'
|
const ALL = 'all'
|
||||||
const RANGE = 'range'
|
const RANGE = 'range'
|
||||||
|
|
||||||
const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
|
const LogsDownloaderPopover = ({ name, query, args, title, getLogs }) => {
|
||||||
const [selectedRadio, setSelectedRadio] = useState(ALL)
|
const [selectedRadio, setSelectedRadio] = useState(ALL)
|
||||||
const [range, setRange] = useState({ from: null, to: null })
|
const [range, setRange] = useState({ from: null, until: null })
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
|
const [fetchLogs] = useLazyQuery(query, {
|
||||||
|
onCompleted: data => createLogsFile(getLogs(data), range)
|
||||||
|
})
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -143,49 +147,55 @@ const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
|
||||||
const handleRadioButtons = evt => {
|
const handleRadioButtons = evt => {
|
||||||
const selectedRadio = R.path(['target', 'value'])(evt)
|
const selectedRadio = R.path(['target', 'value'])(evt)
|
||||||
setSelectedRadio(selectedRadio)
|
setSelectedRadio(selectedRadio)
|
||||||
if (selectedRadio === ALL) setRange({ from: null, to: null })
|
if (selectedRadio === ALL) setRange({ from: null, until: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRangeChange = useCallback(
|
const handleRangeChange = useCallback(
|
||||||
(from, to) => {
|
(from, until) => {
|
||||||
setRange({ from, to })
|
setRange({ from, until })
|
||||||
},
|
},
|
||||||
[setRange]
|
[setRange]
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadLogs = (range, logs) => {
|
const downloadLogs = (range, args, fetchLogs) => {
|
||||||
if (!range) return
|
if (selectedRadio === ALL) {
|
||||||
|
fetchLogs({
|
||||||
|
variables: {
|
||||||
|
...args
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (range.from && !range.to) range.to = moment()
|
if (!range || !range.from) return
|
||||||
|
if (range.from && !range.until) range.until = moment()
|
||||||
|
|
||||||
|
if (selectedRadio === RANGE) {
|
||||||
|
fetchLogs({
|
||||||
|
variables: {
|
||||||
|
...args,
|
||||||
|
from: range.from,
|
||||||
|
until: range.until
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLogsFile = (logs, range) => {
|
||||||
const formatDateFile = date => {
|
const formatDateFile = date => {
|
||||||
return moment(date).format('YYYY-MM-DD_HH-mm')
|
return moment(date).format('YYYY-MM-DD_HH-mm')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedRadio === ALL) {
|
const text = logs.map(it => JSON.stringify(it)).join('\n')
|
||||||
const text = logs.map(it => JSON.stringify(it)).join('\n')
|
const blob = new window.Blob([text], {
|
||||||
const blob = new window.Blob([text], {
|
type: 'text/plain;charset=utf-8'
|
||||||
type: 'text/plain;charset=utf-8'
|
})
|
||||||
})
|
|
||||||
FileSaver.saveAs(blob, `${formatDateFile(new Date())}_${name}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedRadio === RANGE) {
|
FileSaver.saveAs(
|
||||||
const text = logs
|
blob,
|
||||||
.filter(log =>
|
selectedRadio === ALL
|
||||||
moment(getTimestamp(log)).isBetween(range.from, range.to, 'day', '[]')
|
? `${formatDateFile(new Date())}_${name}`
|
||||||
)
|
: `${formatDateFile(range.from)}_${formatDateFile(range.until)}_${name}`
|
||||||
.map(it => JSON.stringify(it))
|
)
|
||||||
.join('\n')
|
|
||||||
const blob = new window.Blob([text], {
|
|
||||||
type: 'text/plain;charset=utf-8'
|
|
||||||
})
|
|
||||||
FileSaver.saveAs(
|
|
||||||
blob,
|
|
||||||
`${formatDateFile(range.from)}_${formatDateFile(range.to)}_${name}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOpenRangePicker = event => {
|
const handleOpenRangePicker = event => {
|
||||||
|
|
@ -231,7 +241,7 @@ const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
|
||||||
<div className={classes.arrowContainer}>
|
<div className={classes.arrowContainer}>
|
||||||
<Arrow className={classes.arrow} />
|
<Arrow className={classes.arrow} />
|
||||||
</div>
|
</div>
|
||||||
<DateContainer date={range.to}>To</DateContainer>
|
<DateContainer date={range.until}>To</DateContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -242,7 +252,9 @@ const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={classes.download}>
|
<div className={classes.download}>
|
||||||
<Link color="primary" onClick={() => downloadLogs(range, logs)}>
|
<Link
|
||||||
|
color="primary"
|
||||||
|
onClick={() => downloadLogs(range, args, fetchLogs)}>
|
||||||
Download
|
Download
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,16 @@ const GET_MACHINES = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const NUM_LOG_RESULTS = 1000
|
||||||
|
|
||||||
const GET_MACHINE_LOGS = gql`
|
const GET_MACHINE_LOGS = gql`
|
||||||
query MachineLogs($deviceId: ID!) {
|
query MachineLogs($deviceId: ID!, $limit: Int, $from: Date, $until: Date) {
|
||||||
machineLogs(deviceId: $deviceId) {
|
machineLogs(
|
||||||
|
deviceId: $deviceId
|
||||||
|
limit: $limit
|
||||||
|
from: $from
|
||||||
|
until: $until
|
||||||
|
) {
|
||||||
logLevel
|
logLevel
|
||||||
id
|
id
|
||||||
timestamp
|
timestamp
|
||||||
|
|
@ -65,7 +72,11 @@ const Logs = () => {
|
||||||
|
|
||||||
const deviceId = selected?.deviceId
|
const deviceId = selected?.deviceId
|
||||||
|
|
||||||
const { data: machineResponse } = useQuery(GET_MACHINES)
|
const { data: machineResponse } = useQuery(GET_MACHINES, {
|
||||||
|
variables: {
|
||||||
|
limit: NUM_LOG_RESULTS
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const [sendSnapshot, { loading }] = useMutation(SUPPORT_LOGS, {
|
const [sendSnapshot, { loading }] = useMutation(SUPPORT_LOGS, {
|
||||||
variables: { deviceId },
|
variables: { deviceId },
|
||||||
|
|
@ -97,8 +108,9 @@ const Logs = () => {
|
||||||
<LogsDowloaderPopover
|
<LogsDowloaderPopover
|
||||||
title="Download logs"
|
title="Download logs"
|
||||||
name="machine-logs"
|
name="machine-logs"
|
||||||
logs={logsResponse.machineLogs}
|
query={GET_MACHINE_LOGS}
|
||||||
getTimestamp={log => log.timestamp}
|
args={{ deviceId }}
|
||||||
|
getLogs={logs => R.path(['machineLogs'])(logs)}
|
||||||
/>
|
/>
|
||||||
<SimpleButton
|
<SimpleButton
|
||||||
className={classes.shareButton}
|
className={classes.shareButton}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ import { mainStyles } from './Transactions.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(mainStyles)
|
const useStyles = makeStyles(mainStyles)
|
||||||
|
|
||||||
|
const NUM_LOG_RESULTS = 1000
|
||||||
|
|
||||||
const GET_TRANSACTIONS = gql`
|
const GET_TRANSACTIONS = gql`
|
||||||
{
|
query transactions($limit: Int, $from: Date, $until: Date) {
|
||||||
transactions {
|
transactions(limit: $limit, from: $from, until: $until) {
|
||||||
id
|
id
|
||||||
txClass
|
txClass
|
||||||
txHash
|
txHash
|
||||||
|
|
@ -52,7 +54,11 @@ const GET_TRANSACTIONS = gql`
|
||||||
const Transactions = () => {
|
const Transactions = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const { data: txResponse } = useQuery(GET_TRANSACTIONS)
|
const { data: txResponse } = useQuery(GET_TRANSACTIONS, {
|
||||||
|
variables: {
|
||||||
|
limit: NUM_LOG_RESULTS
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const formatCustomerName = customer => {
|
const formatCustomerName = customer => {
|
||||||
const { firstName, lastName } = customer
|
const { firstName, lastName } = customer
|
||||||
|
|
@ -136,8 +142,8 @@ const Transactions = () => {
|
||||||
<LogsDowloaderPopover
|
<LogsDowloaderPopover
|
||||||
title="Download logs"
|
title="Download logs"
|
||||||
name="transactions"
|
name="transactions"
|
||||||
logs={txResponse.transactions}
|
query={GET_TRANSACTIONS}
|
||||||
getTimestamp={tx => tx.created}
|
getLogs={logs => R.path(['transactions'])(logs)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue