progress?
This commit is contained in:
@@ -6,7 +6,7 @@ from textual.widgets import DataTable
|
||||
from textual.containers import Vertical
|
||||
from textual.reactive import reactive
|
||||
from textual.message import Message
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
from typing import TYPE_CHECKING, List, Optional, Dict, Tuple
|
||||
from rich.text import Text
|
||||
from rich.box import ROUNDED
|
||||
|
||||
@@ -43,6 +43,7 @@ class EnhancedFlowTable(Vertical):
|
||||
|
||||
selected_flow_index = reactive(0)
|
||||
sort_key = reactive("flows")
|
||||
simplified_view = reactive(False) # Toggle between detailed and simplified view
|
||||
|
||||
def __init__(self, analyzer: 'EthernetAnalyzer', **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -50,6 +51,7 @@ class EnhancedFlowTable(Vertical):
|
||||
self.flows_list = []
|
||||
self.row_to_flow_map = {} # Map row keys to flow indices
|
||||
self.flow_metrics = {} # Store per-flow metrics history
|
||||
self.view_mode_changed = False # Track when view mode changes
|
||||
|
||||
def compose(self):
|
||||
"""Create the enhanced flow table"""
|
||||
@@ -64,25 +66,49 @@ class EnhancedFlowTable(Vertical):
|
||||
|
||||
def on_mount(self):
|
||||
"""Initialize the table"""
|
||||
table = self.query_one("#flows-data-table", DataTable)
|
||||
|
||||
# Compact columns optimized for data density
|
||||
table.add_column("#", width=2, key="num")
|
||||
table.add_column("Source", width=18, key="source")
|
||||
table.add_column("Proto", width=4, key="proto")
|
||||
table.add_column("Destination", width=18, key="dest")
|
||||
table.add_column("Extended", width=8, key="extended")
|
||||
table.add_column("Frame Type", width=10, key="frame_type")
|
||||
table.add_column("Pkts", width=6, key="rate")
|
||||
table.add_column("Size", width=8, key="volume")
|
||||
table.add_column("ΔT(ms)", width=8, key="delta_t")
|
||||
table.add_column("σ(ms)", width=8, key="sigma")
|
||||
table.add_column("Out", width=5, key="outliers")
|
||||
|
||||
self._setup_table_columns()
|
||||
self.refresh_data()
|
||||
|
||||
def _setup_table_columns(self):
|
||||
"""Setup table columns based on current view mode"""
|
||||
table = self.query_one("#flows-data-table", DataTable)
|
||||
|
||||
# Clear existing columns if any
|
||||
if table.columns:
|
||||
table.clear(columns=True)
|
||||
|
||||
if self.simplified_view:
|
||||
# Simplified view - only main flows with summary data
|
||||
table.add_column("#", width=3, key="num")
|
||||
table.add_column("Source", width=18, key="source")
|
||||
table.add_column("Destination", width=18, key="dest")
|
||||
table.add_column("Protocol", width=8, key="protocol")
|
||||
table.add_column("Packets", width=8, key="packets")
|
||||
table.add_column("Volume", width=10, key="volume")
|
||||
table.add_column("Avg ΔT", width=8, key="avg_delta")
|
||||
table.add_column("Quality", width=8, key="quality")
|
||||
table.add_column("Status", width=10, key="status")
|
||||
else:
|
||||
# Detailed view - original layout with subflows
|
||||
table.add_column("#", width=2, key="num")
|
||||
table.add_column("Source", width=18, key="source")
|
||||
table.add_column("Proto", width=4, key="proto")
|
||||
table.add_column("Destination", width=18, key="dest")
|
||||
table.add_column("Extended", width=8, key="extended")
|
||||
table.add_column("Frame Type", width=10, key="frame_type")
|
||||
table.add_column("Pkts", width=6, key="rate")
|
||||
table.add_column("Size", width=8, key="volume")
|
||||
table.add_column("ΔT(ms)", width=8, key="delta_t")
|
||||
table.add_column("σ(ms)", width=8, key="sigma")
|
||||
table.add_column("Out", width=5, key="outliers")
|
||||
|
||||
def refresh_data(self):
|
||||
"""Refresh flow table with enhanced visualizations"""
|
||||
"""Refresh flow table with current view mode"""
|
||||
# Check if view mode changed and rebuild table structure if needed
|
||||
if self.view_mode_changed:
|
||||
self._setup_table_columns()
|
||||
self.view_mode_changed = False
|
||||
|
||||
table = self.query_one("#flows-data-table", DataTable)
|
||||
|
||||
# Preserve cursor and scroll positions
|
||||
@@ -103,7 +129,39 @@ class EnhancedFlowTable(Vertical):
|
||||
# Get and sort flows
|
||||
self.flows_list = self._get_sorted_flows()
|
||||
|
||||
# Add flows with enhanced display
|
||||
if self.simplified_view:
|
||||
self._populate_simplified_view()
|
||||
else:
|
||||
self._populate_detailed_view()
|
||||
|
||||
# Restore cursor position
|
||||
if selected_row_key and selected_row_key in table.rows:
|
||||
row_index = list(table.rows.keys()).index(selected_row_key)
|
||||
table.move_cursor(row=row_index, column=cursor_column, animate=False)
|
||||
elif table.row_count > 0:
|
||||
# If original selection not found, try to maintain row position
|
||||
new_row = min(cursor_row, table.row_count - 1)
|
||||
table.move_cursor(row=new_row, column=cursor_column, animate=False)
|
||||
|
||||
# Restore scroll position
|
||||
table.scroll_to(x=scroll_x, y=scroll_y, animate=False)
|
||||
|
||||
def _populate_simplified_view(self):
|
||||
"""Populate table with simplified flow summary data"""
|
||||
table = self.query_one("#flows-data-table", DataTable)
|
||||
|
||||
for i, flow in enumerate(self.flows_list):
|
||||
# Create simplified row data - no subflows shown
|
||||
row_data = self._create_simplified_row(i + 1, flow)
|
||||
row_key = table.add_row(*row_data, key=f"flow_{i}")
|
||||
|
||||
# Map row key to flow index
|
||||
self.row_to_flow_map[row_key] = i
|
||||
|
||||
def _populate_detailed_view(self):
|
||||
"""Populate table with detailed flow data including subflows"""
|
||||
table = self.query_one("#flows-data-table", DataTable)
|
||||
|
||||
for i, flow in enumerate(self.flows_list):
|
||||
# Track metrics for this flow
|
||||
flow_key = f"{flow.src_ip}:{flow.src_port}-{flow.dst_ip}:{flow.dst_port}"
|
||||
@@ -127,20 +185,14 @@ class EnhancedFlowTable(Vertical):
|
||||
metrics['last_packet_count'] = flow.frame_count
|
||||
metrics['last_update'] = flow.last_seen
|
||||
|
||||
# Create row with visualizations
|
||||
# Create row with detailed visualizations
|
||||
row_data = self._create_enhanced_row(i + 1, flow, metrics)
|
||||
row_key = table.add_row(*row_data, key=f"flow_{i}")
|
||||
|
||||
# Map row key to flow index
|
||||
self.row_to_flow_map[row_key] = i
|
||||
|
||||
# Apply row styling based on status
|
||||
style = self._get_flow_style(flow)
|
||||
if style:
|
||||
# Note: DataTable doesn't have set_row_style, using CSS classes instead
|
||||
pass
|
||||
|
||||
# Add sub-rows for protocol breakdown
|
||||
# Add sub-rows for protocol breakdown (only in detailed view)
|
||||
if self._should_show_subrows(flow):
|
||||
sub_rows = self._create_protocol_subrows(flow)
|
||||
combinations = self._get_protocol_frame_combinations(flow)
|
||||
@@ -151,18 +203,6 @@ class EnhancedFlowTable(Vertical):
|
||||
if j < len(combinations):
|
||||
_, frame_type, _, _ = combinations[j]
|
||||
self.row_to_subflow_map[sub_key] = (i, frame_type)
|
||||
|
||||
# Restore cursor position
|
||||
if selected_row_key and selected_row_key in table.rows:
|
||||
row_index = list(table.rows.keys()).index(selected_row_key)
|
||||
table.move_cursor(row=row_index, column=cursor_column, animate=False)
|
||||
elif table.row_count > 0:
|
||||
# If original selection not found, try to maintain row position
|
||||
new_row = min(cursor_row, table.row_count - 1)
|
||||
table.move_cursor(row=new_row, column=cursor_column, animate=False)
|
||||
|
||||
# Restore scroll position
|
||||
table.scroll_to(x=scroll_x, y=scroll_y, animate=False)
|
||||
|
||||
def _create_enhanced_row(self, num: int, flow: 'FlowStats', metrics: dict) -> List[Text]:
|
||||
"""Create enhanced row with inline visualizations"""
|
||||
@@ -229,6 +269,64 @@ class EnhancedFlowTable(Vertical):
|
||||
delta_t_text, sigma_text, outlier_text
|
||||
]
|
||||
|
||||
def _create_simplified_row(self, num: int, flow: 'FlowStats') -> List[Text]:
|
||||
"""Create simplified row with summary data only"""
|
||||
# Flow number
|
||||
num_text = Text(str(num), justify="right")
|
||||
|
||||
# Source (IP only for simplified view)
|
||||
source_text = Text(flow.src_ip)
|
||||
|
||||
# Destination (IP only for simplified view)
|
||||
dest_text = Text(flow.dst_ip)
|
||||
|
||||
# Main protocol (transport + extended if available)
|
||||
extended = self._get_extended_protocol(flow)
|
||||
if extended != "-":
|
||||
protocol_str = f"{flow.transport_protocol}/{extended}"
|
||||
else:
|
||||
protocol_str = flow.transport_protocol
|
||||
protocol_text = Text(protocol_str, style="bold cyan")
|
||||
|
||||
# Total packet count
|
||||
packets_text = Text(str(flow.frame_count), justify="right")
|
||||
|
||||
# Total volume
|
||||
volume_text = Text(self._format_bytes(flow.total_bytes), justify="right")
|
||||
|
||||
# Average delta T
|
||||
if flow.avg_inter_arrival > 0:
|
||||
delta_t_ms = flow.avg_inter_arrival * 1000
|
||||
if delta_t_ms >= 1000:
|
||||
avg_delta_str = f"{delta_t_ms/1000:.1f}s"
|
||||
else:
|
||||
avg_delta_str = f"{delta_t_ms:.1f}ms"
|
||||
else:
|
||||
avg_delta_str = "N/A"
|
||||
avg_delta_text = Text(avg_delta_str, justify="right")
|
||||
|
||||
# Quality score as percentage
|
||||
quality_score = self._get_quality_score(flow)
|
||||
quality_text = Text(f"{quality_score}%", justify="right",
|
||||
style="green" if quality_score >= 90 else
|
||||
"yellow" if quality_score >= 70 else "red")
|
||||
|
||||
# Flow status
|
||||
status = self._get_flow_status(flow)
|
||||
status_color = {
|
||||
"Enhanced": "bold blue",
|
||||
"Alert": "bold red",
|
||||
"Warning": "yellow",
|
||||
"Normal": "green"
|
||||
}.get(status, "white")
|
||||
status_text = Text(status, style=status_color)
|
||||
|
||||
return [
|
||||
num_text, source_text, dest_text, protocol_text,
|
||||
packets_text, volume_text, avg_delta_text,
|
||||
quality_text, status_text
|
||||
]
|
||||
|
||||
def _create_rate_sparkline(self, history: List[float]) -> str:
|
||||
"""Create mini sparkline for rate"""
|
||||
if not history:
|
||||
@@ -319,16 +417,60 @@ class EnhancedFlowTable(Vertical):
|
||||
|
||||
def _should_show_subrows(self, flow: 'FlowStats') -> bool:
|
||||
"""Determine if flow should show protocol breakdown"""
|
||||
# Show subrows for flows with multiple frame types or enhanced analysis
|
||||
return (len(flow.frame_types) > 1 or
|
||||
flow.enhanced_analysis.decoder_type != "Standard")
|
||||
# Only show subrows if there are enhanced frame types
|
||||
enhanced_frame_types = self._get_enhanced_frame_types(flow)
|
||||
return len(enhanced_frame_types) > 0
|
||||
|
||||
def _get_enhanced_frame_types(self, flow: 'FlowStats') -> Dict[str, 'FrameTypeStats']:
|
||||
"""Get only frame types that belong to enhanced protocols"""
|
||||
enhanced_protocols = {'CHAPTER10', 'CH10', 'PTP', 'IENA'}
|
||||
enhanced_frame_types = {}
|
||||
|
||||
for frame_type, stats in flow.frame_types.items():
|
||||
# Check if this frame type belongs to an enhanced protocol
|
||||
if any(enhanced_proto in frame_type for enhanced_proto in enhanced_protocols):
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
elif frame_type.startswith(('CH10-', 'PTP-', 'IENA-')):
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
elif frame_type in ('TMATS', 'TMATS-Data'): # TMATS is part of Chapter 10
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
|
||||
return enhanced_frame_types
|
||||
|
||||
def _get_enhanced_protocol_frame_combinations(self, flow: 'FlowStats', enhanced_frame_types: Dict[str, 'FrameTypeStats']) -> List[Tuple[str, str, int, float]]:
|
||||
"""Get protocol/frame combinations for enhanced protocols only"""
|
||||
combinations = []
|
||||
total_packets = flow.frame_count
|
||||
|
||||
# Group enhanced frame types by extended protocol
|
||||
protocol_frames = {}
|
||||
|
||||
for frame_type, ft_stats in enhanced_frame_types.items():
|
||||
# Determine extended protocol for this frame type
|
||||
extended_proto = self._get_extended_protocol_for_frame(flow, frame_type)
|
||||
|
||||
if extended_proto not in protocol_frames:
|
||||
protocol_frames[extended_proto] = []
|
||||
|
||||
protocol_frames[extended_proto].append((frame_type, ft_stats.count))
|
||||
|
||||
# Convert to list of tuples with percentages
|
||||
for extended_proto, frame_list in protocol_frames.items():
|
||||
for frame_type, count in frame_list:
|
||||
percentage = (count / total_packets * 100) if total_packets > 0 else 0
|
||||
combinations.append((extended_proto, frame_type, count, percentage))
|
||||
|
||||
# Sort by count (descending)
|
||||
combinations.sort(key=lambda x: x[2], reverse=True)
|
||||
return combinations
|
||||
|
||||
def _create_protocol_subrows(self, flow: 'FlowStats') -> List[List[Text]]:
|
||||
"""Create sub-rows for protocol/frame type breakdown"""
|
||||
"""Create sub-rows for enhanced protocol/frame type breakdown only"""
|
||||
subrows = []
|
||||
combinations = self._get_protocol_frame_combinations(flow)
|
||||
enhanced_frame_types = self._get_enhanced_frame_types(flow)
|
||||
combinations = self._get_enhanced_protocol_frame_combinations(flow, enhanced_frame_types)
|
||||
|
||||
for extended_proto, frame_type, count, percentage in combinations: # Show all subrows
|
||||
for extended_proto, frame_type, count, percentage in combinations: # Show all enhanced subrows
|
||||
# Calculate timing for this frame type if available
|
||||
frame_delta_t = ""
|
||||
frame_sigma = ""
|
||||
@@ -385,6 +527,16 @@ class EnhancedFlowTable(Vertical):
|
||||
self.sort_key = key
|
||||
self.refresh_data()
|
||||
|
||||
def toggle_view_mode(self):
|
||||
"""Toggle between simplified and detailed view modes"""
|
||||
self.simplified_view = not self.simplified_view
|
||||
self.view_mode_changed = True
|
||||
self.refresh_data()
|
||||
|
||||
def get_current_view_mode(self) -> str:
|
||||
"""Get current view mode as string"""
|
||||
return "SIMPLIFIED" if self.simplified_view else "DETAILED"
|
||||
|
||||
class FlowSelected(Message):
|
||||
"""Message sent when a flow is selected"""
|
||||
def __init__(self, flow: Optional['FlowStats'], subflow_type: Optional[str] = None) -> None:
|
||||
@@ -451,6 +603,19 @@ class EnhancedFlowTable(Vertical):
|
||||
self.post_message(self.FlowSelected(selected_flow, subflow_type))
|
||||
|
||||
# Helper methods from original implementation
|
||||
def _get_extended_protocol_for_frame(self, flow: 'FlowStats', frame_type: str) -> str:
|
||||
"""Get extended protocol for a specific frame type"""
|
||||
if frame_type.startswith('CH10') or frame_type == 'TMATS':
|
||||
return 'CH10'
|
||||
elif frame_type.startswith('PTP'):
|
||||
return 'PTP'
|
||||
elif frame_type == 'IENA':
|
||||
return 'IENA'
|
||||
elif frame_type == 'NTP':
|
||||
return 'NTP'
|
||||
else:
|
||||
return self._get_extended_protocol(flow)
|
||||
|
||||
def _get_extended_protocol(self, flow: 'FlowStats') -> str:
|
||||
"""Get extended protocol"""
|
||||
if flow.detected_protocol_types:
|
||||
|
||||
121
analyzer/tui/textual/widgets/progress_bar.py
Normal file
121
analyzer/tui/textual/widgets/progress_bar.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Progress Bar Widget for PCAP parsing progress
|
||||
"""
|
||||
|
||||
from textual.widget import Widget
|
||||
from textual.reactive import reactive
|
||||
from rich.console import RenderableType
|
||||
from rich.progress import Progress, BarColumn, TextColumn, TaskProgressColumn, MofNCompleteColumn, TimeRemainingColumn
|
||||
from rich.text import Text
|
||||
import time
|
||||
|
||||
|
||||
class ParsingProgressBar(Widget):
|
||||
"""Progress bar widget for PCAP parsing with rich formatting"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
ParsingProgressBar {
|
||||
height: 3;
|
||||
margin: 1;
|
||||
padding: 1;
|
||||
background: $surface;
|
||||
border: solid $accent;
|
||||
}
|
||||
"""
|
||||
|
||||
# Reactive attributes
|
||||
progress = reactive(0.0)
|
||||
total_packets = reactive(0)
|
||||
processed_packets = reactive(0)
|
||||
packets_per_second = reactive(0.0)
|
||||
estimated_time_remaining = reactive(0.0)
|
||||
is_complete = reactive(False)
|
||||
is_visible = reactive(False)
|
||||
error_message = reactive("")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.start_time = None
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
"""Render the progress bar"""
|
||||
if not self.is_visible:
|
||||
return Text("")
|
||||
|
||||
if self.error_message:
|
||||
return Text(f"❌ Error: {self.error_message}", style="red")
|
||||
|
||||
if self.is_complete:
|
||||
elapsed = time.time() - self.start_time if self.start_time else 0
|
||||
return Text(
|
||||
f"✅ Parsing complete! {self.processed_packets:,} packets processed in {elapsed:.1f}s",
|
||||
style="green"
|
||||
)
|
||||
|
||||
# Create rich progress bar
|
||||
progress = Progress(
|
||||
TextColumn("[bold blue]Parsing PCAP..."),
|
||||
BarColumn(bar_width=40),
|
||||
TaskProgressColumn(),
|
||||
MofNCompleteColumn(),
|
||||
TextColumn("•"),
|
||||
TextColumn("{task.fields[rate]}"),
|
||||
TextColumn("•"),
|
||||
TimeRemainingColumn(),
|
||||
expand=False
|
||||
)
|
||||
|
||||
# Format rate display
|
||||
if self.packets_per_second >= 1000:
|
||||
rate_str = f"{self.packets_per_second/1000:.1f}K pkt/s"
|
||||
else:
|
||||
rate_str = f"{self.packets_per_second:.0f} pkt/s"
|
||||
|
||||
task = progress.add_task(
|
||||
"parsing",
|
||||
total=self.total_packets,
|
||||
completed=self.processed_packets,
|
||||
rate=rate_str
|
||||
)
|
||||
|
||||
return progress
|
||||
|
||||
def start_parsing(self, total_packets: int):
|
||||
"""Start showing progress for parsing"""
|
||||
self.total_packets = total_packets
|
||||
self.processed_packets = 0
|
||||
self.progress = 0.0
|
||||
self.is_complete = False
|
||||
self.is_visible = True
|
||||
self.error_message = ""
|
||||
self.start_time = time.time()
|
||||
self.refresh()
|
||||
|
||||
def update_progress(self, processed: int, total: int, pps: float, eta: float):
|
||||
"""Update progress values"""
|
||||
self.processed_packets = processed
|
||||
self.total_packets = total
|
||||
self.packets_per_second = pps
|
||||
self.estimated_time_remaining = eta
|
||||
self.progress = (processed / total * 100) if total > 0 else 0
|
||||
self.refresh()
|
||||
|
||||
def complete_parsing(self):
|
||||
"""Mark parsing as complete"""
|
||||
self.is_complete = True
|
||||
self.refresh()
|
||||
# Hide after 3 seconds
|
||||
self.set_timer(3.0, self.hide_progress)
|
||||
|
||||
def show_error(self, error: str):
|
||||
"""Show error message"""
|
||||
self.error_message = error
|
||||
self.is_visible = True
|
||||
self.refresh()
|
||||
# Hide after 5 seconds
|
||||
self.set_timer(5.0, self.hide_progress)
|
||||
|
||||
def hide_progress(self):
|
||||
"""Hide the progress bar"""
|
||||
self.is_visible = False
|
||||
self.refresh()
|
||||
@@ -10,7 +10,7 @@ from rich.text import Text
|
||||
from rich.panel import Panel
|
||||
from rich.console import RenderableType, Group
|
||||
from rich.table import Table
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, Dict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ....models import FlowStats, FrameTypeStats
|
||||
@@ -106,19 +106,18 @@ class FlowMainDetailsPanel(Vertical):
|
||||
sections.append(Text("Enhanced Analysis", style="bold green"))
|
||||
sections.append(enhanced_table)
|
||||
|
||||
# Timing analysis - only show if no sub-flows exist
|
||||
# Timing analysis - only show if no enhanced sub-flows exist
|
||||
# Match the same logic as _should_show_subrows in flow_table_v2.py
|
||||
has_subflows = (len(flow.frame_types) > 1 or
|
||||
flow.enhanced_analysis.decoder_type != "Standard")
|
||||
has_enhanced_subflows = self._has_enhanced_subflows(flow)
|
||||
|
||||
# Debug output
|
||||
try:
|
||||
debug_panel = self.app.query_one("#debug-panel")
|
||||
debug_panel.add_debug_message(f"TIMING_LOGIC: {flow.src_ip}:{flow.src_port} - types={len(flow.frame_types)}, decoder={flow.enhanced_analysis.decoder_type}, has_subflows={has_subflows}")
|
||||
debug_panel.add_debug_message(f"TIMING_LOGIC: {flow.src_ip}:{flow.src_port} - types={len(flow.frame_types)}, decoder={flow.enhanced_analysis.decoder_type}, has_enhanced_subflows={has_enhanced_subflows}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if not has_subflows:
|
||||
if not has_enhanced_subflows:
|
||||
try:
|
||||
debug_panel = self.app.query_one("#debug-panel")
|
||||
debug_panel.add_debug_message(f"BRANCH: Taking FULL timing branch for {flow.src_ip}:{flow.src_port}")
|
||||
@@ -160,6 +159,19 @@ class FlowMainDetailsPanel(Vertical):
|
||||
|
||||
return Group(*sections)
|
||||
|
||||
def _has_enhanced_subflows(self, flow: 'FlowStats') -> bool:
|
||||
"""Check if flow has enhanced frame types that warrant sub-rows"""
|
||||
enhanced_protocols = {'CHAPTER10', 'CH10', 'PTP', 'IENA'}
|
||||
|
||||
for frame_type in flow.frame_types.keys():
|
||||
# Check if this frame type belongs to an enhanced protocol
|
||||
if any(enhanced_proto in frame_type for enhanced_proto in enhanced_protocols):
|
||||
return True
|
||||
elif frame_type.startswith(('CH10-', 'PTP-', 'IENA-')):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _format_bytes(self, bytes_count: int) -> str:
|
||||
"""Format byte count with units"""
|
||||
if bytes_count >= 1_000_000_000:
|
||||
@@ -276,14 +288,23 @@ class SubFlowDetailsPanel(Vertical):
|
||||
return Group(*sections)
|
||||
|
||||
def _create_subflow_summary(self, flow: 'FlowStats') -> RenderableType:
|
||||
"""Create summary of all sub-flows"""
|
||||
if not flow.frame_types or len(flow.frame_types) <= 1:
|
||||
"""Create summary of all sub-flows for enhanced flows"""
|
||||
# For enhanced flows, show ALL frame types, not just enhanced ones
|
||||
if flow.enhanced_analysis.decoder_type != "Standard":
|
||||
frame_types_to_show = flow.frame_types
|
||||
title = "Sub-Flow Summary (All Frame Types)"
|
||||
else:
|
||||
# For standard flows, only show enhanced frame types if any
|
||||
frame_types_to_show = self._get_enhanced_frame_types(flow)
|
||||
title = "Enhanced Sub-Flow Summary"
|
||||
|
||||
if not frame_types_to_show:
|
||||
return Text("No sub-flows available", style="dim")
|
||||
|
||||
sections = []
|
||||
sections.append(Text("Sub-Flow Summary", style="bold yellow"))
|
||||
sections.append(Text(title, style="bold yellow"))
|
||||
|
||||
# Frame type breakdown table
|
||||
# Frame type breakdown table for enhanced protocols only
|
||||
frame_table = Table(show_header=True, box=None)
|
||||
frame_table.add_column("Frame Type", style="blue")
|
||||
frame_table.add_column("Count", justify="right")
|
||||
@@ -294,7 +315,7 @@ class SubFlowDetailsPanel(Vertical):
|
||||
|
||||
total = flow.frame_count
|
||||
for frame_type, stats in sorted(
|
||||
flow.frame_types.items(),
|
||||
frame_types_to_show.items(),
|
||||
key=lambda x: x[1].count,
|
||||
reverse=True
|
||||
):
|
||||
@@ -315,6 +336,22 @@ class SubFlowDetailsPanel(Vertical):
|
||||
sections.append(frame_table)
|
||||
return Group(*sections)
|
||||
|
||||
def _get_enhanced_frame_types(self, flow: 'FlowStats') -> Dict[str, 'FrameTypeStats']:
|
||||
"""Get only frame types that belong to enhanced protocols"""
|
||||
enhanced_protocols = {'CHAPTER10', 'CH10', 'PTP', 'IENA'}
|
||||
enhanced_frame_types = {}
|
||||
|
||||
for frame_type, stats in flow.frame_types.items():
|
||||
# Check if this frame type belongs to an enhanced protocol
|
||||
if any(enhanced_proto in frame_type for enhanced_proto in enhanced_protocols):
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
elif frame_type.startswith(('CH10-', 'PTP-', 'IENA-')):
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
elif frame_type in ('TMATS', 'TMATS-Data'): # TMATS is part of Chapter 10
|
||||
enhanced_frame_types[frame_type] = stats
|
||||
|
||||
return enhanced_frame_types
|
||||
|
||||
def _format_bytes(self, bytes_count: int) -> str:
|
||||
"""Format byte count with units"""
|
||||
if bytes_count >= 1_000_000_000:
|
||||
|
||||
Reference in New Issue
Block a user