Files
StreamLens/analyzer/tui/textual/app.py
noisedestroyers 36a576dc2c Enhanced Textual TUI with proper API usage and documentation
- Fixed DataTable row selection and event handling
- Added explicit column keys to prevent auto-generated keys
- Implemented row-to-flow mapping for reliable selection tracking
- Converted left metrics panel to horizontal top bar
- Fixed all missing FlowStats/EnhancedAnalysisData attributes
- Created comprehensive Textual API documentation in Documentation/textual/
- Added validation checklist to prevent future API mismatches
- Preserved cursor position during data refreshes
- Fixed RowKey type handling and event names

The TUI now properly handles flow selection, displays metrics in a compact top bar,
and correctly correlates selected rows with the details pane.
2025-07-27 18:37:55 -04:00

128 lines
4.6 KiB
Python

"""
Main StreamLens Textual Application
Modern TUI interface using Textual framework
"""
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.widgets import Header, Footer, TabbedContent, TabPane, Static, DataTable
from textual.reactive import reactive
from typing import TYPE_CHECKING
from .widgets.flow_table import FlowAnalysisWidget
from .widgets.packet_viewer import PacketDecoderWidget
from .widgets.metrics_dashboard import StatisticalAnalysisWidget
if TYPE_CHECKING:
from ...analysis.core import EthernetAnalyzer
class StreamLensApp(App):
"""
StreamLens Textual TUI Application
Modern interface with three main tabs:
- Flow Analysis: Interactive flow table with hierarchical protocol breakdown
- Packet Decoder: 3-panel packet inspection interface
- Statistical Analysis: Real-time metrics and performance analysis
"""
CSS_PATH = "styles/streamlens.tcss"
BINDINGS = [
("1", "show_tab('flows')", "Flow Analysis"),
("2", "show_tab('decoder')", "Packet Decoder"),
("3", "show_tab('stats')", "Statistics"),
("q", "quit", "Quit"),
("ctrl+c", "quit", "Quit"),
("?", "toggle_help", "Help"),
]
# Reactive attributes for live data updates
total_flows = reactive(0)
total_packets = reactive(0)
live_status = reactive("Stopped")
def __init__(self, analyzer: 'EthernetAnalyzer'):
super().__init__()
self.analyzer = analyzer
self.title = "StreamLens - Ethernet Traffic Analyzer"
self.sub_title = "Modern Network Flow Analysis"
def compose(self) -> ComposeResult:
"""Create the application layout"""
yield Header()
with Container(id="main-container"):
# Status summary bar
yield Static(
f"Flows: {self.total_flows} | Packets: {self.total_packets} | Status: {self.live_status}",
id="status-summary"
)
# Main tabbed interface
with TabbedContent(initial="flows"):
# Flow Analysis Tab
with TabPane("Flow Analysis", id="flows"):
yield FlowAnalysisWidget(self.analyzer, id="flow-analysis")
# Packet Decoder Tab
with TabPane("Packet Decoder", id="decoder"):
yield PacketDecoderWidget(self.analyzer, id="packet-decoder")
# Statistical Analysis Tab
with TabPane("Statistics", id="stats"):
yield StatisticalAnalysisWidget(self.analyzer, id="statistics")
yield Footer()
def on_mount(self) -> None:
"""Initialize the application"""
self.update_status()
# Set up periodic updates for live analysis
if self.analyzer.is_live:
self.set_interval(1.0, self.update_status)
def update_status(self) -> None:
"""Update reactive status attributes"""
summary = self.analyzer.get_summary()
self.total_flows = summary.get('unique_flows', 0)
self.total_packets = summary.get('total_packets', 0)
self.live_status = "Live" if self.analyzer.is_live else "Offline"
# Update status summary display
status_widget = self.query_one("#status-summary", Static)
status_widget.update(
f"Flows: {self.total_flows} | Packets: {self.total_packets} | Status: {self.live_status}"
)
def action_show_tab(self, tab_id: str) -> None:
"""Switch to specified tab"""
tabs = self.query_one(TabbedContent)
tabs.active = tab_id
def action_toggle_help(self) -> None:
"""Toggle help information"""
# TODO: Implement help modal
pass
def watch_total_flows(self, new_value: int) -> None:
"""React to flow count changes"""
# Trigger updates in child widgets
flow_widget = self.query_one("#flow-analysis", FlowAnalysisWidget)
flow_widget.refresh_data()
def watch_total_packets(self, new_value: int) -> None:
"""React to packet count changes"""
# Trigger updates in decoder widget
decoder_widget = self.query_one("#packet-decoder", PacketDecoderWidget)
decoder_widget.refresh_data()
def get_selected_flow(self):
"""Get currently selected flow from flow analysis widget"""
flow_widget = self.query_one("#flow-analysis", FlowAnalysisWidget)
return flow_widget.get_selected_flow()
def action_quit(self) -> None:
"""Clean exit"""
self.exit()