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
94 lines
4.0 KiB
Python
94 lines
4.0 KiB
Python
"""
|
|
Navigation and input handling for the TUI
|
|
"""
|
|
|
|
import curses
|
|
from typing import List
|
|
from ..models import FlowStats
|
|
|
|
|
|
class NavigationHandler:
|
|
"""Handles navigation and input for the TUI"""
|
|
|
|
def __init__(self):
|
|
self.current_view = 'main' # main, dissection
|
|
self.selected_flow = 0
|
|
self.scroll_offset = 0
|
|
self.show_timeline = True # Toggle for bottom timeline plot
|
|
|
|
def handle_input(self, key: int, flows_list: List[FlowStats]) -> str:
|
|
"""
|
|
Handle keyboard input and return action
|
|
|
|
Returns:
|
|
Action string: 'quit', 'view_change', 'selection_change', 'none'
|
|
"""
|
|
if key == ord('q'):
|
|
return 'quit'
|
|
elif key == ord('d'):
|
|
self.current_view = 'dissection'
|
|
return 'view_change'
|
|
elif key == ord('m') or key == 27: # 'm' or ESC to return to main
|
|
self.current_view = 'main'
|
|
return 'view_change'
|
|
elif key == curses.KEY_UP and self.current_view == 'main':
|
|
self.selected_flow = max(0, self.selected_flow - 1)
|
|
return 'selection_change'
|
|
elif key == curses.KEY_DOWN and self.current_view == 'main':
|
|
max_items = self._get_total_display_items(flows_list)
|
|
self.selected_flow = min(max_items - 1, self.selected_flow + 1)
|
|
return 'selection_change'
|
|
elif key == ord('t'): # Toggle timeline plot
|
|
self.show_timeline = not self.show_timeline
|
|
return 'view_change'
|
|
elif key == curses.KEY_PPAGE and self.current_view == 'main': # Page Up
|
|
self.selected_flow = max(0, self.selected_flow - 10)
|
|
return 'selection_change'
|
|
elif key == curses.KEY_NPAGE and self.current_view == 'main': # Page Down
|
|
max_items = self._get_total_display_items(flows_list)
|
|
self.selected_flow = min(max_items - 1, self.selected_flow + 10)
|
|
return 'selection_change'
|
|
elif key == curses.KEY_HOME and self.current_view == 'main': # Home
|
|
self.selected_flow = 0
|
|
return 'selection_change'
|
|
elif key == curses.KEY_END and self.current_view == 'main': # End
|
|
max_items = self._get_total_display_items(flows_list)
|
|
self.selected_flow = max_items - 1
|
|
return 'selection_change'
|
|
elif key == ord('v') and self.current_view == 'main': # Visualize Chapter 10 signals
|
|
return 'visualize'
|
|
elif key == ord('\t') and self.current_view == 'main': # Tab key to switch detail panel tabs
|
|
return 'switch_tab'
|
|
|
|
return 'none'
|
|
|
|
def _get_total_display_items(self, flows_list: List[FlowStats]) -> int:
|
|
"""Calculate total number of selectable items (flows + frame types)"""
|
|
total = 0
|
|
for flow in flows_list:
|
|
total += 1 # Flow itself
|
|
total += len(flow.frame_types) # Frame types under this flow
|
|
return total
|
|
|
|
def get_status_bar_text(self) -> str:
|
|
"""Get status bar text based on current view"""
|
|
if self.current_view == 'main':
|
|
timeline_status = "ON" if self.show_timeline else "OFF"
|
|
return f"[↑↓]navigate [Tab]switch tabs [PgUp/PgDn]scroll [t]imeline:{timeline_status} [v]isualize CH10 [d]issection [q]uit"
|
|
elif self.current_view == 'dissection':
|
|
return "[m]ain view [q]uit"
|
|
else:
|
|
return "[m]ain [d]issection [q]uit"
|
|
|
|
def has_chapter10_data(self, flow: FlowStats) -> bool:
|
|
"""Check if a flow contains Chapter 10 data"""
|
|
# Check if any frame types in the flow are Chapter 10 related
|
|
for frame_type in flow.frame_types.keys():
|
|
if 'CH10' in frame_type.upper() or 'TMATS' in frame_type.upper():
|
|
return True
|
|
|
|
# Check detected protocol types
|
|
if 'CHAPTER10' in flow.detected_protocol_types or 'CH10' in flow.detected_protocol_types:
|
|
return True
|
|
|
|
return False |