234 lines
9.3 KiB
Python
234 lines
9.3 KiB
Python
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)}" |