tabbed frametype filtering

This commit is contained in:
2025-07-30 23:48:32 -04:00
parent 8d883f25c3
commit bb3eeb79d0
92 changed files with 33696 additions and 139 deletions

View File

@@ -43,7 +43,7 @@ class EnhancedFlowTable(Vertical):
selected_flow_index = reactive(0)
sort_key = reactive("flows")
simplified_view = reactive(False) # Toggle between detailed and simplified view
simplified_view = reactive(True) # Default to simplified view without subflows
def __init__(self, analyzer: 'EthernetAnalyzer', **kwargs):
super().__init__(**kwargs)
@@ -96,11 +96,12 @@ class EnhancedFlowTable(Vertical):
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("Pkts", width=6, key="packets")
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")
table.add_column("Rate", width=6, key="rate")
def refresh_data(self):
"""Refresh flow table with current view mode"""
@@ -228,45 +229,30 @@ class EnhancedFlowTable(Vertical):
frame_summary = self._get_frame_summary(flow)
frame_text = Text(frame_summary, style="blue")
# Rate with sparkline
# Packet count (separate from rate)
packets_text = Text(str(flow.frame_count), justify="right")
# Rate sparkline (separate column)
rate_spark = self._create_rate_sparkline(metrics['rate_history'])
rate_text = Text(f"{metrics['rate_history'][-1]:.0f} {rate_spark}")
rate_text = Text(rate_spark, justify="center")
# Size with actual value
size_value = self._format_bytes(flow.total_bytes)
size_text = Text(f"{size_value:>8}")
# Delta T (average time between packets in ms)
if flow.avg_inter_arrival > 0:
delta_t_ms = flow.avg_inter_arrival * 1000
if delta_t_ms >= 1000:
delta_t_str = f"{delta_t_ms/1000:.1f}s"
else:
delta_t_str = f"{delta_t_ms:.1f}"
else:
delta_t_str = "N/A"
delta_t_text = Text(delta_t_str, justify="right")
# Delta T and Sigma - empty for main flows (subflows show the detail)
delta_t_text = Text("", justify="right")
sigma_text = Text("", justify="right")
# Sigma (standard deviation in ms)
if flow.std_inter_arrival > 0:
sigma_ms = flow.std_inter_arrival * 1000
if sigma_ms >= 1000:
sigma_str = f"{sigma_ms/1000:.1f}s"
else:
sigma_str = f"{sigma_ms:.1f}"
else:
sigma_str = "N/A"
sigma_text = Text(sigma_str, justify="right")
# Outlier count (packets outside tolerance)
outlier_count = len(flow.outlier_frames)
outlier_text = Text(str(outlier_count), justify="right",
style="red" if outlier_count > 0 else "green")
# Outlier count - sum of frame-type-specific outliers (not flow-level)
frame_type_outlier_count = sum(len(ft_stats.outlier_frames) for ft_stats in flow.frame_types.values())
outlier_text = Text(str(frame_type_outlier_count), justify="right",
style="red" if frame_type_outlier_count > 0 else "green")
return [
num_text, source_text, proto_text, dest_text,
extended_text, frame_text, rate_text, size_text,
delta_t_text, sigma_text, outlier_text
extended_text, frame_text, packets_text, size_text,
delta_t_text, sigma_text, outlier_text, rate_text
]
def _create_simplified_row(self, num: int, flow: 'FlowStats') -> List[Text]:
@@ -389,20 +375,24 @@ class EnhancedFlowTable(Vertical):
if flow.enhanced_analysis.decoder_type != "Standard":
return int(flow.enhanced_analysis.avg_frame_quality)
else:
# Base quality on outlier percentage
outlier_pct = len(flow.outlier_frames) / flow.frame_count * 100 if flow.frame_count > 0 else 0
# Base quality on frame-type-specific outlier percentage
frame_type_outlier_count = sum(len(ft_stats.outlier_frames) for ft_stats in flow.frame_types.values())
outlier_pct = frame_type_outlier_count / flow.frame_count * 100 if flow.frame_count > 0 else 0
return max(0, int(100 - outlier_pct * 10))
def _get_flow_status(self, flow: 'FlowStats') -> str:
"""Determine flow status"""
if flow.enhanced_analysis.decoder_type != "Standard":
return "Enhanced"
elif len(flow.outlier_frames) > flow.frame_count * 0.1:
return "Alert"
elif len(flow.outlier_frames) > 0:
return "Warning"
else:
return "Normal"
# Use frame-type-specific outliers for status
frame_type_outlier_count = sum(len(ft_stats.outlier_frames) for ft_stats in flow.frame_types.values())
if frame_type_outlier_count > flow.frame_count * 0.1:
return "Alert"
elif frame_type_outlier_count > 0:
return "Warning"
else:
return "Normal"
def _get_flow_style(self, flow: 'FlowStats') -> Optional[str]:
"""Get styling for flow row"""
@@ -465,12 +455,18 @@ class EnhancedFlowTable(Vertical):
return combinations
def _create_protocol_subrows(self, flow: 'FlowStats') -> List[List[Text]]:
"""Create sub-rows for enhanced protocol/frame type breakdown only"""
"""Create sub-rows for protocol/frame type breakdown - matches details panel logic"""
subrows = []
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 enhanced subrows
# For enhanced flows, show ALL frame types (same logic as details panel)
if flow.enhanced_analysis.decoder_type != "Standard":
combinations = self._get_protocol_frame_combinations(flow)
else:
# For standard flows, only show enhanced frame types
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:
# Calculate timing for this frame type if available
frame_delta_t = ""
frame_sigma = ""
@@ -478,12 +474,30 @@ class EnhancedFlowTable(Vertical):
if frame_type in flow.frame_types:
ft_stats = flow.frame_types[frame_type]
# Always calculate timing if we have data, even if very small values
if ft_stats.avg_inter_arrival > 0:
dt_ms = ft_stats.avg_inter_arrival * 1000
frame_delta_t = f"{dt_ms:.1f}" if dt_ms < 1000 else f"{dt_ms/1000:.1f}s"
elif len(ft_stats.inter_arrival_times) >= 2:
# If avg is 0 but we have data, recalculate on the fly
import statistics
avg_arrival = statistics.mean(ft_stats.inter_arrival_times)
if avg_arrival > 0:
dt_ms = avg_arrival * 1000
frame_delta_t = f"{dt_ms:.1f}" if dt_ms < 1000 else f"{dt_ms/1000:.1f}s"
if ft_stats.std_inter_arrival > 0:
sig_ms = ft_stats.std_inter_arrival * 1000
frame_sigma = f"{sig_ms:.1f}" if sig_ms < 1000 else f"{sig_ms/1000:.1f}s"
elif len(ft_stats.inter_arrival_times) >= 2:
# If std is 0 but we have data, recalculate on the fly
import statistics
std_arrival = statistics.stdev(ft_stats.inter_arrival_times)
if std_arrival > 0:
sig_ms = std_arrival * 1000
frame_sigma = f"{sig_ms:.1f}" if sig_ms < 1000 else f"{sig_ms/1000:.1f}s"
frame_outliers = str(len(ft_stats.outlier_frames))
subrow = [
@@ -497,7 +511,8 @@ class EnhancedFlowTable(Vertical):
Text(f"{self._format_bytes(count * (flow.total_bytes // flow.frame_count) if flow.frame_count > 0 else 0):>8}", style="dim"),
Text(frame_delta_t, style="dim", justify="right"),
Text(frame_sigma, style="dim", justify="right"),
Text(frame_outliers, style="dim red" if frame_outliers and int(frame_outliers) > 0 else "dim", justify="right")
Text(frame_outliers, style="dim red" if frame_outliers and int(frame_outliers) > 0 else "dim", justify="right"),
Text("", style="dim") # Empty rate column for subrows
]
subrows.append(subrow)