Files
explorer-monorepo/frontend/src/components/explorer/BridgeMonitoringPage.tsx
defiQUG b5a2e0c0a4 feat(freshness): enhance diagnostics and update snapshot structure
- Introduced a new Diagnostics struct to capture transaction visibility state and activity state.
- Updated BuildSnapshot function to return diagnostics alongside snapshot, completeness, and sampling.
- Enhanced test cases to validate the new diagnostics data.
- Updated frontend components to utilize the new diagnostics information for improved user feedback on freshness context.

This change improves the observability of transaction activity and enhances the user experience by providing clearer insights into the freshness of data.
2026-04-12 18:22:08 -07:00

426 lines
16 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { Card } from '@/libs/frontend-ui-primitives'
import {
getMissionControlRelayLabel,
getMissionControlRelays,
missionControlApi,
type MissionControlBridgeStatusResponse,
type MissionControlRelayPayload,
type MissionControlRelaySnapshot,
} from '@/services/api/missionControl'
import { statsApi, type ExplorerStats } from '@/services/api/stats'
import { explorerFeaturePages } from '@/data/explorerOperations'
import { summarizeChainActivity } from '@/utils/activityContext'
import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import { resolveEffectiveFreshness } from '@/utils/explorerFreshness'
type FeedState = 'connecting' | 'live' | 'fallback'
interface RelayLaneCard {
key: string
label: string
status: string
profile: string
sourceChain: string
destinationChain: string
queueSize: number
processed: number
failed: number
lastPolled: string
bridgeAddress: string
issueScope: string | null
issueMessage: string | null
inventoryShortfallWei: string | null
inventoryRequiredWei: string | null
inventoryAvailableWei: string | null
}
const relayOrder = ['mainnet_cw', 'mainnet_weth', 'bsc', 'avax', 'avax_cw', 'avax_to_138']
function relativeAge(isoString?: string): string {
if (!isoString) return 'Unknown'
const parsed = Date.parse(isoString)
if (!Number.isFinite(parsed)) return 'Unknown'
const seconds = Math.max(0, Math.round((Date.now() - parsed) / 1000))
if (seconds < 60) return `${seconds}s ago`
const minutes = Math.round(seconds / 60)
if (minutes < 60) return `${minutes}m ago`
const hours = Math.round(minutes / 60)
return `${hours}h ago`
}
function shortAddress(value?: string): string {
if (!value) return 'Unspecified'
if (value.length <= 14) return value
return `${value.slice(0, 6)}...${value.slice(-4)}`
}
function resolveSnapshot(relay?: MissionControlRelayPayload): MissionControlRelaySnapshot | null {
return relay?.url_probe?.body || relay?.file_snapshot || null
}
function relayPolicyCue(snapshot: MissionControlRelaySnapshot | null): string | null {
if (!snapshot) return null
if (snapshot.last_error?.scope === 'bridge_inventory') {
return 'Queued release waiting on bridge inventory'
}
if (snapshot.last_error?.scope === 'bridge_inventory_probe') {
return 'Bridge inventory check is temporarily unavailable'
}
if (String(snapshot.status || '').toLowerCase() === 'paused' && snapshot.monitoring?.delivery_enabled === false) {
return 'Delivery disabled by policy'
}
return null
}
function laneToneClasses(status: string): string {
const normalized = status.toLowerCase()
if (['degraded', 'stale', 'stopped', 'down', 'snapshot-error'].includes(normalized)) {
return 'border-red-200 bg-red-50/80 dark:border-red-900/60 dark:bg-red-950/20'
}
if (['paused', 'starting'].includes(normalized)) {
return 'border-amber-200 bg-amber-50/80 dark:border-amber-900/60 dark:bg-amber-950/20'
}
return 'border-emerald-200 bg-emerald-50/80 dark:border-emerald-900/60 dark:bg-emerald-950/20'
}
function statusPillClasses(status: string): string {
const normalized = status.toLowerCase()
if (['degraded', 'stale', 'stopped', 'down', 'snapshot-error'].includes(normalized)) {
return 'bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-100'
}
if (['paused', 'starting'].includes(normalized)) {
return 'bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-100'
}
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-100'
}
function formatWeiToToken(value?: string | null): string {
if (!value) return 'Unknown'
try {
const raw = BigInt(value)
const whole = raw / 10n ** 18n
const fractional = (raw % 10n ** 18n).toString().padStart(18, '0').slice(0, 6).replace(/0+$/, '')
return fractional ? `${whole.toString()}.${fractional}` : whole.toString()
} catch {
return value
}
}
function ActionLink({
href,
label,
external,
}: {
href: string
label: string
external?: boolean
}) {
const className = 'inline-flex items-center text-sm font-semibold text-primary-600 hover:underline'
const text = `${label} ->`
if (external) {
return (
<a href={href} className={className} target="_blank" rel="noopener noreferrer">
{text}
</a>
)
}
return (
<Link href={href} className={className}>
{text}
</Link>
)
}
export default function BridgeMonitoringPage({
initialBridgeStatus = null,
initialStats = null,
}: {
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
initialStats?: ExplorerStats | null
}) {
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
const [stats, setStats] = useState<ExplorerStats | null>(initialStats)
const [feedState, setFeedState] = useState<FeedState>(initialBridgeStatus ? 'fallback' : 'connecting')
const page = explorerFeaturePages.bridge
useEffect(() => {
let cancelled = false
const loadSnapshot = async () => {
try {
const [snapshot, latestStats] = await Promise.all([
missionControlApi.getBridgeStatus(),
statsApi.get().catch(() => null),
])
if (!cancelled) {
setBridgeStatus(snapshot)
if (latestStats) {
setStats(latestStats)
}
}
} catch (error) {
if (!cancelled && process.env.NODE_ENV !== 'production') {
console.warn('Failed to load bridge monitoring snapshot:', error)
}
}
}
loadSnapshot()
const unsubscribe = missionControlApi.subscribeBridgeStatus(
(status) => {
if (!cancelled) {
setBridgeStatus(status)
setFeedState('live')
}
},
(error) => {
if (!cancelled) {
setFeedState('fallback')
}
if (process.env.NODE_ENV !== 'production') {
console.warn('Bridge monitoring live stream issue:', error)
}
}
)
return () => {
cancelled = true
unsubscribe()
}
}, [])
const activityContext = useMemo(
() =>
summarizeChainActivity({
blocks: [],
transactions: [],
latestBlockNumber: stats?.latest_block,
latestBlockTimestamp: null,
freshness: resolveEffectiveFreshness(stats, bridgeStatus),
diagnostics: stats?.diagnostics ?? bridgeStatus?.data?.diagnostics ?? null,
}),
[bridgeStatus, stats],
)
const relayLanes = useMemo((): RelayLaneCard[] => {
const relays = getMissionControlRelays(bridgeStatus)
if (!relays) return []
const orderIndex = new Map(relayOrder.map((key, index) => [key, index]))
return Object.entries(relays)
.map(([key, relay]) => {
const snapshot = resolveSnapshot(relay)
const status = String(snapshot?.status || (relay.file_snapshot_error ? 'snapshot-error' : 'configured')).toLowerCase()
return {
key,
label: getMissionControlRelayLabel(key),
status:
snapshot?.last_error?.scope === 'bridge_inventory'
? 'underfunded'
: snapshot?.last_error?.scope === 'bridge_inventory_probe'
? 'warning'
: status,
profile: snapshot?.service?.profile || key,
sourceChain: snapshot?.source?.chain_name || 'Unknown',
destinationChain: snapshot?.destination?.chain_name || 'Unknown',
queueSize: snapshot?.queue?.size ?? 0,
processed: snapshot?.queue?.processed ?? 0,
failed: snapshot?.queue?.failed ?? 0,
lastPolled: relativeAge(snapshot?.last_source_poll?.at),
bridgeAddress:
snapshot?.destination?.relay_bridge_default ||
snapshot?.destination?.relay_bridge ||
snapshot?.source?.bridge_filter ||
'',
issueScope: snapshot?.last_error?.scope || null,
issueMessage: snapshot?.last_error?.message || null,
inventoryShortfallWei: snapshot?.last_error?.shortfall || null,
inventoryRequiredWei: snapshot?.last_error?.required_amount || null,
inventoryAvailableWei: snapshot?.last_error?.available_amount || null,
}
})
.sort((left, right) => {
const leftIndex = orderIndex.get(left.key) ?? Number.MAX_SAFE_INTEGER
const rightIndex = orderIndex.get(right.key) ?? Number.MAX_SAFE_INTEGER
return leftIndex - rightIndex || left.label.localeCompare(right.label)
})
}, [bridgeStatus])
const chainStatus = bridgeStatus?.data?.chains?.['138']
const overallStatus = bridgeStatus?.data?.status || 'unknown'
const checkedAt = relativeAge(bridgeStatus?.data?.checked_at)
return (
<div className="container mx-auto px-4 py-6 sm:py-8">
<div className="mb-6 max-w-4xl sm:mb-8">
<div className="mb-3 inline-flex rounded-full border border-sky-200 bg-sky-50 px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-sky-700">
{page.eyebrow}
</div>
<h1 className="mb-3 text-3xl font-bold text-gray-900 dark:text-white sm:text-4xl">
{page.title}
</h1>
<p className="text-base leading-7 text-gray-600 dark:text-gray-400 sm:text-lg sm:leading-8">
{page.description}
</p>
</div>
{page.note ? (
<Card className="mb-6 border border-amber-200 bg-amber-50/70 dark:border-amber-900/50 dark:bg-amber-950/20">
<p className="text-sm leading-6 text-amber-950 dark:text-amber-100">
{page.note}
</p>
</Card>
) : null}
<div className="mb-6">
<ActivityContextPanel context={activityContext} title="Bridge Freshness Context" />
<FreshnessTrustNote
className="mt-3"
context={activityContext}
stats={stats}
bridgeStatus={bridgeStatus}
scopeLabel="Bridge relay posture is shown alongside the same explorer freshness model used on the homepage and core explorer routes"
/>
</div>
<div className="mb-6 grid gap-4 lg:grid-cols-3">
<Card className="border border-sky-200 bg-sky-50/70 dark:border-sky-900/50 dark:bg-sky-950/20">
<div className="text-sm font-semibold uppercase tracking-wide text-sky-800 dark:text-sky-100">
Relay Fleet
</div>
<div className="mt-2 text-2xl font-bold text-gray-900 dark:text-white">
{overallStatus}
</div>
<div className="mt-2 text-sm text-gray-700 dark:text-gray-300">
{relayLanes.length} managed lanes visible
</div>
<div className="mt-2 text-xs font-medium uppercase tracking-wide text-sky-800/80 dark:text-sky-100/80">
Feed: {feedState === 'live' ? 'Live SSE' : feedState === 'fallback' ? 'Snapshot fallback' : 'Connecting'}
</div>
</Card>
<Card className="border border-gray-200 dark:border-gray-700">
<div className="text-sm font-semibold uppercase tracking-wide text-gray-700 dark:text-gray-300">
Chain 138 RPC
</div>
<div className="mt-2 text-2xl font-bold text-gray-900 dark:text-white">
{chainStatus?.status || 'unknown'}
</div>
<div className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Head age: {chainStatus?.head_age_sec != null ? `${chainStatus.head_age_sec.toFixed(1)}s` : 'Unknown'}
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
Latency: {chainStatus?.latency_ms != null ? `${chainStatus.latency_ms}ms` : 'Unknown'}
</div>
</Card>
<Card className="border border-gray-200 dark:border-gray-700">
<div className="text-sm font-semibold uppercase tracking-wide text-gray-700 dark:text-gray-300">
Last Check
</div>
<div className="mt-2 text-2xl font-bold text-gray-900 dark:text-white">
{checkedAt}
</div>
<div className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Public status JSON and live stream are both active.
</div>
<div className="mt-4">
<ActionLink href="/explorer-api/v1/track1/bridge/status" label="Open status JSON" external />
</div>
</Card>
</div>
<div className="mb-8 grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
{relayLanes.map((lane) => (
<Card key={lane.key} className={`border ${laneToneClasses(lane.status)}`}>
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-lg font-semibold text-gray-900 dark:text-white">
{lane.label}
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
{`${lane.sourceChain} -> ${lane.destinationChain}`}
</div>
</div>
<div className={`rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wide ${statusPillClasses(lane.status)}`}>
{lane.status}
</div>
</div>
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
<div>
<div className="text-gray-500 dark:text-gray-400">Profile</div>
<div className="font-medium text-gray-900 dark:text-white">{lane.profile}</div>
</div>
<div>
<div className="text-gray-500 dark:text-gray-400">Queue</div>
<div className="font-medium text-gray-900 dark:text-white">{lane.queueSize}</div>
</div>
<div>
<div className="text-gray-500 dark:text-gray-400">Processed</div>
<div className="font-medium text-gray-900 dark:text-white">{lane.processed}</div>
</div>
<div>
<div className="text-gray-500 dark:text-gray-400">Failed</div>
<div className="font-medium text-gray-900 dark:text-white">{lane.failed}</div>
</div>
</div>
<div className="mt-4 text-sm text-gray-600 dark:text-gray-400">
Last polled: {lane.lastPolled}
</div>
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">
Bridge: {shortAddress(lane.bridgeAddress)}
</div>
{relayPolicyCue(resolveSnapshot((getMissionControlRelays(bridgeStatus) || {})[lane.key])) ? (
<div className="mt-3 text-xs font-medium uppercase tracking-wide text-amber-700 dark:text-amber-300">
{relayPolicyCue(resolveSnapshot((getMissionControlRelays(bridgeStatus) || {})[lane.key]))}
</div>
) : null}
{lane.issueScope === 'bridge_inventory' ? (
<div className="mt-3 rounded-2xl border border-amber-200 bg-amber-50/80 p-3 text-sm text-amber-950 dark:border-amber-900/60 dark:bg-amber-950/30 dark:text-amber-100">
<div className="font-semibold">Bridge inventory below required release amount</div>
<div className="mt-1">
Shortfall: {formatWeiToToken(lane.inventoryShortfallWei)} WETH
</div>
<div className="mt-1 text-xs opacity-80">
Required: {formatWeiToToken(lane.inventoryRequiredWei)} WETH · Available: {formatWeiToToken(lane.inventoryAvailableWei)} WETH
</div>
</div>
) : null}
</Card>
))}
</div>
<div className="grid gap-4 lg:grid-cols-2">
{page.actions.map((action) => (
<Card key={`${action.title}-${action.href}`} className="border border-gray-200 dark:border-gray-700">
<div className="flex h-full flex-col">
<div className="text-base font-semibold text-gray-900 dark:text-white">
{action.title}
</div>
<p className="mt-2 flex-1 text-sm leading-6 text-gray-600 dark:text-gray-400">
{action.description}
</p>
<div className="mt-4">
<ActionLink
href={action.href}
label={action.label}
external={'external' in action ? action.external : undefined}
/>
</div>
</div>
</Card>
))}
</div>
</div>
)
}