wallet logs: less visual clutter, refactor (#2369)
* Remove unnecessary initial state for template logs * Rename skip to noFetch * Remove outdated TODO * Cleaner wallet template logs + refactor
This commit is contained in:
parent
1aeb206842
commit
7857601c36
@ -5,6 +5,7 @@ import { useCallback, useEffect, useState, Fragment } from 'react'
|
||||
import { timeSince } from '@/lib/time'
|
||||
import classNames from 'classnames'
|
||||
import { ModalClosedError } from '@/components/modal'
|
||||
import { isTemplate } from '@/wallets/lib/util'
|
||||
|
||||
// TODO(wallet-v2):
|
||||
// when we delete logs for a protocol, the cache is not updated
|
||||
@ -28,40 +29,105 @@ export function WalletLogs ({ protocol, className, debug }) {
|
||||
|
||||
const embedded = !!protocol
|
||||
|
||||
// avoid unnecessary clutter when attaching new wallet
|
||||
const hideLogs = logs.length === 0 && protocol && isTemplate(protocol)
|
||||
if (hideLogs) return null
|
||||
|
||||
// showing delete button and logs footer for temporary template logs is unnecessary clutter
|
||||
const template = protocol && isTemplate(protocol)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames('d-flex w-100 align-items-center mb-3', className)}>
|
||||
<span
|
||||
style={{ cursor: 'pointer' }}
|
||||
className='text-muted fw-bold nav-link ms-auto' onClick={onDelete}
|
||||
>clear logs
|
||||
</span>
|
||||
</div>
|
||||
<div className={className}>
|
||||
{!template && (
|
||||
<div className='d-flex w-100 align-items-center mb-3'>
|
||||
<span
|
||||
style={{ cursor: 'pointer' }}
|
||||
className='text-muted fw-bold nav-link ms-auto' onClick={onDelete}
|
||||
>clear logs
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames(styles.container, embedded && styles.embedded)}>
|
||||
{logs.map((log, i) => (
|
||||
<LogMessage
|
||||
key={i}
|
||||
tag={log.wallet?.name}
|
||||
tag={protocol ? null : log.wallet?.name}
|
||||
level={log.level}
|
||||
message={log.message}
|
||||
context={log.context}
|
||||
ts={log.createdAt}
|
||||
/>
|
||||
))}
|
||||
{loading
|
||||
? <div className='w-100 text-center'>loading...</div>
|
||||
: logs.length === 0 && <div className='w-100 text-center'>empty</div>}
|
||||
{hasMore
|
||||
? <div className='w-100 text-center'><Button onClick={loadMore} size='sm' className='mt-3'>more</Button></div>
|
||||
: <div className='w-100 text-center'>------ start of logs ------</div>}
|
||||
{!template && <WalletLogsFooter empty={logs.length === 0} loading={loading} hasMore={hasMore} loadMore={loadMore} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WalletLogsFooter ({ empty, loading, hasMore, loadMore }) {
|
||||
return (
|
||||
<>
|
||||
{loading
|
||||
? <div className='w-100 text-center'>loading...</div>
|
||||
: empty && <div className='w-100 text-center'>empty</div>}
|
||||
{hasMore
|
||||
? <div className='w-100 text-center'><Button onClick={loadMore} size='sm' className='mt-3'>more</Button></div>
|
||||
: <div className='w-100 text-center'>------ start of logs ------</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function LogMessage ({ tag, level, message, context, ts }) {
|
||||
const [show, setShow] = useState(false)
|
||||
const [showContext, setShowContext] = useState(false)
|
||||
|
||||
const filtered = context
|
||||
? Object.keys(context)
|
||||
.filter(key => !['send', 'recv', 'status'].includes(key))
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = context[key]
|
||||
return obj
|
||||
}, {})
|
||||
: {}
|
||||
|
||||
const hasContext = context && Object.keys(filtered).length > 0
|
||||
const handleClick = () => {
|
||||
if (hasContext) { setShowContext(show => !show) }
|
||||
}
|
||||
const style = hasContext ? { cursor: 'pointer' } : { cursor: 'inherit' }
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.row} onClick={handleClick} style={style}>
|
||||
<TimeSince timestamp={ts} />
|
||||
{tag !== null && <Tag tag={tag?.toLowerCase() ?? 'system'} />}
|
||||
<Level level={level} />
|
||||
<Message message={message} />
|
||||
{hasContext && <Indicator show={showContext} />}
|
||||
</div>
|
||||
{hasContext && showContext && <Context context={filtered} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TimeSince ({ timestamp }) {
|
||||
const [time, setTime] = useState(timeSince(new Date(timestamp)))
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setTime(timeSince(new Date(timestamp)))
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [timestamp])
|
||||
|
||||
return <div className={styles.timestamp}>{time}</div>
|
||||
}
|
||||
|
||||
function Tag ({ tag }) {
|
||||
return <div className={styles.tag}>{`[${tag}]`}</div>
|
||||
}
|
||||
|
||||
function Level ({ level }) {
|
||||
let className
|
||||
switch (level.toLowerCase()) {
|
||||
case 'ok':
|
||||
@ -80,69 +146,29 @@ export function LogMessage ({ tag, level, message, context, ts }) {
|
||||
className = 'text-muted'; break
|
||||
}
|
||||
|
||||
const filtered = context
|
||||
? Object.keys(context)
|
||||
.filter(key => !['send', 'recv', 'status'].includes(key))
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = context[key]
|
||||
return obj
|
||||
}, {})
|
||||
: {}
|
||||
return <div className={classNames(styles.level, className)}>{level}</div>
|
||||
}
|
||||
|
||||
const hasContext = context && Object.keys(filtered).length > 0
|
||||
function Message ({ message }) {
|
||||
return <div className={styles.message}>{message}</div>
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (hasContext) { setShow(show => !show) }
|
||||
}
|
||||
|
||||
const style = hasContext ? { cursor: 'pointer' } : { cursor: 'inherit' }
|
||||
const indicator = hasContext ? (show ? '-' : '+') : <></>
|
||||
|
||||
// TODO(wallet-v2): show invoice context
|
||||
function Indicator ({ show }) {
|
||||
return <div className={styles.indicator}>{show ? '-' : '+'}</div>
|
||||
}
|
||||
|
||||
function Context ({ context }) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.row} onClick={handleClick} style={style}>
|
||||
<TimeSince timestamp={ts} />
|
||||
<div className={styles.tag}>{`[${nameToTag(tag)}]`}</div>
|
||||
<div className={`${styles.level} ${className}`}>{level}</div>
|
||||
<div className={styles.message}>{message}</div>
|
||||
<div className={styles.indicator}>{indicator}</div>
|
||||
</div>
|
||||
{show && hasContext && (
|
||||
<div className={styles.context}>
|
||||
{Object.entries(filtered)
|
||||
.map(([key, value], i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<div>{key}:</div>
|
||||
<div className='text-break'>{value}</div>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div className={styles.context}>
|
||||
{Object.entries(context)
|
||||
.map(([key, value], i) => {
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<div>{key}:</div>
|
||||
<div className='text-break'>{value}</div>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function nameToTag (name) {
|
||||
switch (name) {
|
||||
case undefined: return 'system'
|
||||
default: return name.toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
function TimeSince ({ timestamp }) {
|
||||
const [time, setTime] = useState(timeSince(new Date(timestamp)))
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setTime(timeSince(new Date(timestamp)))
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [timestamp])
|
||||
|
||||
return <div className={styles.timestamp}>{time}</div>
|
||||
}
|
||||
|
@ -88,21 +88,21 @@ export function useWalletLogs (protocol, debug) {
|
||||
const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
|
||||
|
||||
const [cursor, setCursor] = useState(null)
|
||||
// if we're configuring a protocol template, there are no logs to fetch
|
||||
const skip = protocol && isTemplate(protocol)
|
||||
const [logs, setLogs] = useState(skip ? templateLogs : [])
|
||||
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,
|
||||
skip: noFetch,
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (skip) return
|
||||
if (noFetch) return
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
const { data, error } = await fetchLogs({ variables: { protocolId, debug } })
|
||||
@ -118,7 +118,7 @@ export function useWalletLogs (protocol, debug) {
|
||||
}, FAST_POLL_INTERVAL)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [fetchLogs, called, skip, debug])
|
||||
}, [fetchLogs, called, noFetch, debug])
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } })
|
||||
@ -135,14 +135,14 @@ export function useWalletLogs (protocol, debug) {
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
loading: skip ? false : (!called ? true : loading),
|
||||
logs: skip ? templateLogs : logs,
|
||||
loading: noFetch ? false : (!called ? true : loading),
|
||||
logs: noFetch ? templateLogs : logs,
|
||||
error,
|
||||
loadMore,
|
||||
hasMore: cursor !== null,
|
||||
clearLogs
|
||||
}
|
||||
}, [loading, skip, called, templateLogs, logs, error, loadMore, clearLogs])
|
||||
}, [loading, noFetch, called, templateLogs, logs, error, loadMore, clearLogs])
|
||||
}
|
||||
|
||||
function mapLevelToConsole (level) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user