refactor: rename SolaceScanScout to Solace and update related configurations
- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation. - Changed default base URL for Playwright tests and updated security headers to reflect the new branding. - Enhanced README and API documentation to include new authentication endpoints and product access details. This refactor aligns the project branding and improves clarity in the API documentation.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Card } from '@/libs/frontend-ui-primitives'
|
||||
import Link from 'next/link'
|
||||
import { explorerFeaturePages } from '@/data/explorerOperations'
|
||||
import { blocksApi, type Block } from '@/services/api/blocks'
|
||||
import {
|
||||
@@ -7,8 +8,14 @@ import {
|
||||
type MissionControlBridgeStatusResponse,
|
||||
type MissionControlChainStatus,
|
||||
} from '@/services/api/missionControl'
|
||||
import { statsApi, type ExplorerStats } from '@/services/api/stats'
|
||||
import {
|
||||
statsApi,
|
||||
type ExplorerRecentActivitySnapshot,
|
||||
type ExplorerStats,
|
||||
type ExplorerTransactionTrendPoint,
|
||||
} from '@/services/api/stats'
|
||||
import { transactionsApi, type Transaction } from '@/services/api/transactions'
|
||||
import { formatWeiAsEth } from '@/utils/format'
|
||||
import OperationsPageShell, {
|
||||
MetricCard,
|
||||
StatusBadge,
|
||||
@@ -17,6 +24,15 @@ import OperationsPageShell, {
|
||||
truncateMiddle,
|
||||
} from './OperationsPageShell'
|
||||
|
||||
interface AnalyticsOperationsPageProps {
|
||||
initialStats?: ExplorerStats | null
|
||||
initialTransactionTrend?: ExplorerTransactionTrendPoint[]
|
||||
initialActivitySnapshot?: ExplorerRecentActivitySnapshot | null
|
||||
initialBlocks?: Block[]
|
||||
initialTransactions?: Transaction[]
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
}
|
||||
|
||||
function getChainStatus(bridgeStatus: MissionControlBridgeStatusResponse | null): MissionControlChainStatus | null {
|
||||
const chains = bridgeStatus?.data?.chains
|
||||
if (!chains) return null
|
||||
@@ -24,11 +40,20 @@ function getChainStatus(bridgeStatus: MissionControlBridgeStatusResponse | null)
|
||||
return firstChain || null
|
||||
}
|
||||
|
||||
export default function AnalyticsOperationsPage() {
|
||||
const [stats, setStats] = useState<ExplorerStats | null>(null)
|
||||
const [blocks, setBlocks] = useState<Block[]>([])
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([])
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
export default function AnalyticsOperationsPage({
|
||||
initialStats = null,
|
||||
initialTransactionTrend = [],
|
||||
initialActivitySnapshot = null,
|
||||
initialBlocks = [],
|
||||
initialTransactions = [],
|
||||
initialBridgeStatus = null,
|
||||
}: AnalyticsOperationsPageProps) {
|
||||
const [stats, setStats] = useState<ExplorerStats | null>(initialStats)
|
||||
const [transactionTrend, setTransactionTrend] = useState<ExplorerTransactionTrendPoint[]>(initialTransactionTrend)
|
||||
const [activitySnapshot, setActivitySnapshot] = useState<ExplorerRecentActivitySnapshot | null>(initialActivitySnapshot)
|
||||
const [blocks, setBlocks] = useState<Block[]>(initialBlocks)
|
||||
const [transactions, setTransactions] = useState<Transaction[]>(initialTransactions)
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.analytics
|
||||
|
||||
@@ -36,8 +61,10 @@ export default function AnalyticsOperationsPage() {
|
||||
let cancelled = false
|
||||
|
||||
const load = async () => {
|
||||
const [statsResult, blocksResult, transactionsResult, bridgeResult] = await Promise.allSettled([
|
||||
const [statsResult, trendResult, snapshotResult, blocksResult, transactionsResult, bridgeResult] = await Promise.allSettled([
|
||||
statsApi.get(),
|
||||
statsApi.getTransactionTrend(),
|
||||
statsApi.getRecentActivitySnapshot(),
|
||||
blocksApi.list({ chain_id: 138, page: 1, page_size: 5 }),
|
||||
transactionsApi.list(138, 1, 5),
|
||||
missionControlApi.getBridgeStatus(),
|
||||
@@ -46,15 +73,17 @@ export default function AnalyticsOperationsPage() {
|
||||
if (cancelled) return
|
||||
|
||||
if (statsResult.status === 'fulfilled') setStats(statsResult.value)
|
||||
if (trendResult.status === 'fulfilled') setTransactionTrend(trendResult.value)
|
||||
if (snapshotResult.status === 'fulfilled') setActivitySnapshot(snapshotResult.value)
|
||||
if (blocksResult.status === 'fulfilled') setBlocks(blocksResult.value.data)
|
||||
if (transactionsResult.status === 'fulfilled') setTransactions(transactionsResult.value.data)
|
||||
if (bridgeResult.status === 'fulfilled') setBridgeStatus(bridgeResult.value)
|
||||
|
||||
const failedCount = [statsResult, blocksResult, transactionsResult, bridgeResult].filter(
|
||||
const failedCount = [statsResult, trendResult, snapshotResult, blocksResult, transactionsResult, bridgeResult].filter(
|
||||
(result) => result.status === 'rejected'
|
||||
).length
|
||||
|
||||
if (failedCount === 4) {
|
||||
if (failedCount === 6) {
|
||||
setLoadingError('Analytics data is temporarily unavailable from the public explorer APIs.')
|
||||
}
|
||||
}
|
||||
@@ -71,6 +100,27 @@ export default function AnalyticsOperationsPage() {
|
||||
}, [])
|
||||
|
||||
const chainStatus = useMemo(() => getChainStatus(bridgeStatus), [bridgeStatus])
|
||||
const trailingWindow = useMemo(() => transactionTrend.slice(0, 7), [transactionTrend])
|
||||
const sevenDayAverage = useMemo(() => {
|
||||
if (trailingWindow.length === 0) return 0
|
||||
const total = trailingWindow.reduce((sum, point) => sum + point.transaction_count, 0)
|
||||
return total / trailingWindow.length
|
||||
}, [trailingWindow])
|
||||
const topDay = useMemo(() => {
|
||||
if (trailingWindow.length === 0) return null
|
||||
return trailingWindow.reduce((best, point) => (point.transaction_count > best.transaction_count ? point : best))
|
||||
}, [trailingWindow])
|
||||
const averageGasUtilization = useMemo(() => {
|
||||
if (blocks.length === 0) return 0
|
||||
return blocks.reduce((sum, block) => {
|
||||
const ratio = block.gas_limit > 0 ? block.gas_used / block.gas_limit : 0
|
||||
return sum + ratio
|
||||
}, 0) / blocks.length
|
||||
}, [blocks])
|
||||
const trendPeak = useMemo(
|
||||
() => trailingWindow.reduce((max, point) => Math.max(max, point.transaction_count), 0),
|
||||
[trailingWindow],
|
||||
)
|
||||
|
||||
return (
|
||||
<OperationsPageShell page={page}>
|
||||
@@ -107,9 +157,111 @@ export default function AnalyticsOperationsPage() {
|
||||
: 'Latest public RPC head age from mission control.'
|
||||
}
|
||||
/>
|
||||
<MetricCard
|
||||
title="7d Avg Tx"
|
||||
value={formatNumber(Math.round(sevenDayAverage))}
|
||||
description="Average daily transactions over the latest seven charted days."
|
||||
className="border border-violet-200 bg-violet-50/70 dark:border-violet-900/50 dark:bg-violet-950/20"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Recent Success Rate"
|
||||
value={activitySnapshot ? `${Math.round(activitySnapshot.success_rate * 100)}%` : 'Unknown'}
|
||||
description="Success rate across the public main-page transaction sample."
|
||||
/>
|
||||
<MetricCard
|
||||
title="Failure Rate"
|
||||
value={activitySnapshot ? `${Math.round(activitySnapshot.failure_rate * 100)}%` : 'Unknown'}
|
||||
description="The complement to the recent success rate in the visible sample."
|
||||
className="border border-rose-200 bg-rose-50/70 dark:border-rose-900/50 dark:bg-rose-950/20"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Avg Gas Used"
|
||||
value={activitySnapshot ? formatNumber(Math.round(activitySnapshot.average_gas_used)) : 'Unknown'}
|
||||
description="Average gas used in the recent sampled transactions."
|
||||
/>
|
||||
<MetricCard
|
||||
title="Avg Block Gas"
|
||||
value={`${Math.round(averageGasUtilization * 100)}%`}
|
||||
description="Average gas utilization across the latest visible blocks."
|
||||
className="border border-amber-200 bg-amber-50/70 dark:border-amber-900/50 dark:bg-amber-950/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-8 grid gap-6 lg:grid-cols-[1fr_1fr]">
|
||||
<Card title="Activity Trend">
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-3">
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Peak Day</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{topDay ? formatNumber(topDay.transaction_count) : 'Unknown'}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">{topDay?.date || 'No trend data yet'}</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Contract Creations</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{formatNumber(activitySnapshot?.contract_creations)}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">Within the sampled recent transaction feed.</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Avg Sample Fee</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{activitySnapshot ? formatWeiAsEth(Math.round(activitySnapshot.average_fee_wei).toString(), 6) : 'Unknown'}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">Average fee from the recent public transaction sample.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activitySnapshot ? (
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Token Transfer Share</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{Math.round(activitySnapshot.token_transfer_share * 100)}%
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">Sampled transactions involving token transfers.</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Contract Call Share</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{Math.round(activitySnapshot.contract_call_share * 100)}%
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">Sampled transactions calling contracts.</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Creation Share</div>
|
||||
<div className="mt-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{Math.round(activitySnapshot.contract_creation_share * 100)}%
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-600 dark:text-gray-400">Sampled transactions deploying contracts.</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-3">
|
||||
{trailingWindow.map((point) => {
|
||||
const width = trendPeak > 0 ? Math.max(8, Math.round((point.transaction_count / trendPeak) * 100)) : 0
|
||||
return (
|
||||
<div key={point.date}>
|
||||
<div className="mb-1 flex items-center justify-between text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>{point.date}</span>
|
||||
<span>{formatNumber(point.transaction_count)} tx</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-gray-200 dark:bg-gray-800">
|
||||
<div className="h-2 rounded-full bg-primary-600" style={{ width: `${width}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{trailingWindow.length === 0 ? (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Trend data is temporarily unavailable.</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Recent Blocks">
|
||||
<div className="space-y-4">
|
||||
{blocks.map((block) => (
|
||||
@@ -119,15 +271,18 @@ export default function AnalyticsOperationsPage() {
|
||||
>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
<Link href={`/blocks/${block.number}`} className="text-base font-semibold text-primary-600 hover:underline">
|
||||
Block {formatNumber(block.number)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{truncateMiddle(block.hash)} · miner {truncateMiddle(block.miner)}
|
||||
{truncateMiddle(block.hash)} · miner{' '}
|
||||
<Link href={`/addresses/${block.miner}`} className="text-primary-600 hover:underline">
|
||||
{truncateMiddle(block.miner)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{formatNumber(block.transaction_count)} tx · {relativeAge(block.timestamp)}
|
||||
{formatNumber(block.transaction_count)} tx · {Math.round((block.gas_limit > 0 ? block.gas_used / block.gas_limit : 0) * 100)}% gas · {relativeAge(block.timestamp)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -147,11 +302,26 @@ export default function AnalyticsOperationsPage() {
|
||||
>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
<Link href={`/transactions/${transaction.hash}`} className="text-base font-semibold text-primary-600 hover:underline">
|
||||
{truncateMiddle(transaction.hash, 12, 10)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Block {formatNumber(transaction.block_number)} · from {truncateMiddle(transaction.from_address)}
|
||||
Block{' '}
|
||||
<Link href={`/blocks/${transaction.block_number}`} className="text-primary-600 hover:underline">
|
||||
{formatNumber(transaction.block_number)}
|
||||
</Link>
|
||||
{' '}· from{' '}
|
||||
<Link href={`/addresses/${transaction.from_address}`} className="text-primary-600 hover:underline">
|
||||
{truncateMiddle(transaction.from_address)}
|
||||
</Link>
|
||||
{transaction.to_address ? (
|
||||
<>
|
||||
{' '}· to{' '}
|
||||
<Link href={`/addresses/${transaction.to_address}`} className="text-primary-600 hover:underline">
|
||||
{truncateMiddle(transaction.to_address)}
|
||||
</Link>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -164,6 +334,13 @@ export default function AnalyticsOperationsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2 text-xs">
|
||||
{transaction.method ? <StatusBadge status={transaction.method} tone="warning" /> : null}
|
||||
{transaction.contract_address ? <StatusBadge status="contract creation" tone="warning" /> : null}
|
||||
{transaction.token_transfers && transaction.token_transfers.length > 0 ? (
|
||||
<StatusBadge status={`${transaction.token_transfers.length} token transfer${transaction.token_transfers.length === 1 ? '' : 's'}`} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{transactions.length === 0 ? (
|
||||
|
||||
@@ -100,9 +100,13 @@ function ActionLink({
|
||||
)
|
||||
}
|
||||
|
||||
export default function BridgeMonitoringPage() {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
const [feedState, setFeedState] = useState<FeedState>('connecting')
|
||||
export default function BridgeMonitoringPage({
|
||||
initialBridgeStatus = null,
|
||||
}: {
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
}) {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [feedState, setFeedState] = useState<FeedState>(initialBridgeStatus ? 'fallback' : 'connecting')
|
||||
const page = explorerFeaturePages.bridge
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -30,21 +30,55 @@ interface TokenPoolRecord {
|
||||
pools: MissionControlLiquidityPool[]
|
||||
}
|
||||
|
||||
interface EndpointCard {
|
||||
name: string
|
||||
method: string
|
||||
href: string
|
||||
notes: string
|
||||
}
|
||||
|
||||
interface LiquidityOperationsPageProps {
|
||||
initialTokenList?: TokenListResponse | null
|
||||
initialRouteMatrix?: RouteMatrixResponse | null
|
||||
initialPlannerCapabilities?: PlannerCapabilitiesResponse | null
|
||||
initialInternalPlan?: InternalExecutionPlanResponse | null
|
||||
initialTokenPoolRecords?: TokenPoolRecord[]
|
||||
}
|
||||
|
||||
function routePairLabel(routeId: string, routeLabel: string, tokenIn?: string, tokenOut?: string): string {
|
||||
return [tokenIn, tokenOut].filter(Boolean).join(' / ') || routeLabel || routeId
|
||||
}
|
||||
|
||||
export default function LiquidityOperationsPage() {
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(null)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(null)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(null)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(null)
|
||||
const [tokenPoolRecords, setTokenPoolRecords] = useState<TokenPoolRecord[]>([])
|
||||
export default function LiquidityOperationsPage({
|
||||
initialTokenList = null,
|
||||
initialRouteMatrix = null,
|
||||
initialPlannerCapabilities = null,
|
||||
initialInternalPlan = null,
|
||||
initialTokenPoolRecords = [],
|
||||
}: LiquidityOperationsPageProps) {
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(initialTokenList)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(initialRouteMatrix)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(initialPlannerCapabilities)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(initialInternalPlan)
|
||||
const [tokenPoolRecords, setTokenPoolRecords] = useState<TokenPoolRecord[]>(initialTokenPoolRecords)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const [copiedEndpoint, setCopiedEndpoint] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
if (
|
||||
initialTokenList &&
|
||||
initialRouteMatrix &&
|
||||
initialPlannerCapabilities &&
|
||||
initialInternalPlan &&
|
||||
initialTokenPoolRecords.length > 0
|
||||
) {
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const [tokenListResult, routeMatrixResult, plannerCapabilitiesResult, planResult] =
|
||||
await Promise.allSettled([
|
||||
@@ -102,7 +136,13 @@ export default function LiquidityOperationsPage() {
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
}, [
|
||||
initialInternalPlan,
|
||||
initialPlannerCapabilities,
|
||||
initialRouteMatrix,
|
||||
initialTokenList,
|
||||
initialTokenPoolRecords,
|
||||
])
|
||||
|
||||
const featuredTokens = useMemo(
|
||||
() => selectFeaturedLiquidityTokens(tokenList?.tokens || []),
|
||||
@@ -139,7 +179,7 @@ export default function LiquidityOperationsPage() {
|
||||
[routeMatrix, aggregatedPools.length, featuredTokens.length, livePlannerProviders.length, internalPlan?.plannerResponse?.decision, routeBackedPoolAddresses.length]
|
||||
)
|
||||
|
||||
const endpointCards = [
|
||||
const endpointCards: EndpointCard[] = [
|
||||
{
|
||||
name: 'Canonical route matrix',
|
||||
method: 'GET',
|
||||
@@ -166,6 +206,18 @@ export default function LiquidityOperationsPage() {
|
||||
},
|
||||
]
|
||||
|
||||
const copyEndpoint = async (endpoint: EndpointCard) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(endpoint.href)
|
||||
setCopiedEndpoint(endpoint.name)
|
||||
window.setTimeout(() => {
|
||||
setCopiedEndpoint((current) => (current === endpoint.name ? null : current))
|
||||
}, 1500)
|
||||
} catch {
|
||||
setCopiedEndpoint(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="container mx-auto px-4 py-6 sm:py-8">
|
||||
<div className="mb-8 max-w-4xl">
|
||||
@@ -258,9 +310,16 @@ export default function LiquidityOperationsPage() {
|
||||
</div>
|
||||
))}
|
||||
{aggregatedPools.length === 0 ? (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
No live pool inventory is available right now.
|
||||
</p>
|
||||
<div className="rounded-2xl border border-amber-200 bg-amber-50/70 p-4 dark:border-amber-900/50 dark:bg-amber-950/20">
|
||||
<p className="text-sm leading-6 text-amber-900 dark:text-amber-100">
|
||||
Mission-control pool inventory is currently empty, but the live route matrix still references{' '}
|
||||
{formatNumber(routeBackedPoolAddresses.length)} pool-backed legs across{' '}
|
||||
{formatNumber(routeMatrix?.counts?.filteredLiveRoutes)} published live routes.
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-6 text-amber-900/80 dark:text-amber-100/80">
|
||||
Use the highlighted route-backed paths below and the public route matrix endpoint while pool inventory catches up.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
@@ -339,12 +398,9 @@ export default function LiquidityOperationsPage() {
|
||||
<Card title="Explorer Access Points">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{endpointCards.map((endpoint) => (
|
||||
<a
|
||||
<div
|
||||
key={endpoint.href}
|
||||
href={endpoint.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-2xl border border-gray-200 bg-white p-5 transition hover:border-primary-400 hover:shadow-md dark:border-gray-700 dark:bg-gray-800"
|
||||
className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<div className="flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white">{endpoint.name}</div>
|
||||
@@ -356,7 +412,24 @@ export default function LiquidityOperationsPage() {
|
||||
{endpoint.href}
|
||||
</div>
|
||||
<div className="mt-3 text-sm leading-6 text-gray-600 dark:text-gray-400">{endpoint.notes}</div>
|
||||
</a>
|
||||
<div className="mt-4 flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyEndpoint(endpoint)}
|
||||
className="rounded-full border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:border-primary-400 hover:text-primary-700 dark:border-gray-600 dark:text-gray-300 dark:hover:text-primary-300"
|
||||
>
|
||||
{copiedEndpoint === endpoint.name ? 'Copied' : 'Copy endpoint'}
|
||||
</button>
|
||||
{endpoint.name === 'Mission-control token pools' ? (
|
||||
<Link
|
||||
href="/pools"
|
||||
className="rounded-full border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:border-primary-400 hover:text-primary-700 dark:border-gray-600 dark:text-gray-300 dark:hover:text-primary-300"
|
||||
>
|
||||
Open pools page
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
@@ -404,12 +477,12 @@ export default function LiquidityOperationsPage() {
|
||||
>
|
||||
Open wallet tools
|
||||
</Link>
|
||||
<a
|
||||
href="/docs.html"
|
||||
<Link
|
||||
href="/docs"
|
||||
className="rounded-full border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:border-primary-400 hover:text-primary-700 dark:border-gray-600 dark:text-gray-300 dark:hover:text-primary-300"
|
||||
>
|
||||
Explorer docs
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -45,14 +45,28 @@ function ActionLink({
|
||||
)
|
||||
}
|
||||
|
||||
export default function MoreOperationsPage() {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(null)
|
||||
const [networksConfig, setNetworksConfig] = useState<NetworksConfigResponse | null>(null)
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(null)
|
||||
const [capabilities, setCapabilities] = useState<CapabilitiesResponse | null>(null)
|
||||
interface OperationsHubPageProps {
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
initialRouteMatrix?: RouteMatrixResponse | null
|
||||
initialNetworksConfig?: NetworksConfigResponse | null
|
||||
initialTokenList?: TokenListResponse | null
|
||||
initialCapabilities?: CapabilitiesResponse | null
|
||||
}
|
||||
|
||||
export default function OperationsHubPage({
|
||||
initialBridgeStatus = null,
|
||||
initialRouteMatrix = null,
|
||||
initialNetworksConfig = null,
|
||||
initialTokenList = null,
|
||||
initialCapabilities = null,
|
||||
}: OperationsHubPageProps) {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(initialRouteMatrix)
|
||||
const [networksConfig, setNetworksConfig] = useState<NetworksConfigResponse | null>(initialNetworksConfig)
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(initialTokenList)
|
||||
const [capabilities, setCapabilities] = useState<CapabilitiesResponse | null>(initialCapabilities)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.more
|
||||
const page = explorerFeaturePages.operations
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -17,6 +17,13 @@ import OperationsPageShell, {
|
||||
truncateMiddle,
|
||||
} from './OperationsPageShell'
|
||||
|
||||
interface OperatorOperationsPageProps {
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
initialRouteMatrix?: RouteMatrixResponse | null
|
||||
initialPlannerCapabilities?: PlannerCapabilitiesResponse | null
|
||||
initialInternalPlan?: InternalExecutionPlanResponse | null
|
||||
}
|
||||
|
||||
function relayTone(status?: string): 'normal' | 'warning' | 'danger' {
|
||||
const normalized = String(status || 'unknown').toLowerCase()
|
||||
if (['degraded', 'stale', 'stopped', 'down'].includes(normalized)) return 'danger'
|
||||
@@ -24,11 +31,16 @@ function relayTone(status?: string): 'normal' | 'warning' | 'danger' {
|
||||
return 'normal'
|
||||
}
|
||||
|
||||
export default function OperatorOperationsPage() {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(null)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(null)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(null)
|
||||
export default function OperatorOperationsPage({
|
||||
initialBridgeStatus = null,
|
||||
initialRouteMatrix = null,
|
||||
initialPlannerCapabilities = null,
|
||||
initialInternalPlan = null,
|
||||
}: OperatorOperationsPageProps) {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(initialRouteMatrix)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(initialPlannerCapabilities)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(initialInternalPlan)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.operator
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export default function PoolsOperationsPage() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Liquidity Shortcuts">
|
||||
<Card title="Pool operation shortcuts">
|
||||
<div className="space-y-4 text-sm leading-6 text-gray-600 dark:text-gray-400">
|
||||
<p>
|
||||
The broader liquidity page now shows live route, planner, and pool access together.
|
||||
|
||||
@@ -10,6 +10,12 @@ import {
|
||||
type RouteMatrixResponse,
|
||||
} from '@/services/api/routes'
|
||||
|
||||
interface RoutesMonitoringPageProps {
|
||||
initialRouteMatrix?: RouteMatrixResponse | null
|
||||
initialNetworks?: ExplorerNetwork[]
|
||||
initialPools?: MissionControlLiquidityPool[]
|
||||
}
|
||||
|
||||
const canonicalLiquidityToken = '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22'
|
||||
|
||||
function relativeAge(isoString?: string): string {
|
||||
@@ -80,10 +86,14 @@ function ActionLink({
|
||||
)
|
||||
}
|
||||
|
||||
export default function RoutesMonitoringPage() {
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(null)
|
||||
const [networks, setNetworks] = useState<ExplorerNetwork[]>([])
|
||||
const [pools, setPools] = useState<MissionControlLiquidityPool[]>([])
|
||||
export default function RoutesMonitoringPage({
|
||||
initialRouteMatrix = null,
|
||||
initialNetworks = [],
|
||||
initialPools = [],
|
||||
}: RoutesMonitoringPageProps) {
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(initialRouteMatrix)
|
||||
const [networks, setNetworks] = useState<ExplorerNetwork[]>(initialNetworks)
|
||||
const [pools, setPools] = useState<MissionControlLiquidityPool[]>(initialPools)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.routes
|
||||
|
||||
@@ -389,7 +399,7 @@ export default function RoutesMonitoringPage() {
|
||||
<ActionLink
|
||||
href={action.href}
|
||||
label={action.label}
|
||||
external={'external' in action ? action.external : undefined}
|
||||
external={Boolean((action as { external?: boolean }).external)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,13 +12,29 @@ import OperationsPageShell, {
|
||||
relativeAge,
|
||||
} from './OperationsPageShell'
|
||||
|
||||
export default function SystemOperationsPage() {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
const [networksConfig, setNetworksConfig] = useState<NetworksConfigResponse | null>(null)
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(null)
|
||||
const [capabilities, setCapabilities] = useState<CapabilitiesResponse | null>(null)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(null)
|
||||
const [stats, setStats] = useState<ExplorerStats | null>(null)
|
||||
interface SystemOperationsPageProps {
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
initialNetworksConfig?: NetworksConfigResponse | null
|
||||
initialTokenList?: TokenListResponse | null
|
||||
initialCapabilities?: CapabilitiesResponse | null
|
||||
initialRouteMatrix?: RouteMatrixResponse | null
|
||||
initialStats?: ExplorerStats | null
|
||||
}
|
||||
|
||||
export default function SystemOperationsPage({
|
||||
initialBridgeStatus = null,
|
||||
initialNetworksConfig = null,
|
||||
initialTokenList = null,
|
||||
initialCapabilities = null,
|
||||
initialRouteMatrix = null,
|
||||
initialStats = null,
|
||||
}: SystemOperationsPageProps) {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [networksConfig, setNetworksConfig] = useState<NetworksConfigResponse | null>(initialNetworksConfig)
|
||||
const [tokenList, setTokenList] = useState<TokenListResponse | null>(initialTokenList)
|
||||
const [capabilities, setCapabilities] = useState<CapabilitiesResponse | null>(initialCapabilities)
|
||||
const [routeMatrix, setRouteMatrix] = useState<RouteMatrixResponse | null>(initialRouteMatrix)
|
||||
const [stats, setStats] = useState<ExplorerStats | null>(initialStats)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.system
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@ import OperationsPageShell, {
|
||||
truncateMiddle,
|
||||
} from './OperationsPageShell'
|
||||
|
||||
interface WethOperationsPageProps {
|
||||
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
||||
initialPlannerCapabilities?: PlannerCapabilitiesResponse | null
|
||||
initialInternalPlan?: InternalExecutionPlanResponse | null
|
||||
}
|
||||
|
||||
function relayTone(status?: string): 'normal' | 'warning' | 'danger' {
|
||||
const normalized = String(status || 'unknown').toLowerCase()
|
||||
if (['degraded', 'stale', 'stopped', 'down'].includes(normalized)) return 'danger'
|
||||
@@ -27,10 +33,14 @@ function relaySnapshot(relay: MissionControlRelayPayload | undefined) {
|
||||
return relay?.url_probe?.body || relay?.file_snapshot
|
||||
}
|
||||
|
||||
export default function WethOperationsPage() {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(null)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(null)
|
||||
export default function WethOperationsPage({
|
||||
initialBridgeStatus = null,
|
||||
initialPlannerCapabilities = null,
|
||||
initialInternalPlan = null,
|
||||
}: WethOperationsPageProps) {
|
||||
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(initialBridgeStatus)
|
||||
const [plannerCapabilities, setPlannerCapabilities] = useState<PlannerCapabilitiesResponse | null>(initialPlannerCapabilities)
|
||||
const [internalPlan, setInternalPlan] = useState<InternalExecutionPlanResponse | null>(initialInternalPlan)
|
||||
const [loadingError, setLoadingError] = useState<string | null>(null)
|
||||
const page = explorerFeaturePages.weth
|
||||
|
||||
@@ -85,6 +95,13 @@ export default function WethOperationsPage() {
|
||||
|
||||
return (
|
||||
<OperationsPageShell page={page}>
|
||||
<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">
|
||||
These WETH references are bridge and transport surfaces, not a claim that Ethereum mainnet WETH contracts are native Chain 138 assets.
|
||||
Use this page to review wrapped-asset lane posture, counterpart contracts, and operational dependencies.
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
{loadingError ? (
|
||||
<Card className="mb-6 border border-red-200 bg-red-50/70 dark:border-red-900/50 dark:bg-red-950/20">
|
||||
<p className="text-sm leading-6 text-red-900 dark:text-red-100">{loadingError}</p>
|
||||
|
||||
Reference in New Issue
Block a user