import { Button } from 'react-bootstrap' import styles from '@/styles/logger.module.css' import { useWalletLogs, useDeleteWalletLogs } from '@/wallets/client/hooks' 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 // so when we go to all wallet logs, we still see the deleted logs until the query is refetched export function WalletLogs ({ protocol, className, debug }) { const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol, debug) const deleteLogs = useDeleteWalletLogs(protocol, debug) const onDelete = useCallback(async () => { try { await deleteLogs() clearLogs() } catch (err) { if (err instanceof ModalClosedError) { return } console.error('error deleting logs:', err) } }, [deleteLogs, clearLogs]) 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 (
{!template && (
clear logs
)}
{logs.map((log, i) => ( ))} {!template && }
) } function WalletLogsFooter ({ empty, loading, hasMore, loadMore }) { return ( <> {loading ?
loading...
: empty &&
empty
} {hasMore ?
:
------ start of logs ------
} ) } export function LogMessage ({ tag, level, message, context, ts }) { 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 ( <>
{tag !== null && } {hasContext && }
{hasContext && showContext && } ) } 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
{time}
} function Tag ({ tag }) { return
{`[${tag}]`}
} function Level ({ level }) { let className switch (level.toLowerCase()) { case 'ok': case 'success': level = 'ok' className = 'text-success'; break case 'error': className = 'text-danger'; break case 'warning': level = 'warn' className = 'text-warning'; break case 'info': className = 'text-info'; break case 'debug': default: className = 'text-muted'; break } return
{level}
} function Message ({ message }) { return
{message}
} function Indicator ({ show }) { return
{show ? '-' : '+'}
} function Context ({ context }) { return (
{Object.entries(context) .map(([key, value], i) => { return (
{key}:
{value}
) })}
) }