The Terminal component provides a live view of agent activity through WebSocket-based log streaming. It displays color-coded logs with timestamps, allowing you to monitor the agent’s decisions and actions in real-time.
Overview
The terminal interface streams logs directly from the backend using WebSocket connections, providing instant feedback on:
Service monitoring events
Diagnostic findings
Execution commands
Error messages
System notifications
Real-time Updates WebSocket connection delivers logs with sub-second latency
Auto-scrolling Terminal automatically scrolls to the latest log entry
Color-coded Logs Different log types use distinct colors for easy scanning
500 Log Buffer Keeps the last 500 logs in memory to prevent overflow
WebSocket Connection
The terminal uses a custom React hook (useLogs) to manage the WebSocket connection:
// hooks/use-logs.ts
export function useLogs () {
const [ logs , setLogs ] = useState < LogEntry []>([]);
const [ isConnected , setIsConnected ] = useState ( false );
const ws = useRef < WebSocket | null >( null );
useEffect (() => {
const connect = () => {
// Convert HTTP URL to WebSocket URL
const wsUrl = API_URL . replace ( / ^ http/ , 'ws' ) + '/ws/logs' ;
const socket = new WebSocket ( wsUrl );
socket . onopen = () => {
setIsConnected ( true );
setLogs ( prev => [ ... prev , {
timestamp: new Date (). toISOString (),
type: 'system' ,
message: 'Connected to Sentinel AI Event Stream'
}]);
};
socket . onmessage = ( event ) => {
const data = JSON . parse ( event . data );
setLogs ( prev => {
const newLogs = [ ... prev , data ];
return newLogs . slice ( - 500 ); // Keep last 500 logs
});
};
socket . onclose = () => {
setIsConnected ( false );
setTimeout ( connect , 5000 ); // Reconnect after 5s
};
ws . current = socket ;
};
connect ();
return () => ws . current ?. close ();
}, []);
return { logs , isConnected };
}
The WebSocket connection automatically reconnects every 5 seconds if the connection is lost.
Log Entry Structure
Each log entry follows this TypeScript interface:
export interface LogEntry {
timestamp : string ; // ISO 8601 timestamp
type : string ; // Log type (monitor, plan, execute, etc.)
message : string ; // Human-readable message
details ?: Record < string , unknown >; // Optional structured data
}
Backend WebSocket Handler
The FastAPI backend implements the WebSocket endpoint:
# src/api/routes.py
@router.websocket ( "/ws/logs" )
async def websocket_logs ( websocket : WebSocket):
await websocket.accept()
queue = await bus.subscribe()
try :
while True :
data = await queue.get()
await websocket.send_json(data)
except WebSocketDisconnect:
bus.unsubscribe(queue)
except Exception as e:
log( "error" , f "WebSocket error: { e } " )
bus.unsubscribe(queue)
The backend uses an event bus pattern to distribute logs to all connected WebSocket clients.
Multiple dashboard instances can connect simultaneously - each receives the same log stream.
Terminal UI Component
The Terminal component renders logs with syntax highlighting:
// components/dashboard/Terminal.tsx
export function Terminal ({ logs } : TerminalProps ) {
const endRef = useRef < HTMLDivElement >( null );
// Auto-scroll to bottom when new logs arrive
useEffect (() => {
endRef . current ?. scrollIntoView ({ behavior: "smooth" });
}, [ logs ]);
const getLogColor = ( type : string ) => {
switch ( type . toLowerCase ()) {
case 'error' : return 'text-red-400' ;
case 'warning' : return 'text-yellow-400' ;
case 'success' : return 'text-green-400' ;
case 'monitor' : return 'text-blue-300' ;
case 'plan' : return 'text-purple-400' ;
case 'execute' : return 'text-orange-400' ;
case 'system' : return 'text-gray-400 italic' ;
default : return 'text-gray-300' ;
}
};
return (
< div className = "rounded-lg border bg-black/40 backdrop-blur-xl" >
< div className = "flex items-center px-4 py-2 border-b" >
< TerminalIcon className = "w-4 h-4 mr-2" />
< span className = "text-xs" > Live Agent Logs </ span >
</ div >
< div className = "overflow-y-auto p-4 space-y-1 font-mono text-sm" >
{ logs . map (( log , i ) => (
< div key = { i } className = "flex gap-2" >
< span className = "text-muted-foreground text-xs" >
[ {new Date ( log . timestamp ). toLocaleTimeString () } ]
</ span >
< span className = { getLogColor ( log . type ) } >
< span className = "font-bold mr-2 uppercase text-[10px]" >
{ log . type }
</ span >
{ log . message }
</ span >
</ div >
)) }
< div ref = { endRef } />
</ div >
</ div >
);
}
Log Types and Colors
ERROR Red - Critical errors requiring attention
WARNING Yellow - Warnings and non-critical issues
SUCCESS Green - Successful operations
MONITOR Blue - Service monitoring events
PLAN Purple - Planning and decision-making
EXECUTE Orange - Command execution
SYSTEM Gray - System notifications
Connection Status Indicator
The dashboard displays the WebSocket connection status:
// app/page.tsx
< div className = "flex items-center gap-2" >
< span className = { `w-2 h-2 rounded-full ${
isConnected ? 'bg-green-500 animate-pulse' : 'bg-red-500'
} ` } />
< span className = "text-xs text-muted-foreground" >
{ isConnected ? 'Real-time via WebSocket' : 'Connecting...' }
</ span >
</ div >
If the connection status shows “Connecting…” for more than 10 seconds, check that the backend server is running and accessible.
Example Log Stream
Here’s what a typical log stream looks like:
[14:23:45] SYSTEM Connected to Sentinel AI Event Stream
[14:23:46] MONITOR Checking service status for nginx
[14:23:47] MONITOR Checking service status for postgresql
[14:23:48] SUCCESS All services healthy
[14:25:12] MONITOR Service 'nginx' detected as stopped
[14:25:13] PLAN Analyzing failure: nginx not responding
[14:25:14] PLAN Proposed fix: sudo systemctl restart nginx
[14:25:15] EXECUTE Running remediation command
[14:25:16] SUCCESS Service restored successfully
Log Buffer Management : The terminal keeps only the last 500 logs to prevent memory issues during long-running sessions.
The log buffer is managed automatically:
setLogs ( prev => {
const newLogs = [ ... prev , data ];
return newLogs . slice ( - 500 ); // Keep last 500
});
Filtering and Search
While the current implementation displays all logs, you can filter by log type:
const filteredLogs = logs . filter ( log =>
log . type . toLowerCase () === 'error'
);
For advanced filtering, consider implementing a search bar that filters logs by type, message content, or time range.
Next Steps
Action History View filtered high-level actions instead of raw logs
Chat Interface Ask questions about logs using natural language
WebSocket API Learn about the backend event distribution system