2026-04-07 23:22:12 -07:00
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
|
|
import { Card } from '@/libs/frontend-ui-primitives'
|
|
|
|
|
import { explorerFeaturePages } from '@/data/explorerOperations'
|
|
|
|
|
import {
|
|
|
|
|
getMissionControlRelays,
|
|
|
|
|
getMissionControlRelayLabel,
|
|
|
|
|
missionControlApi,
|
|
|
|
|
type MissionControlBridgeStatusResponse,
|
|
|
|
|
} from '@/services/api/missionControl'
|
|
|
|
|
import { plannerApi, type InternalExecutionPlanResponse, type PlannerCapabilitiesResponse } from '@/services/api/planner'
|
|
|
|
|
import { routesApi, type RouteMatrixResponse } from '@/services/api/routes'
|
|
|
|
|
import OperationsPageShell, {
|
|
|
|
|
MetricCard,
|
|
|
|
|
StatusBadge,
|
|
|
|
|
formatNumber,
|
|
|
|
|
relativeAge,
|
|
|
|
|
truncateMiddle,
|
|
|
|
|
} from './OperationsPageShell'
|
|
|
|
|
|
2026-04-10 12:52:17 -07:00
|
|
|
interface OperatorOperationsPageProps {
|
|
|
|
|
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
|
|
|
|
|
initialRouteMatrix?: RouteMatrixResponse | null
|
|
|
|
|
initialPlannerCapabilities?: PlannerCapabilitiesResponse | null
|
|
|
|
|
initialInternalPlan?: InternalExecutionPlanResponse | null
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 23:22:12 -07:00
|
|
|
function relayTone(status?: string): 'normal' | 'warning' | 'danger' {
|
|
|
|
|
const normalized = String(status || 'unknown').toLowerCase()
|
|
|
|
|
if (['degraded', 'stale', 'stopped', 'down'].includes(normalized)) return 'danger'
|
|
|
|
|
if (['paused', 'starting', 'unknown'].includes(normalized)) return 'warning'
|
|
|
|
|
return 'normal'
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 12:52:17 -07:00
|
|
|
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)
|
2026-04-07 23:22:12 -07:00
|
|
|
const [loadingError, setLoadingError] = useState<string | null>(null)
|
|
|
|
|
const page = explorerFeaturePages.operator
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let cancelled = false
|
|
|
|
|
|
|
|
|
|
const load = async () => {
|
|
|
|
|
const [bridgeResult, routesResult, capabilitiesResult, planResult] = await Promise.allSettled([
|
|
|
|
|
missionControlApi.getBridgeStatus(),
|
|
|
|
|
routesApi.getRouteMatrix(),
|
|
|
|
|
plannerApi.getCapabilities(),
|
|
|
|
|
plannerApi.getInternalExecutionPlan(),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
if (cancelled) return
|
|
|
|
|
|
|
|
|
|
if (bridgeResult.status === 'fulfilled') setBridgeStatus(bridgeResult.value)
|
|
|
|
|
if (routesResult.status === 'fulfilled') setRouteMatrix(routesResult.value)
|
|
|
|
|
if (capabilitiesResult.status === 'fulfilled') setPlannerCapabilities(capabilitiesResult.value)
|
|
|
|
|
if (planResult.status === 'fulfilled') setInternalPlan(planResult.value)
|
|
|
|
|
|
|
|
|
|
const failedCount = [bridgeResult, routesResult, capabilitiesResult, planResult].filter(
|
|
|
|
|
(result) => result.status === 'rejected'
|
|
|
|
|
).length
|
|
|
|
|
|
|
|
|
|
if (failedCount === 4) {
|
|
|
|
|
setLoadingError('Operator telemetry is temporarily unavailable from the public explorer APIs.')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
load().catch((error) => {
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
setLoadingError(error instanceof Error ? error.message : 'Operator telemetry is temporarily unavailable from the public explorer APIs.')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
cancelled = true
|
|
|
|
|
}
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const relays = useMemo(() => getMissionControlRelays(bridgeStatus), [bridgeStatus])
|
|
|
|
|
const relayEntries = useMemo(() => Object.entries(relays || {}), [relays])
|
|
|
|
|
const totalQueue = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
relayEntries.reduce((sum, [, relay]) => {
|
|
|
|
|
const queueSize = relay.url_probe?.body?.queue?.size ?? relay.file_snapshot?.queue?.size ?? 0
|
|
|
|
|
return sum + queueSize
|
|
|
|
|
}, 0),
|
|
|
|
|
[relayEntries]
|
|
|
|
|
)
|
|
|
|
|
const providers = plannerCapabilities?.providers || []
|
|
|
|
|
const liveProviders = providers.filter((provider) => provider.live)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<OperationsPageShell page={page}>
|
|
|
|
|
{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>
|
|
|
|
|
</Card>
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
<div className="mb-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
|
|
|
<MetricCard
|
|
|
|
|
title="Relay Fleet"
|
|
|
|
|
value={bridgeStatus?.data?.status || 'unknown'}
|
|
|
|
|
description={`${relayEntries.length} managed lanes · queue ${formatNumber(totalQueue)}`}
|
|
|
|
|
className="border border-sky-200 bg-sky-50/70 dark:border-sky-900/50 dark:bg-sky-950/20"
|
|
|
|
|
/>
|
|
|
|
|
<MetricCard
|
|
|
|
|
title="Live Routes"
|
|
|
|
|
value={formatNumber(routeMatrix?.counts?.filteredLiveRoutes)}
|
|
|
|
|
description={`${formatNumber(routeMatrix?.counts?.blockedOrPlannedRoutes)} planned or blocked routes remain in the matrix.`}
|
|
|
|
|
className="border border-emerald-200 bg-emerald-50/70 dark:border-emerald-900/50 dark:bg-emerald-950/20"
|
|
|
|
|
/>
|
|
|
|
|
<MetricCard
|
|
|
|
|
title="Planner Providers"
|
|
|
|
|
value={formatNumber(liveProviders.length)}
|
|
|
|
|
description={`${formatNumber(providers.length)} published providers in planner v2 capabilities.`}
|
|
|
|
|
/>
|
|
|
|
|
<MetricCard
|
|
|
|
|
title="Fallback Decision"
|
|
|
|
|
value={internalPlan?.plannerResponse?.decision || 'unknown'}
|
|
|
|
|
description={
|
|
|
|
|
internalPlan?.execution?.contractAddress
|
|
|
|
|
? `Execution contract ${truncateMiddle(internalPlan.execution.contractAddress)}`
|
|
|
|
|
: 'Latest internal execution plan posture.'
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mb-8 grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
|
|
|
|
|
<Card title="Managed Relay Lanes">
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{relayEntries.map(([key, relay]) => {
|
|
|
|
|
const snapshot = relay.url_probe?.body || relay.file_snapshot
|
|
|
|
|
const status = snapshot?.status || 'unknown'
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={key}
|
|
|
|
|
className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40"
|
|
|
|
|
>
|
|
|
|
|
<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">
|
|
|
|
|
{getMissionControlRelayLabel(key)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
{snapshot?.destination?.chain_name || 'Unknown destination'} · queue {formatNumber(snapshot?.queue?.size ?? 0)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<StatusBadge status={status} tone={relayTone(status)} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
|
Last source poll {relativeAge(snapshot?.last_source_poll?.at)} · processed {formatNumber(snapshot?.queue?.processed ?? 0)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
{relayEntries.length === 0 ? (
|
|
|
|
|
<p className="text-sm text-gray-600 dark:text-gray-400">No relay lane data available.</p>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<Card title="Execution Readiness">
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<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-sm text-gray-500 dark:text-gray-400">Internal execution plan</div>
|
|
|
|
|
<div className="mt-2 flex items-center gap-3">
|
|
|
|
|
<StatusBadge
|
|
|
|
|
status={internalPlan?.plannerResponse?.decision || 'unknown'}
|
|
|
|
|
tone={internalPlan?.plannerResponse?.decision === 'direct-pool' ? 'normal' : 'warning'}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400">
|
|
|
|
|
Contract {truncateMiddle(internalPlan?.execution?.contractAddress)} · {formatNumber(internalPlan?.plannerResponse?.steps?.length)} planner steps
|
|
|
|
|
</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-sm text-gray-500 dark:text-gray-400">Live providers</div>
|
|
|
|
|
<div className="mt-3 flex flex-wrap gap-2">
|
|
|
|
|
{liveProviders.map((provider) => (
|
|
|
|
|
<span
|
|
|
|
|
key={provider.provider}
|
|
|
|
|
className="rounded-full bg-primary-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
|
|
|
|
|
>
|
|
|
|
|
{provider.provider}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
{liveProviders.length === 0 ? (
|
|
|
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">No live providers reported.</span>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
</OperationsPageShell>
|
|
|
|
|
)
|
|
|
|
|
}
|