from dash import Output, Input, State, html, callback_context, no_update import dash_bootstrap_components as dbc from utils.logger import get_logger from database.connection import DatabaseManager from datetime import datetime, timedelta from dashboard.callbacks.system_health_modules.common_health_utils import _check_data_collection_service_running from config.constants.system_health_constants import ( DATA_FRESHNESS_RECENT_MINUTES, DATA_FRESHNESS_STALE_HOURS ) logger = get_logger() def register_data_collection_callbacks(app): """Register data collection status and metrics callbacks.""" # Detailed Data Collection Service Status @app.callback( [Output('data-collection-service-status', 'children'), Output('data-collection-metrics', 'children')], [Input('interval-component', 'n_intervals'), Input('refresh-data-status-btn', 'n_clicks')] ) def update_data_collection_status(n_intervals, refresh_clicks): """Update detailed data collection service status and metrics.""" try: service_status = _get_data_collection_service_status() metrics = _get_data_collection_metrics() return service_status, metrics except Exception as e: logger.error(f"Error updating data collection status: {e}") error_div = dbc.Alert( f"Error: {str(e)}", color="danger", dismissable=True ) return error_div, error_div # Individual Collectors Status @app.callback( Output('individual-collectors-status', 'children'), [Input('interval-component', 'n_intervals'), Input('refresh-data-status-btn', 'n_clicks')] ) def update_individual_collectors_status(n_intervals, refresh_clicks): """Update individual data collector health status.""" try: return _get_individual_collectors_status() except Exception as e: logger.error(f"Error updating individual collectors status: {e}") return dbc.Alert( f"Error: {str(e)}", color="danger", dismissable=True ) # Data Collection Details Modal @app.callback( [Output("collection-details-modal", "is_open"), Output("collection-details-content", "children")], [Input("view-collection-details-btn", "n_clicks")], [State("collection-details-modal", "is_open")] ) def toggle_collection_details_modal(n_clicks, is_open): """Toggle and populate the collection details modal.""" if n_clicks: details_content = _get_collection_details_content() return not is_open, details_content return is_open, no_update # Collection Logs Modal @app.callback( [Output("collection-logs-modal", "is_open"), Output("collection-logs-content", "children")], [Input("view-collection-logs-btn", "n_clicks"), Input("refresh-logs-btn", "n_clicks")], [State("collection-logs-modal", "is_open")], prevent_initial_call=True ) def toggle_collection_logs_modal(logs_clicks, refresh_clicks, is_open): """Toggle and populate the collection logs modal.""" ctx = callback_context if not ctx.triggered: return is_open, no_update triggered_id = ctx.triggered_id if triggered_id in ["view-collection-logs-btn", "refresh-logs-btn"]: logs_content = _get_collection_logs_content() return True, logs_content return is_open, no_update @app.callback( Output("collection-logs-modal", "is_open", allow_duplicate=True), Input("close-logs-modal", "n_clicks"), State("collection-logs-modal", "is_open"), prevent_initial_call=True ) def close_logs_modal(n_clicks, is_open): if n_clicks: return not is_open return is_open def _get_data_collection_service_status() -> html.Div: """Get detailed data collection service status.""" try: is_running = _check_data_collection_service_running() current_time = datetime.now().strftime('%H:%M:%S') if is_running: status_badge = dbc.Badge("Service Running", color="success", className="me-2") status_text = html.P("Data collection service is actively collecting market data.", className="mb-0") details = html.Div() else: status_badge = dbc.Badge("Service Stopped", color="danger", className="me-2") status_text = html.P("Data collection service is not running.", className="text-danger") details = html.Div([ html.P("To start the service, run:", className="mt-2 mb-1"), html.Code("python scripts/start_data_collection.py") ]) return html.Div([ dbc.Row([ dbc.Col(status_badge, width="auto"), dbc.Col(html.P(f"Checked: {current_time}", className="text-muted mb-0"), width="auto") ], align="center", className="mb-2"), status_text, details ]) except Exception as e: return dbc.Alert(f"Error checking status: {e}", color="danger") def _get_data_collection_metrics() -> html.Div: """Get data collection metrics.""" try: db_manager = DatabaseManager() db_manager.initialize() with db_manager.get_session() as session: from sqlalchemy import text candles_count = session.execute(text("SELECT COUNT(*) FROM market_data")).scalar() or 0 tickers_count = session.execute(text("SELECT COUNT(*) FROM raw_trades WHERE data_type = 'ticker'")).scalar() or 0 latest_market_data = session.execute(text("SELECT MAX(timestamp) FROM market_data")).scalar() latest_raw_data = session.execute(text("SELECT MAX(timestamp) FROM raw_trades")).scalar() latest_data = max(d for d in [latest_market_data, latest_raw_data] if d) if any([latest_market_data, latest_raw_data]) else None if latest_data: time_diff = datetime.utcnow() - (latest_data.replace(tzinfo=None) if latest_data.tzinfo else latest_data) if time_diff < timedelta(minutes=DATA_FRESHNESS_RECENT_MINUTES): freshness_badge = dbc.Badge(f"Fresh ({time_diff.seconds // 60}m ago)", color="success") elif time_diff < timedelta(hours=DATA_FRESHNESS_STALE_HOURS): freshness_badge = dbc.Badge(f"Recent ({time_diff.seconds // 60}m ago)", color="warning") else: freshness_badge = dbc.Badge(f"Stale ({time_diff.total_seconds() // 3600:.1f}h ago)", color="danger") else: freshness_badge = dbc.Badge("No data", color="secondary") return html.Div([ dbc.Row([ dbc.Col(html.Strong("Candles:")), dbc.Col(f"{candles_count:,}", className="text-end") ]), dbc.Row([ dbc.Col(html.Strong("Tickers:")), dbc.Col(f"{tickers_count:,}", className="text-end") ]), dbc.Row([ dbc.Col(html.Strong("Data Freshness:")), dbc.Col(freshness_badge, className="text-end") ]) ]) except Exception as e: return dbc.Alert(f"Error loading metrics: {e}", color="danger") def _get_individual_collectors_status() -> html.Div: """Get individual data collector status.""" try: return dbc.Alert([ html.P("Individual collector health data will be displayed here when the data collection service is running.", className="mb-2"), html.Hr(), html.P("To start monitoring, run the following command:", className="mb-1"), html.Code("python scripts/start_data_collection.py") ], color="info") except Exception as e: return dbc.Alert(f"Error checking collector status: {e}", color="danger") def _get_collection_details_content() -> html.Div: """Get detailed collection information for modal.""" try: return html.Div([ html.H5("Data Collection Service Details"), html.P("Comprehensive data collection service information would be displayed here."), html.Hr(), html.H6("Configuration"), html.P("Service configuration details..."), html.H6("Performance Metrics"), html.P("Detailed performance analytics..."), html.H6("Health Status"), html.P("Individual collector health information...") ]) except Exception as e: return dbc.Alert(f"Error loading details: {e}", color="danger") def _get_collection_logs_content() -> str: """Get recent collection service logs.""" try: current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"""[{current_time}] INFO - Data Collection Service Logs Recent log entries would be displayed here from the data collection service. This would include: - Service startup/shutdown events - Collector connection status changes - Data collection statistics - Error messages and warnings - Performance metrics To view real logs, check the logs/ directory or configure log file monitoring. """ except Exception as e: return f"Error loading logs: {str(e)}"