2026-03-28 15:16:54 +01:00
import { useMemo } from 'react' ;
2026-03-28 15:00:45 +01:00
import { useNavigate } from 'react-router' ;
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
import { GitBranch , Server , RotateCcw , FileText } from 'lucide-react' ;
2026-04-02 22:56:49 +02:00
import { StatusDot , MonoText , Badge , useGlobalFilters } from '@cameleer/design-system' ;
2026-03-28 13:55:13 +01:00
import { useCorrelationChain } from '../../api/queries/correlation' ;
2026-03-28 15:16:54 +01:00
import { useAgents } from '../../api/queries/agents' ;
2026-04-02 22:56:49 +02:00
import { useRouteCatalog } from '../../api/queries/catalog' ;
2026-03-30 21:42:06 +02:00
import { useAuthStore } from '../../auth/auth-store' ;
2026-03-28 13:55:13 +01:00
import type { ExecutionDetail } from '../../components/ExecutionDiagram/types' ;
2026-03-28 15:37:49 +01:00
import { attributeBadgeColor } from '../../utils/attribute-color' ;
2026-03-30 21:42:06 +02:00
import { RouteControlBar } from './RouteControlBar' ;
2026-03-28 14:49:45 +01:00
import styles from './ExchangeHeader.module.css' ;
2026-03-28 13:55:13 +01:00
interface ExchangeHeaderProps {
detail : ExecutionDetail ;
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
onCorrelatedSelect ? : ( executionId : string , applicationId : string , routeId : string ) = > void ;
2026-03-28 15:42:45 +01:00
onClearSelection ? : ( ) = > void ;
2026-03-28 13:55:13 +01:00
}
type StatusVariant = 'success' | 'error' | 'running' | 'warning' ;
function statusVariant ( s : string ) : StatusVariant {
switch ( s ) {
case 'COMPLETED' : return 'success' ;
case 'FAILED' : return 'error' ;
case 'RUNNING' : return 'running' ;
default : return 'warning' ;
}
}
2026-03-28 15:00:45 +01:00
function statusLabel ( s : string ) : string {
switch ( s ) {
case 'COMPLETED' : return 'OK' ;
case 'FAILED' : return 'ERR' ;
case 'RUNNING' : return 'RUN' ;
default : return s ;
}
}
2026-03-28 13:55:13 +01:00
function formatDuration ( ms : number ) : string {
if ( ms >= 60 _000 ) return ` ${ ( ms / 1000 ) . toFixed ( 0 ) } s ` ;
if ( ms >= 1000 ) return ` ${ ( ms / 1000 ) . toFixed ( 2 ) } s ` ;
return ` ${ ms } ms ` ;
}
2026-03-28 15:42:45 +01:00
export function ExchangeHeader ( { detail , onCorrelatedSelect , onClearSelection } : ExchangeHeaderProps ) {
2026-03-28 15:00:45 +01:00
const navigate = useNavigate ( ) ;
2026-04-02 22:56:49 +02:00
const { timeRange } = useGlobalFilters ( ) ;
2026-03-28 13:55:13 +01:00
const { data : chainResult } = useCorrelationChain ( detail . correlationId ? ? null ) ;
const chain = chainResult ? . data ;
const showChain = chain && chain . length > 1 ;
2026-03-28 15:07:23 +01:00
const attrs = Object . entries ( detail . attributes ? ? { } ) ;
2026-03-28 13:55:13 +01:00
2026-04-02 22:56:49 +02:00
// Look up route state from catalog
const { data : catalog } = useRouteCatalog ( timeRange . start . toISOString ( ) , timeRange . end . toISOString ( ) ) ;
const routeState = useMemo ( ( ) = > {
if ( ! catalog ) return undefined ;
for ( const app of catalog as any [ ] ) {
if ( app . appId !== detail . applicationId ) continue ;
for ( const route of app . routes || [ ] ) {
if ( route . routeId === detail . routeId ) {
return ( route . routeState ? ? 'started' ) as 'started' | 'stopped' | 'suspended' ;
}
}
}
return undefined ;
} , [ catalog , detail . applicationId , detail . routeId ] ) ;
2026-03-30 21:42:06 +02:00
// Look up agent state for icon coloring + route control capability
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
const { data : agents } = useAgents ( undefined , detail . applicationId ) ;
2026-03-30 21:42:06 +02:00
const { agentState , hasRouteControl , hasReplay } = useMemo ( ( ) = > {
if ( ! agents ) return { agentState : undefined , hasRouteControl : false , hasReplay : false } ;
const agentList = agents as any [ ] ;
fix: update agent field names in frontend to match backend DTO
The AgentInstanceResponse backend DTO uses instanceId, displayName,
applicationId, status — but the stale schema.d.ts still had id, name,
application, state. This caused the runtime table to show no data.
- Update schema.d.ts AgentInstanceResponse fields
- Fix AgentHealth: row.id→instanceId, row.name→displayName,
row.application→applicationId, inst.id→instanceId
- Fix AgentInstance: agent.id→instanceId, agent.name→displayName
- Fix ExchangeHeader: agent.id→instanceId, agent.state→status
- Fix LayoutShell search: agent.state→status, agentTps→tps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:36:31 +02:00
const agent = detail . instanceId ? agentList . find ( ( a : any ) = > a . instanceId === detail . instanceId ) : undefined ;
2026-03-30 21:42:06 +02:00
return {
fix: update agent field names in frontend to match backend DTO
The AgentInstanceResponse backend DTO uses instanceId, displayName,
applicationId, status — but the stale schema.d.ts still had id, name,
application, state. This caused the runtime table to show no data.
- Update schema.d.ts AgentInstanceResponse fields
- Fix AgentHealth: row.id→instanceId, row.name→displayName,
row.application→applicationId, inst.id→instanceId
- Fix AgentInstance: agent.id→instanceId, agent.name→displayName
- Fix ExchangeHeader: agent.id→instanceId, agent.state→status
- Fix LayoutShell search: agent.state→status, agentTps→tps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:36:31 +02:00
agentState : agent?.status?.toLowerCase ( ) as 'live' | 'stale' | 'dead' | undefined ,
2026-03-30 21:42:06 +02:00
hasRouteControl : agentList.some ( ( a : any ) = > a . capabilities ? . routeControl === true ) ,
hasReplay : agentList.some ( ( a : any ) = > a . capabilities ? . replay === true ) ,
} ;
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
} , [ agents , detail . instanceId ] ) ;
2026-03-28 15:16:54 +01:00
2026-03-30 21:42:06 +02:00
const roles = useAuthStore ( ( s ) = > s . roles ) ;
const canControl = roles . some ( r = > r === 'OPERATOR' || r === 'ADMIN' ) ;
2026-03-28 13:55:13 +01:00
return (
2026-03-28 14:49:45 +01:00
< div className = { styles . header } >
2026-03-28 15:00:45 +01:00
{ /* Exchange info — always shown */ }
< div className = { styles . info } >
< StatusDot variant = { statusVariant ( detail . status ) } / >
< Badge label = { statusLabel ( detail . status ) } color = { statusVariant ( detail . status ) } / >
2026-03-28 15:07:23 +01:00
{ attrs . length > 0 && (
< >
< span className = { styles . separator } / >
{ attrs . map ( ( [ k , v ] ) = > (
2026-03-28 15:37:49 +01:00
< Badge key = { k } label = { ` ${ k } : ${ v } ` } color = { attributeBadgeColor ( String ( v ) ) } / >
2026-03-28 15:07:23 +01:00
) ) }
< / >
) }
< span className = { styles . separator } / >
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
< button className = { styles . linkBtn } onClick = { ( ) = > { onClearSelection ? . ( ) ; navigate ( ` /exchanges/ ${ detail . applicationId } ` ) ; } } title = "Show all exchanges for this application" >
< span className = { styles . app } > { detail . applicationId } < / span >
2026-03-28 15:14:48 +01:00
< / button >
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
< button className = { styles . linkBtn } onClick = { ( ) = > { onClearSelection ? . ( ) ; navigate ( ` /exchanges/ ${ detail . applicationId } / ${ detail . routeId } ` ) ; } } title = "Show all exchanges for this route" >
2026-03-28 15:14:48 +01:00
< span className = { styles . route } > { detail . routeId } < / span >
< GitBranch size = { 12 } className = { styles . icon } / >
< / button >
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
{ detail . instanceId && (
2026-03-28 15:07:58 +01:00
< >
< span className = { styles . separator } / >
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
< button className = { styles . linkBtn } onClick = { ( ) = > navigate ( ` /runtime/ ${ detail . applicationId } ` ) } title = "All agents for this application" >
< span className = { styles . app } > { detail . applicationId } < / span >
2026-03-28 15:16:54 +01:00
< / button >
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
< button className = { styles . linkBtn } onClick = { ( ) = > navigate ( ` /runtime/ ${ detail . applicationId } / ${ detail . instanceId } ` ) } title = "Agent details" >
< MonoText size = "xs" > { detail . instanceId } < / MonoText >
2026-03-28 15:16:54 +01:00
< Server size = { 12 } className = { agentState === 'live' ? styles.iconLive : agentState === 'stale' ? styles.iconStale : agentState === 'dead' ? styles.iconDead : styles.icon } / >
2026-03-28 15:14:48 +01:00
< / button >
2026-03-28 15:07:58 +01:00
< / >
) }
2026-03-28 15:09:00 +01:00
< span className = { styles . duration } > { formatDuration ( detail . durationMs ) } < / span >
feat: add Logs tab with cursor-paginated search, level filters, and live tail
- Extend GET /api/v1/logs with cursor pagination, multi-level filtering,
optional application scoping, and level count aggregation
- Add exchangeId, instanceId, application, mdc fields to log responses
- Refactor ClickHouseLogStore with keyset pagination (N+1 pattern)
- Add LogSearchRequest/LogSearchResponse core domain records
- Create LogSearchPageResponse wrapper DTO
- Add Logs as 4th content tab (Exchanges | Dashboard | Runtime | Logs)
- Implement LogSearch component with debounced search, level filter bar,
expandable log entries, cursor pagination, and live tail mode
- Add cross-navigation: exchange header → logs, log tab → logs tab
- Update ClickHouseLogStoreIT with cursor, multi-level, cross-app tests
Closes: #104
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:47:16 +02:00
< button
className = { styles . linkBtn }
onClick = { ( ) = > navigate ( ` /logs/ ${ detail . applicationId } ?exchangeId= ${ detail . exchangeId } ` ) }
title = "View surrounding logs"
>
< FileText size = { 12 } className = { styles . icon } / >
< / button >
2026-03-28 15:00:45 +01:00
< / div >
2026-03-30 21:42:06 +02:00
{ /* Route control / replay — only if agent supports it AND user has operator+ role */ }
{ canControl && ( hasRouteControl || hasReplay ) && (
< RouteControlBar
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
application = { detail . applicationId }
2026-03-30 21:42:06 +02:00
routeId = { detail . routeId }
2026-04-02 22:56:49 +02:00
routeState = { routeState }
2026-03-30 21:42:06 +02:00
hasRouteControl = { hasRouteControl }
hasReplay = { hasReplay }
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
agentId = { detail . instanceId }
2026-03-30 21:42:06 +02:00
exchangeId = { detail . exchangeId }
inputHeaders = { detail . inputHeaders }
inputBody = { detail . inputBody }
/ >
) }
2026-03-28 15:24:20 +01:00
{ /* Correlation chain */ }
< div className = { styles . chain } >
< span className = { styles . chainLabel } > Correlated < / span >
{ showChain ? chain . map ( ( ce : any , i : number ) = > {
2026-03-28 14:49:45 +01:00
const isCurrent = ce . executionId === detail . executionId ;
const variant = statusVariant ( ce . status ) ;
2026-03-31 14:39:40 +02:00
const isReplay = ! ! ce . isReplay ;
2026-03-28 14:49:45 +01:00
const statusCls =
variant === 'success' ? styles . chainNodeSuccess
: variant === 'error' ? styles . chainNodeError
: variant === 'running' ? styles . chainNodeRunning
: styles . chainNodeWarning ;
return (
2026-03-28 15:00:45 +01:00
< span key = { ce . executionId } className = { styles . chainEntry } >
{ i > 0 && < span className = { styles . chainArrow } > & rarr ; < / span > }
< button
className = { ` ${ styles . chainNode } ${ statusCls } ${ isCurrent ? styles . chainNodeCurrent : '' } ` }
onClick = { ( ) = > {
2026-03-28 15:26:01 +01:00
if ( ! isCurrent && onCorrelatedSelect ) {
fix: update frontend field names for identity rename (applicationId, instanceId)
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 18:22:16 +02:00
onCorrelatedSelect ( ce . executionId , ce . applicationId ? ? detail . applicationId , ce . routeId ) ;
2026-03-28 15:00:45 +01:00
}
} }
2026-03-30 23:26:55 +02:00
title = { ` ${ ce . executionId } \ n ${ ce . routeId } \ u2014 ${ formatDuration ( ce . durationMs ) } ${ isReplay ? '\n(replay)' : '' } ` }
2026-03-28 15:00:45 +01:00
>
< StatusDot variant = { variant } / >
2026-03-30 23:26:55 +02:00
{ isReplay && < RotateCcw size = { 9 } className = { styles . replayIcon } / > }
2026-03-28 15:00:45 +01:00
< span className = { styles . chainRoute } > { ce . routeId } < / span >
< span className = { styles . chainDuration } > { formatDuration ( ce . durationMs ) } < / span >
< / button >
< / span >
2026-03-28 14:49:45 +01:00
) ;
2026-03-28 15:24:20 +01:00
} ) : (
< span className = { styles . chainNone } > no correlated exchanges found < / span >
) }
2026-03-28 15:29:35 +01:00
{ showChain && ( ( ) = > {
const starts = chain . map ( ( ce : any ) = > new Date ( ce . startTime ) . getTime ( ) ) . filter ( ( t : number ) = > ! isNaN ( t ) ) ;
const ends = chain . map ( ( ce : any ) = > new Date ( ce . endTime ? ? ce . startTime ) . getTime ( ) + ( ce . durationMs ? ? 0 ) ) . filter ( ( t : number ) = > ! isNaN ( t ) ) ;
const totalMs = starts . length > 0 && ends . length > 0 ? Math . max ( . . . ends ) - Math . min ( . . . starts ) : 0 ;
2026-03-28 15:30:22 +01:00
return totalMs > 0 ? < span className = { styles . chainTotal } > { formatDuration ( totalMs ) } < / span > : null ;
2026-03-28 15:29:35 +01:00
} ) ( ) }
2026-03-28 15:24:20 +01:00
< / div >
2026-03-28 13:55:13 +01:00
< / div >
) ;
}