progress?
This commit is contained in:
@@ -15,12 +15,15 @@ from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
import time
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from .widgets.sparkline import SparklineWidget
|
||||
from .widgets.metric_card import MetricCard
|
||||
from .widgets.flow_table_v2 import EnhancedFlowTable
|
||||
from .widgets.split_flow_details import FlowMainDetailsPanel, SubFlowDetailsPanel
|
||||
from .widgets.debug_panel import DebugPanel
|
||||
from ...analysis.background_analyzer import BackgroundAnalyzer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...analysis.core import EthernetAnalyzer
|
||||
@@ -50,6 +53,7 @@ class StreamLensAppV2(App):
|
||||
("4", "sort('quality')", "Sort Quality"),
|
||||
("p", "toggle_pause", "Pause"),
|
||||
("d", "show_details", "Details"),
|
||||
("v", "toggle_view_mode", "Toggle View"),
|
||||
("?", "toggle_help", "Help"),
|
||||
]
|
||||
|
||||
@@ -73,6 +77,17 @@ class StreamLensAppV2(App):
|
||||
self.sub_title = "Network Flow Analysis"
|
||||
self.paused = False
|
||||
|
||||
# Background parsing support
|
||||
self.background_analyzer = BackgroundAnalyzer(
|
||||
analyzer=analyzer,
|
||||
num_threads=4,
|
||||
batch_size=1000,
|
||||
progress_callback=None,
|
||||
flow_update_callback=self._on_flow_update
|
||||
)
|
||||
self.pcap_file = None
|
||||
|
||||
|
||||
# Metrics history for sparklines
|
||||
self.packets_history = []
|
||||
self.bytes_history = []
|
||||
@@ -127,11 +142,20 @@ class StreamLensAppV2(App):
|
||||
except:
|
||||
pass # Debug panel not visible
|
||||
|
||||
# Set initial subtitle with view mode
|
||||
try:
|
||||
flow_table = self.query_one("#flow-table", EnhancedFlowTable)
|
||||
view_mode = flow_table.get_current_view_mode()
|
||||
status = "PAUSED" if self.paused else "LIVE"
|
||||
self.sub_title = f"Network Flow Analysis - {status} - {view_mode} VIEW"
|
||||
except:
|
||||
pass
|
||||
|
||||
self.update_metrics()
|
||||
|
||||
# Set up update intervals like TipTop
|
||||
self.metric_timer = self.set_interval(0.5, self.update_metrics) # 2Hz for smooth graphs
|
||||
self.flow_timer = self.set_interval(1.0, self.update_flows) # 1Hz for flow data
|
||||
# Set up update intervals like TipTop (reduced frequency since we have real-time updates)
|
||||
self.metric_timer = self.set_interval(2.0, self.update_metrics) # 0.5Hz for background updates
|
||||
self.flow_timer = self.set_interval(5.0, self.update_flows) # 0.2Hz for fallback flow updates
|
||||
|
||||
# Initialize sparkline history
|
||||
self._initialize_history()
|
||||
@@ -177,13 +201,23 @@ class StreamLensAppV2(App):
|
||||
self.packets_per_sec = self.total_packets / elapsed
|
||||
self.bytes_per_sec = summary.get('total_bytes', 0) / elapsed
|
||||
|
||||
# Count enhanced and outliers
|
||||
# Count enhanced and outliers (thread-safe access)
|
||||
enhanced = 0
|
||||
outliers = 0
|
||||
for flow in self.analyzer.flows.values():
|
||||
if flow.enhanced_analysis.decoder_type != "Standard":
|
||||
enhanced += 1
|
||||
outliers += len(flow.outlier_frames)
|
||||
try:
|
||||
# Use background analyzer's thread-safe flow access
|
||||
flows = self.background_analyzer.get_current_flows()
|
||||
for flow in flows.values():
|
||||
if flow.enhanced_analysis.decoder_type != "Standard":
|
||||
enhanced += 1
|
||||
outliers += len(flow.outlier_frames)
|
||||
except Exception:
|
||||
# Fallback to direct access if background analyzer not available
|
||||
for flow in self.analyzer.flows.values():
|
||||
if flow.enhanced_analysis.decoder_type != "Standard":
|
||||
enhanced += 1
|
||||
outliers += len(flow.outlier_frames)
|
||||
|
||||
self.enhanced_flows = enhanced
|
||||
self.outlier_count = outliers
|
||||
|
||||
@@ -256,6 +290,53 @@ class StreamLensAppV2(App):
|
||||
flow_table = self.query_one("#flow-table", EnhancedFlowTable)
|
||||
flow_table.refresh_data()
|
||||
|
||||
|
||||
def _on_flow_update(self):
|
||||
"""Handle flow data updates from background parser"""
|
||||
try:
|
||||
# Use call_from_thread to safely update UI from background thread
|
||||
self.call_from_thread(self._update_flow_ui)
|
||||
except Exception:
|
||||
# Ignore errors during shutdown
|
||||
pass
|
||||
|
||||
def _update_flow_ui(self):
|
||||
"""Update flow UI (called from main thread)"""
|
||||
try:
|
||||
# Update flow table
|
||||
flow_table = self.query_one("#flow-table", EnhancedFlowTable)
|
||||
flow_table.refresh_data()
|
||||
|
||||
# Also update metrics in real-time
|
||||
self.update_metrics()
|
||||
except Exception:
|
||||
# Flow table widget may not be available yet
|
||||
pass
|
||||
|
||||
def start_background_parsing(self, pcap_file: str):
|
||||
"""Start parsing PCAP file in background"""
|
||||
self.pcap_file = pcap_file
|
||||
|
||||
# Start background parsing
|
||||
self.background_analyzer.start_parsing(pcap_file)
|
||||
|
||||
def stop_background_parsing(self):
|
||||
"""Stop background parsing"""
|
||||
self.background_analyzer.stop_parsing()
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup resources when app shuts down"""
|
||||
try:
|
||||
self.background_analyzer.cleanup()
|
||||
# Cancel any pending timers
|
||||
if self.metric_timer:
|
||||
self.metric_timer.stop()
|
||||
if self.flow_timer:
|
||||
self.flow_timer.stop()
|
||||
except Exception as e:
|
||||
# Don't let cleanup errors prevent shutdown
|
||||
pass
|
||||
|
||||
def on_enhanced_flow_table_flow_selected(self, event: EnhancedFlowTable.FlowSelected) -> None:
|
||||
"""Handle flow selection events"""
|
||||
try:
|
||||
@@ -290,7 +371,14 @@ class StreamLensAppV2(App):
|
||||
"""Toggle pause state"""
|
||||
self.paused = not self.paused
|
||||
status = "PAUSED" if self.paused else "LIVE"
|
||||
self.sub_title = f"Network Flow Analysis - {status}"
|
||||
|
||||
# Get current view mode to maintain it in subtitle
|
||||
try:
|
||||
flow_table = self.query_one("#flow-table", EnhancedFlowTable)
|
||||
view_mode = flow_table.get_current_view_mode()
|
||||
self.sub_title = f"Network Flow Analysis - {status} - {view_mode} VIEW"
|
||||
except:
|
||||
self.sub_title = f"Network Flow Analysis - {status}"
|
||||
|
||||
def action_sort(self, key: str) -> None:
|
||||
"""Sort flow table by specified key"""
|
||||
@@ -302,10 +390,29 @@ class StreamLensAppV2(App):
|
||||
# TODO: Implement detailed flow modal
|
||||
pass
|
||||
|
||||
def action_toggle_view_mode(self) -> None:
|
||||
"""Toggle between simplified and detailed view modes"""
|
||||
flow_table = self.query_one("#flow-table", EnhancedFlowTable)
|
||||
flow_table.toggle_view_mode()
|
||||
|
||||
# Update subtitle to show current view mode
|
||||
view_mode = flow_table.get_current_view_mode()
|
||||
status = "PAUSED" if self.paused else "LIVE"
|
||||
self.sub_title = f"Network Flow Analysis - {status} - {view_mode} VIEW"
|
||||
|
||||
def on_mouse_down(self, event: MouseDown) -> None:
|
||||
"""Prevent default mouse down behavior to disable mouse interaction."""
|
||||
event.prevent_default()
|
||||
|
||||
def on_mouse_move(self, event: MouseMove) -> None:
|
||||
"""Prevent default mouse move behavior to disable mouse interaction."""
|
||||
event.prevent_default()
|
||||
event.prevent_default()
|
||||
|
||||
def action_quit(self) -> None:
|
||||
"""Quit the application with proper cleanup"""
|
||||
self.cleanup()
|
||||
self.exit()
|
||||
|
||||
def on_unmount(self) -> None:
|
||||
"""Called when app is being unmounted - ensure cleanup"""
|
||||
self.cleanup()
|
||||
Reference in New Issue
Block a user