Files
StreamLens/analyzer/tui/panels/detail_panel.py

177 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Right panel - Flow details with frame type table
"""
from typing import List, Optional, Tuple
import curses
from ...models import FlowStats, FrameTypeStats
class DetailPanel:
"""Right panel showing detailed flow information"""
def draw(self, stdscr, x_offset: int, y_offset: int, width: int,
flows_list: List[FlowStats], selected_flow: int, max_height: Optional[int] = None):
"""Draw detailed information panel for selected flow or frame type"""
if not flows_list:
stdscr.addstr(y_offset, x_offset, "No flows available")
return
# Get the selected flow and frame type
flow, selected_frame_type = self._get_selected_flow_and_frame_type(flows_list, selected_flow)
if not flow:
stdscr.addstr(y_offset, x_offset, "No flow selected")
return
if max_height is None:
height, _ = stdscr.getmaxyx()
max_lines = height - y_offset - 2
else:
max_lines = y_offset + max_height
try:
# ALWAYS show flow details first
stdscr.addstr(y_offset, x_offset, f"FLOW DETAILS: {flow.src_ip} -> {flow.dst_ip}", curses.A_BOLD)
y_offset += 2
stdscr.addstr(y_offset, x_offset, f"Packets: {flow.frame_count} | Bytes: {flow.total_bytes:,}")
y_offset += 1
# Frame types table
if flow.frame_types and y_offset < max_lines:
y_offset += 1
stdscr.addstr(y_offset, x_offset, "Frame Types:", curses.A_BOLD)
y_offset += 1
# Table header
header = f"{'Type':<12} {'#Pkts':<6} {'Bytes':<8} {'Avg ΔT':<8} {'2σ Out':<6}"
stdscr.addstr(y_offset, x_offset, header, curses.A_UNDERLINE)
y_offset += 1
sorted_frame_types = sorted(flow.frame_types.items(), key=lambda x: x[1].count, reverse=True)
for frame_type, ft_stats in sorted_frame_types:
if y_offset >= max_lines:
break
avg_str = f"{ft_stats.avg_inter_arrival:.3f}s" if ft_stats.avg_inter_arrival > 0 else "N/A"
bytes_str = f"{ft_stats.total_bytes:,}" if ft_stats.total_bytes < 10000 else f"{ft_stats.total_bytes/1000:.1f}K"
outliers_count = len(ft_stats.outlier_details) if ft_stats.outlier_details else 0
# Truncate frame type name if too long
type_name = frame_type[:11] if len(frame_type) > 11 else frame_type
ft_line = f"{type_name:<12} {ft_stats.count:<6} {bytes_str:<8} {avg_str:<8} {outliers_count:<6}"
stdscr.addstr(y_offset, x_offset, ft_line)
y_offset += 1
# Timing statistics
if y_offset < max_lines:
y_offset += 1
stdscr.addstr(y_offset, x_offset, "Timing:", curses.A_BOLD)
y_offset += 1
if flow.avg_inter_arrival > 0:
stdscr.addstr(y_offset, x_offset + 2, f"Avg: {flow.avg_inter_arrival:.6f}s")
y_offset += 1
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset + 2, f"Std: {flow.std_inter_arrival:.6f}s")
y_offset += 1
else:
stdscr.addstr(y_offset, x_offset + 2, "No timing data")
y_offset += 1
# Display outlier frame details for each frame type
if flow.frame_types and y_offset < max_lines:
outlier_frame_types = [(frame_type, ft_stats) for frame_type, ft_stats in flow.frame_types.items()
if ft_stats.outlier_details]
if outlier_frame_types:
y_offset += 1
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset, "Outlier Frames:", curses.A_BOLD)
y_offset += 1
for frame_type, ft_stats in outlier_frame_types:
if y_offset >= max_lines:
break
# Display frame type header
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset + 2, f"{frame_type}:", curses.A_UNDERLINE)
y_offset += 1
# Display outlier details as individual table rows in format "frame# | deltaT"
for frame_num, frame_inter_arrival_time in ft_stats.outlier_details:
if y_offset >= max_lines:
break
outlier_line = f"{frame_num} | {frame_inter_arrival_time:.3f}s"
stdscr.addstr(y_offset, x_offset + 4, outlier_line)
y_offset += 1
# If a frame type is selected, show additional frame type specific details
if selected_frame_type and selected_frame_type in flow.frame_types and y_offset < max_lines:
ft_stats = flow.frame_types[selected_frame_type]
# Add separator
y_offset += 2
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset, "" * min(width-2, 40))
y_offset += 1
# Frame type specific header
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset, f"FRAME TYPE: {selected_frame_type}", curses.A_BOLD)
y_offset += 2
# Frame type specific info
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset, f"Count: {ft_stats.count}")
y_offset += 1
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset, f"Bytes: {ft_stats.total_bytes:,}")
y_offset += 1
# Frame type timing
if y_offset < max_lines:
y_offset += 1
stdscr.addstr(y_offset, x_offset, "Timing:", curses.A_BOLD)
y_offset += 1
if ft_stats.avg_inter_arrival > 0:
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset + 2, f"Avg: {ft_stats.avg_inter_arrival:.6f}s")
y_offset += 1
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset + 2, f"Std: {ft_stats.std_inter_arrival:.6f}s")
y_offset += 1
else:
if y_offset < max_lines:
stdscr.addstr(y_offset, x_offset + 2, "No timing data")
y_offset += 1
except curses.error:
# Ignore curses errors from writing outside screen bounds
pass
def _get_selected_flow_and_frame_type(self, flows_list: List[FlowStats],
selected_flow: int) -> Tuple[Optional[FlowStats], Optional[str]]:
"""Get the currently selected flow and frame type based on selection index"""
current_item = 0
for flow in flows_list:
if current_item == selected_flow:
return flow, None # Selected the main flow
current_item += 1
# Check frame types for this flow
if flow.frame_types:
sorted_frame_types = sorted(flow.frame_types.items(), key=lambda x: x[1].count, reverse=True)
for frame_type, ft_stats in sorted_frame_types:
if current_item == selected_flow:
return flow, frame_type # Selected a frame type
current_item += 1
# Fallback to first flow if selection is out of bounds
return flows_list[0] if flows_list else None, None