Files
StreamLens/analyzer/tui/modern_interface.py
noisedestroyers 5c2cb1a4ed Modern TUI with Enhanced Protocol Hierarchy Interface
Major Features:
- Complete modern TUI interface with three focused views
- Enhanced multi-column layout: Source | Proto | Destination | Extended | Frame Type | Metrics
- Simplified navigation with 1/2/3 hotkeys instead of F1/F2/F3
- Protocol hierarchy: Transport (TCP/UDP) → Extended (CH10/PTP) → Frame Types
- Classic TUI preserved with --classic flag

Views Implemented:
1. Flow Analysis View: Enhanced multi-column flow overview with protocol detection
2. Packet Decoder View: Three-panel deep inspection (Flows | Frames | Fields)
3. Statistical Analysis View: Four analysis modes with timing and quality metrics

Technical Improvements:
- Left-aligned text columns with IP:port precision
- Transport protocol separation from extended protocols
- Frame type identification (CH10-Data, TMATS, PTP Sync)
- Cross-view communication with persistent flow selection
- Context-sensitive help and status bars
- Comprehensive error handling with console fallback
2025-07-26 22:46:49 -04:00

272 lines
9.9 KiB
Python

"""
Modern TUI Interface for StreamLens
Focused on Flow Analysis, Packet Decoding, and Statistical Analysis
"""
import curses
from typing import TYPE_CHECKING, List, Optional
from enum import Enum
from .navigation import NavigationHandler
from .modern_views import FlowAnalysisView, PacketDecoderView, StatisticalAnalysisView
from ..utils.signal_visualizer import signal_visualizer
if TYPE_CHECKING:
from ..analysis.core import EthernetAnalyzer
class ViewMode(Enum):
FLOW_ANALYSIS = "flow"
PACKET_DECODER = "decode"
STATISTICAL_ANALYSIS = "stats"
class ModernTUIInterface:
"""
Modern StreamLens TUI Interface
Three primary views accessed via 1/2/3:
- 1: Flow Analysis - Visual flow overview with enhanced protocol detection
- 2: Packet Decoder - Deep protocol inspection and field extraction
- 3: Statistical Analysis - Timing analysis, outliers, and quality metrics
"""
def __init__(self, analyzer: 'EthernetAnalyzer'):
self.analyzer = analyzer
self.navigation = NavigationHandler()
# Current view mode
self.current_view = ViewMode.FLOW_ANALYSIS
# Initialize view controllers
self.flow_view = FlowAnalysisView(analyzer)
self.decoder_view = PacketDecoderView(analyzer)
self.stats_view = StatisticalAnalysisView(analyzer)
# Global state
self.selected_flow_key = None
self.show_help = False
def run(self, stdscr):
"""Main TUI loop for modern interface"""
curses.curs_set(0) # Hide cursor
stdscr.keypad(True)
# Set timeout based on live mode
if self.analyzer.is_live:
stdscr.timeout(500) # 0.5 second for live updates
else:
stdscr.timeout(1000) # 1 second for static analysis
while True:
try:
stdscr.clear()
# Draw header with view indicators
self._draw_header(stdscr)
# Draw main content based on current view
if self.current_view == ViewMode.FLOW_ANALYSIS:
self.flow_view.draw(stdscr, self.selected_flow_key)
elif self.current_view == ViewMode.PACKET_DECODER:
self.decoder_view.draw(stdscr, self.selected_flow_key)
elif self.current_view == ViewMode.STATISTICAL_ANALYSIS:
self.stats_view.draw(stdscr, self.selected_flow_key)
# Draw status bar
self._draw_status_bar(stdscr)
# Overlay help if requested
if self.show_help:
self._draw_help_overlay(stdscr)
stdscr.refresh()
# Handle input
key = stdscr.getch()
# Handle timeout (no key pressed) - refresh for live capture
if key == -1 and self.analyzer.is_live:
continue
action = self._handle_input(key)
if action == 'quit':
if self.analyzer.is_live:
self.analyzer.stop_capture = True
break
except curses.error:
# Handle terminal resize or other curses errors
pass
def _draw_header(self, stdscr):
"""Draw the header with application title and view tabs"""
height, width = stdscr.getmaxyx()
# Title
title = "StreamLens - Ethernet Traffic Analyzer"
stdscr.addstr(0, 2, title, curses.A_BOLD)
# Live indicator
if self.analyzer.is_live:
live_text = "[LIVE]"
stdscr.addstr(0, width - len(live_text) - 2, live_text,
curses.A_BOLD | curses.A_BLINK)
# View tabs
tab_line = 1
tab_x = 2
# 1: Flow Analysis
if self.current_view == ViewMode.FLOW_ANALYSIS:
stdscr.addstr(tab_line, tab_x, "[1: Flow Analysis]", curses.A_REVERSE)
else:
stdscr.addstr(tab_line, tab_x, " 1: Flow Analysis ", curses.A_DIM)
tab_x += 19
# 2: Packet Decoder
if self.current_view == ViewMode.PACKET_DECODER:
stdscr.addstr(tab_line, tab_x, "[2: Packet Decoder]", curses.A_REVERSE)
else:
stdscr.addstr(tab_line, tab_x, " 2: Packet Decoder ", curses.A_DIM)
tab_x += 20
# 3: Statistical Analysis
if self.current_view == ViewMode.STATISTICAL_ANALYSIS:
stdscr.addstr(tab_line, tab_x, "[3: Statistical Analysis]", curses.A_REVERSE)
else:
stdscr.addstr(tab_line, tab_x, " 3: Statistical Analysis ", curses.A_DIM)
# Draw separator line
stdscr.addstr(2, 0, "" * width)
def _draw_status_bar(self, stdscr):
"""Draw status bar with context-sensitive help"""
height, width = stdscr.getmaxyx()
status_y = height - 1
# Base controls
status_text = "[1-3]Views [↑↓]Navigate [Enter]Select [H]Help [Q]Quit"
# Add view-specific controls
if self.current_view == ViewMode.FLOW_ANALYSIS:
status_text += " [V]Visualize [D]Decode"
elif self.current_view == ViewMode.PACKET_DECODER:
status_text += " [E]Export [C]Copy"
elif self.current_view == ViewMode.STATISTICAL_ANALYSIS:
status_text += " [R]Refresh [O]Outliers"
# Add live capture controls
if self.analyzer.is_live:
status_text += " [P]Pause"
stdscr.addstr(status_y, 0, status_text[:width-1], curses.A_REVERSE)
def _draw_help_overlay(self, stdscr):
"""Draw help overlay with comprehensive controls"""
height, width = stdscr.getmaxyx()
# Calculate overlay size
overlay_height = min(20, height - 4)
overlay_width = min(80, width - 4)
start_y = (height - overlay_height) // 2
start_x = (width - overlay_width) // 2
# Create help window
help_lines = [
"StreamLens - Help",
"",
"VIEWS:",
" 1 - Flow Analysis: Visual flow overview and protocol detection",
" 2 - Packet Decoder: Deep packet inspection and field extraction",
" 3 - Statistical Analysis: Timing analysis and quality metrics",
"",
"NAVIGATION:",
" ↑/↓ - Navigate items",
" Enter - Select flow/packet",
" Tab - Switch panels (when available)",
" PgUp/PgDn - Scroll large lists",
"",
"ANALYSIS:",
" V - Visualize signals (Flow Analysis)",
" D - Deep decode selected flow",
" E - Export decoded data",
" R - Refresh statistics",
" O - Show outlier details",
"",
"GENERAL:",
" H - Toggle this help",
" Q - Quit application",
"",
"Press any key to close help..."
]
# Draw background
for y in range(overlay_height):
stdscr.addstr(start_y + y, start_x, " " * overlay_width, curses.A_REVERSE)
# Draw help content
for i, line in enumerate(help_lines[:overlay_height-1]):
if start_y + i < height - 1:
display_line = line[:overlay_width-2]
attr = curses.A_REVERSE | curses.A_BOLD if i == 0 else curses.A_REVERSE
stdscr.addstr(start_y + i, start_x + 1, display_line, attr)
def _handle_input(self, key: int) -> str:
"""Handle keyboard input with view-specific actions"""
# Global controls
if key == ord('q') or key == ord('Q'):
return 'quit'
elif key == ord('h') or key == ord('H'):
self.show_help = not self.show_help
return 'help_toggle'
elif self.show_help:
# Any key closes help
self.show_help = False
return 'help_close'
# View switching
elif key == ord('1'):
self.current_view = ViewMode.FLOW_ANALYSIS
return 'view_change'
elif key == ord('2'):
self.current_view = ViewMode.PACKET_DECODER
return 'view_change'
elif key == ord('3'):
self.current_view = ViewMode.STATISTICAL_ANALYSIS
return 'view_change'
# Delegate to current view
elif self.current_view == ViewMode.FLOW_ANALYSIS:
return self.flow_view.handle_input(key, self._get_flows_list())
elif self.current_view == ViewMode.PACKET_DECODER:
return self.decoder_view.handle_input(key, self._get_flows_list())
elif self.current_view == ViewMode.STATISTICAL_ANALYSIS:
return self.stats_view.handle_input(key, self._get_flows_list())
return 'none'
def _get_flows_list(self):
"""Get prioritized list of flows for analysis"""
flows_list = list(self.analyzer.flows.values())
# Sort by relevance: enhanced flows first, then by packet count
flows_list.sort(key=lambda x: (
x.enhanced_analysis.decoder_type != "Standard", # Enhanced first
self.analyzer.statistics_engine.get_max_sigma_deviation(x), # High outliers
x.frame_count # Packet count
), reverse=True)
return flows_list
def get_selected_flow(self):
"""Get currently selected flow for cross-view communication"""
if self.selected_flow_key:
return self.analyzer.flows.get(self.selected_flow_key)
return None
def set_selected_flow(self, flow_key):
"""Set selected flow for cross-view communication"""
self.selected_flow_key = flow_key