239 lines
7.3 KiB
JavaScript
239 lines
7.3 KiB
JavaScript
import { useMutation, useLazyQuery } from '@apollo/client'
|
|
import { ADD_WALLET_LOG, WALLET_LOGS, DELETE_WALLET_LOGS } from '@/wallets/client/fragments'
|
|
import { createContext, useCallback, useContext, useMemo, useState, useEffect } from 'react'
|
|
import { Button } from 'react-bootstrap'
|
|
import { ModalClosedError, useShowModal } from '@/components/modal'
|
|
import { useToast } from '@/components/toast'
|
|
import { FAST_POLL_INTERVAL } from '@/lib/constants'
|
|
import { isTemplate } from '@/wallets/lib/util'
|
|
import { useDiagnostics } from '@/wallets/client/hooks/diagnostics'
|
|
|
|
const TemplateLogsContext = createContext({})
|
|
|
|
export function TemplateLogsProvider ({ children }) {
|
|
const [templateLogs, setTemplateLogs] = useState([])
|
|
|
|
const addTemplateLog = useCallback(({ level, message }) => {
|
|
// TODO(wallet-v2): Date.now() might return the same value for two logs
|
|
// use window.performance.now() instead?
|
|
setTemplateLogs(prev => [{ id: Date.now(), level, message, createdAt: new Date() }, ...prev])
|
|
}, [])
|
|
|
|
const clearTemplateLogs = useCallback(() => {
|
|
setTemplateLogs([])
|
|
}, [])
|
|
|
|
const value = useMemo(() => ({
|
|
templateLogs,
|
|
addTemplateLog,
|
|
clearTemplateLogs
|
|
}), [templateLogs, addTemplateLog, clearTemplateLogs])
|
|
|
|
return (
|
|
<TemplateLogsContext.Provider value={value}>
|
|
{children}
|
|
</TemplateLogsContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useWalletLoggerFactory () {
|
|
const { addTemplateLog } = useContext(TemplateLogsContext)
|
|
const [addWalletLog] = useMutation(ADD_WALLET_LOG)
|
|
const [diagnostics] = useDiagnostics()
|
|
|
|
const log = useCallback(({ protocol, level, message, invoiceId }) => {
|
|
console[mapLevelToConsole(level)](`[${protocol ? protocol.name : 'system'}] ${message}`)
|
|
|
|
if (protocol && isTemplate(protocol)) {
|
|
// this is a template, so there's no protocol yet to which we could attach logs in the db
|
|
addTemplateLog?.({ level, message })
|
|
return
|
|
}
|
|
|
|
return addWalletLog({ variables: { protocolId: protocol ? Number(protocol.id) : null, level, message, invoiceId, timestamp: new Date() } })
|
|
.catch(err => {
|
|
console.error('error adding wallet log:', err)
|
|
})
|
|
}, [addWalletLog, addTemplateLog])
|
|
|
|
return useCallback((protocol, invoice) => {
|
|
const invoiceId = invoice ? Number(invoice.id) : null
|
|
return {
|
|
ok: (message) => {
|
|
log({ protocol, level: 'OK', message, invoiceId })
|
|
},
|
|
info: (message) => {
|
|
log({ protocol, level: 'INFO', message, invoiceId })
|
|
},
|
|
error: (message) => {
|
|
log({ protocol, level: 'ERROR', message, invoiceId })
|
|
},
|
|
warn: (message) => {
|
|
log({ protocol, level: 'WARN', message, invoiceId })
|
|
},
|
|
debug: (message) => {
|
|
if (!diagnostics) return
|
|
log({ protocol, level: 'DEBUG', message, invoiceId })
|
|
}
|
|
}
|
|
}, [log, diagnostics])
|
|
}
|
|
|
|
export function useWalletLogger (protocol) {
|
|
const loggerFactory = useWalletLoggerFactory()
|
|
return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol])
|
|
}
|
|
|
|
export function useWalletLogs (protocol, debug) {
|
|
const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
|
|
|
|
const [cursor, setCursor] = useState(null)
|
|
const [logs, setLogs] = useState([])
|
|
|
|
// if no protocol was given, we want to fetch all logs
|
|
const protocolId = protocol ? Number(protocol.id) : undefined
|
|
|
|
// if we're configuring a protocol template, there are no logs to fetch
|
|
const noFetch = protocol && isTemplate(protocol)
|
|
const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, {
|
|
variables: { protocolId, debug },
|
|
skip: noFetch,
|
|
fetchPolicy: 'network-only'
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (noFetch) return
|
|
|
|
const interval = setInterval(async () => {
|
|
const { data, error } = await fetchLogs({ variables: { protocolId, debug } })
|
|
if (error) {
|
|
console.error('failed to fetch wallet logs:', error.message)
|
|
return
|
|
}
|
|
const { entries: updatedLogs, cursor } = data.walletLogs
|
|
setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs])
|
|
if (!called) {
|
|
setCursor(cursor)
|
|
}
|
|
}, FAST_POLL_INTERVAL)
|
|
|
|
return () => clearInterval(interval)
|
|
}, [fetchLogs, protocolId, called, noFetch, debug])
|
|
|
|
const loadMore = useCallback(async () => {
|
|
const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } })
|
|
const { entries: cursorLogs, cursor: newCursor } = data.walletLogs
|
|
setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))])
|
|
setCursor(newCursor)
|
|
}, [fetchLogs, cursor, protocolId, debug])
|
|
|
|
const clearLogs = useCallback(() => {
|
|
setLogs([])
|
|
clearTemplateLogs?.()
|
|
setCursor(null)
|
|
}, [clearTemplateLogs])
|
|
|
|
return useMemo(() => {
|
|
return {
|
|
loading: noFetch ? false : (!called ? true : loading),
|
|
logs: noFetch ? templateLogs : logs,
|
|
error,
|
|
loadMore,
|
|
hasMore: cursor !== null,
|
|
clearLogs
|
|
}
|
|
}, [loading, noFetch, called, templateLogs, logs, error, loadMore, clearLogs])
|
|
}
|
|
|
|
function mapLevelToConsole (level) {
|
|
switch (level) {
|
|
case 'OK':
|
|
case 'INFO':
|
|
return 'info'
|
|
case 'ERROR':
|
|
return 'error'
|
|
case 'WARN':
|
|
return 'warn'
|
|
default:
|
|
return 'log'
|
|
}
|
|
}
|
|
|
|
export function useDeleteWalletLogs (protocol, debug) {
|
|
const showModal = useShowModal()
|
|
|
|
return useCallback(async () => {
|
|
return await new Promise((resolve, reject) => {
|
|
const onClose = () => {
|
|
reject(new ModalClosedError())
|
|
}
|
|
|
|
showModal(close => {
|
|
const onDelete = () => {
|
|
resolve()
|
|
close()
|
|
}
|
|
|
|
const onClose = () => {
|
|
reject(new ModalClosedError())
|
|
close()
|
|
}
|
|
|
|
return (
|
|
<DeleteWalletLogsObstacle
|
|
protocol={protocol}
|
|
onClose={onClose}
|
|
onDelete={onDelete}
|
|
debug={debug}
|
|
/>
|
|
)
|
|
}, { onClose })
|
|
})
|
|
}, [showModal])
|
|
}
|
|
|
|
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete, debug }) {
|
|
const toaster = useToast()
|
|
const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS)
|
|
|
|
const deleteLogs = useCallback(async () => {
|
|
// there are no logs to delete on the server if protocol is a template
|
|
if (protocol && isTemplate(protocol)) return
|
|
|
|
await deleteWalletLogs({
|
|
variables: { protocolId: protocol ? Number(protocol.id) : undefined, debug }
|
|
})
|
|
}, [protocol, deleteWalletLogs, debug])
|
|
|
|
const onClick = useCallback(async () => {
|
|
try {
|
|
await deleteLogs()
|
|
onDelete()
|
|
onClose()
|
|
toaster.success('deleted wallet logs')
|
|
} catch (err) {
|
|
console.error('failed to delete wallet logs:', err)
|
|
toaster.danger('failed to delete wallet logs')
|
|
}
|
|
}, [onClose, deleteLogs, toaster])
|
|
|
|
let prompt = debug ? 'Do you really want to delete all debug logs?' : 'Do you really want to delete all logs?'
|
|
if (protocol) {
|
|
prompt = 'Do you really want to delete all logs of this protocol?'
|
|
}
|
|
|
|
return (
|
|
<div className='text-center'>
|
|
{prompt}
|
|
<div className='d-flex align-items-center mt-3 mx-auto'>
|
|
<span style={{ cursor: 'pointer' }} className='d-flex ms-auto text-muted fw-bold nav-link mx-3' onClick={onClose}>cancel</span>
|
|
<Button
|
|
className='d-flex me-auto mx-3' variant='danger'
|
|
onClick={onClick}
|
|
>delete
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|