//! Dashboard Framework for TLI Terminal Interface //! //! This module provides a comprehensive dashboard system for the Foxhunt HFT trading system. //! It implements a multi-dashboard architecture with real-time data streaming and interactive //! controls using Ratatui for terminal-based visualization. //! //! ## Architecture //! - **`DashboardManager`**: Central coordinator for all dashboards //! - **Dashboard Trait**: Common interface for all dashboard implementations //! - **Real-time Updates**: Event-driven data streaming from gRPC services //! - **Navigation**: Keyboard shortcuts for dashboard switching //! - **Layout Management**: Consistent UI layout across all dashboards use anyhow::Result; use crossterm::event::KeyEvent; use ratatui::prelude::*; use std::collections::HashMap; use tokio::sync::mpsc; // Import from events module use crate::dashboard::events::DashboardEvent; use crate::dashboard::layout::LayoutManager; pub mod backtesting; pub mod config; pub mod events; pub mod layout; pub mod ml; pub mod performance; pub mod risk; pub mod trading; pub mod vault_status; // Import dashboard implementations after module declarations use backtesting::BacktestingDashboard; use config::create_config_dashboard; use ml::MLDashboard; use performance::PerformanceDashboard; use risk::RiskDashboard; use trading::TradingDashboard; use vault_status::VaultStatusWidget; // NO RE-EXPORTS: Import directly from submodules // Use tli::dashboard::events::DashboardEvent instead // Use tli::dashboard::layout::LayoutManager instead /// Main dashboard manager that coordinates all dashboards pub struct DashboardManager { pub active_dashboard: DashboardType, pub dashboards: HashMap>, pub layout_manager: LayoutManager, pub event_receiver: mpsc::Receiver, pub _event_sender: mpsc::Sender, // Vault service removed - TLI is pure client, uses shared config crate } /// Available dashboard types #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DashboardType { Trading, // Live positions, orders, executions, market data Risk, // VaR, drawdown, position limits, safety controls ML, // Model predictions, signal strength, confidence Performance, // PnL, Sharpe ratios, strategy performance Config, // System configuration management Backtesting, // Strategy testing, historical analysis, results Vault, // Vault status, credentials, service discovery } impl DashboardType { pub fn all() -> Vec { vec![ DashboardType::Trading, DashboardType::Risk, DashboardType::ML, DashboardType::Performance, DashboardType::Config, DashboardType::Backtesting, DashboardType::Vault, ] } pub const fn shortcut_key(&self) -> char { match self { DashboardType::Trading => 't', DashboardType::Risk => 'r', DashboardType::ML => 'm', DashboardType::Performance => 'p', DashboardType::Config => 'c', DashboardType::Backtesting => 'b', DashboardType::Vault => 'v', } } pub const fn title(&self) -> &'static str { match self { DashboardType::Trading => "Trading", DashboardType::Risk => "Risk", DashboardType::ML => "ML", DashboardType::Performance => "Performance", DashboardType::Config => "Configuration", DashboardType::Backtesting => "Backtesting", DashboardType::Vault => "Vault Status", } } } /// Common interface for all dashboard implementations pub trait Dashboard: Send + Sync { /// Render the dashboard to the given frame area fn render(&mut self, frame: &mut Frame, area: Rect) -> Result<()>; /// Handle keyboard input and return optional dashboard events fn handle_input(&mut self, key: KeyEvent) -> Result>; /// Update dashboard with new data/events fn update(&mut self, event: DashboardEvent) -> Result<()>; /// Get dashboard title for display fn title(&self) -> &str; /// Get keyboard shortcut for this dashboard fn shortcut_key(&self) -> char; /// Check if dashboard needs redraw fn needs_redraw(&self) -> bool; /// Mark dashboard as drawn fn mark_drawn(&mut self); } impl DashboardManager { pub fn new() -> (Self, mpsc::Sender) { let (_event_sender, event_receiver) = mpsc::channel(1000); let mut dashboards: HashMap> = HashMap::new(); // Initialize all dashboards dashboards.insert( DashboardType::Trading, Box::new(TradingDashboard::new(_event_sender.clone())), ); dashboards.insert( DashboardType::Risk, Box::new(RiskDashboard::new(_event_sender.clone())), ); dashboards.insert( DashboardType::ML, Box::new(MLDashboard::new(_event_sender.clone())), ); dashboards.insert( DashboardType::Performance, Box::new(PerformanceDashboard::new(_event_sender.clone())), ); dashboards.insert( DashboardType::Config, create_config_dashboard(_event_sender.clone()), ); dashboards.insert( DashboardType::Backtesting, Box::new(BacktestingDashboard::new(_event_sender.clone())), ); dashboards.insert( DashboardType::Vault, Box::new(VaultStatusWidget::new(_event_sender.clone())), ); let manager = Self { active_dashboard: DashboardType::Trading, dashboards, layout_manager: LayoutManager::new(), event_receiver, _event_sender: _event_sender.clone(), }; (manager, _event_sender) } pub fn render(&mut self, frame: &mut Frame) -> Result<()> { let area = frame.area(); // Create main layout let (header_area, content_area, sidebar_area, footer_area) = self.layout_manager.create_layout(area); // Render header with navigation tabs self.render_header(frame, header_area)?; // Render active dashboard if let Some(dashboard) = self.dashboards.get_mut(&self.active_dashboard) { dashboard.render(frame, content_area)?; } // Render sidebar with quick stats self.render_sidebar(frame, sidebar_area)?; // Render footer with help and status self.render_footer(frame, footer_area)?; Ok(()) } pub fn handle_input(&mut self, key: KeyEvent) -> Result> { // Check for dashboard switching shortcuts first for dashboard_type in DashboardType::all() { if key.code == crossterm::event::KeyCode::Char(dashboard_type.shortcut_key()) { self.active_dashboard = dashboard_type; return Ok(Some(DashboardEvent::SwitchDashboard(dashboard_type))); } } // Handle ESC for exit if key.code == crossterm::event::KeyCode::Esc { return Ok(Some(DashboardEvent::Exit)); } // Pass input to active dashboard if let Some(dashboard) = self.dashboards.get_mut(&self.active_dashboard) { dashboard.handle_input(key) } else { Ok(None) } } pub async fn handle_event(&mut self, event: DashboardEvent) -> Result { match event { DashboardEvent::SwitchDashboard(dashboard_type) => { self.active_dashboard = dashboard_type; Ok(false) }, DashboardEvent::Exit => { Ok(true) // Signal to exit }, _ => { // Forward event to all dashboards that might be interested for dashboard in self.dashboards.values_mut() { let _ = dashboard.update(event.clone()); } Ok(false) }, } } // Vault service functionality removed - TLI is pure client, uses shared config crate /// All vault-related methods removed - TLI uses shared config crate instead // Vault service functionality completely removed from TLI // TLI is a pure client - no vault service management fn render_header(&self, frame: &mut Frame, area: Rect) -> Result<()> { let titles: Vec = DashboardType::all() .iter() .map(|dt| { let _prefix = if *dt == self.active_dashboard { "\u{25cf}" } else { "\u{25cb}" }; format!("[{}]{}", dt.shortcut_key().to_uppercase(), dt.title()) }) .collect(); let tabs = ratatui::widgets::Tabs::new(titles) .block( ratatui::widgets::Block::default() .borders(ratatui::widgets::Borders::ALL) .title("Foxhunt HFT Trading System - TLI Terminal"), ) .style(Style::default().fg(Color::White)) .highlight_style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ) .select(self.active_dashboard as usize); frame.render_widget(tabs, area); Ok(()) } fn render_sidebar(&self, frame: &mut Frame, area: Rect) -> Result<()> { let block = ratatui::widgets::Block::default() .borders(ratatui::widgets::Borders::ALL) .title("Quick Stats"); // Get actual Vault status (placeholder - TLI uses shared config crate) let vault_status = "\u{25cb}"; // Empty circle for not available - use config crate integration let content = ratatui::widgets::Paragraph::new( format!( "Connection: \u{25cf}\u{25cf}\u{25cf}\nVault: {}\nLatency: 12ms\nOrders: 15\nPositions: 5\nPnL: +$2,500", vault_status ), ) .block(block) .wrap(ratatui::widgets::Wrap { trim: true }); frame.render_widget(content, area); Ok(()) } fn render_footer(&self, frame: &mut Frame, area: Rect) -> Result<()> { let help_text = format!( "[{}] Dashboards | [ESC] Exit | Status: Connected", DashboardType::all() .iter() .map(|dt| format!( "[{}]{}", dt.shortcut_key().to_uppercase(), dt.title().chars().next().unwrap() )) .collect::>() .join(" ") ); let footer = ratatui::widgets::Paragraph::new(help_text) .block(ratatui::widgets::Block::default().borders(ratatui::widgets::Borders::ALL)) .style(Style::default().fg(Color::Gray)); frame.render_widget(footer, area); Ok(()) } }