Removed Mantine for UI, and used bootstrap for simplicity

This commit is contained in:
Vasily.onl
2025-06-06 13:33:59 +08:00
parent 38cbf9cd2f
commit 58a754414a
12 changed files with 682 additions and 1284 deletions

View File

@@ -3,6 +3,7 @@ Chart-related callbacks for the dashboard.
"""
from dash import Output, Input, State, Patch, ctx, html, no_update, dcc
import dash_bootstrap_components as dbc
from datetime import datetime, timedelta
from utils.logger import get_logger
from components.charts import (
@@ -137,15 +138,15 @@ def register_chart_callbacks(app):
)
def update_market_stats(stored_data, symbol, timeframe):
if not stored_data:
return html.Div("Statistics will be available once chart data is loaded.")
return dbc.Alert("Statistics will be available once chart data is loaded.", color="info")
try:
df = pd.read_json(io.StringIO(stored_data), orient='split')
if df.empty:
return html.Div("Not enough data to calculate statistics.")
return dbc.Alert("Not enough data to calculate statistics.", color="warning")
return get_market_statistics(df, symbol, timeframe)
except Exception as e:
logger.error(f"Error updating market stats from stored data: {e}", exc_info=True)
return html.Div(f"Error loading statistics: {e}", style={'color': 'red'})
return dbc.Alert(f"Error loading statistics: {e}", color="danger")
@app.callback(
Output("download-chart-data", "data"),

View File

@@ -3,7 +3,7 @@ Data analysis callbacks for the dashboard.
"""
from dash import Output, Input, html, dcc
import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from utils.logger import get_logger
from dashboard.components.data_analysis import (
VolumeAnalyzer,
@@ -35,14 +35,14 @@ def register_data_analysis_callbacks(app):
logger.info(f"🎯 DATA ANALYSIS CALLBACK TRIGGERED! Type: {analysis_type}, Period: {period}")
# Return placeholder message since we're moving to enhanced market stats
info_msg = html.Div([
html.H4("📊 Statistical Analysis"),
info_msg = dbc.Alert([
html.H4("📊 Statistical Analysis", className="alert-heading"),
html.P("Data analysis has been integrated into the Market Statistics section above."),
html.P("The enhanced statistics now include volume analysis, price movement analysis, and trend indicators."),
html.P("Change the symbol and timeframe in the main chart to see updated analysis."),
html.Hr(),
html.Small("This section will be updated with additional analytical tools in future versions.")
], style={'border': '2px solid #17a2b8', 'padding': '20px', 'margin': '10px', 'background-color': '#d1ecf1'})
html.P("This section will be updated with additional analytical tools in future versions.", className="mb-0")
], color="info")
return info_msg, html.Div()

View File

@@ -3,7 +3,8 @@ Indicator-related callbacks for the dashboard.
"""
import dash
from dash import Output, Input, State, html, dcc, callback_context
from dash import Output, Input, State, html, dcc, callback_context, no_update
import dash_bootstrap_components as dbc
import json
from utils.logger import get_logger
@@ -15,106 +16,36 @@ def register_indicator_callbacks(app):
# Modal control callbacks
@app.callback(
[Output('indicator-modal', 'style'),
Output('indicator-modal-background', 'style')],
[Input('add-indicator-btn', 'n_clicks'),
Input('close-modal-btn', 'n_clicks'),
Output('indicator-modal', 'is_open'),
[Input('add-indicator-btn-visible', 'n_clicks'),
Input('cancel-indicator-btn', 'n_clicks'),
Input('edit-indicator-store', 'data')]
)
def toggle_indicator_modal(add_clicks, close_clicks, cancel_clicks, edit_data):
"""Toggle the visibility of the add indicator modal."""
# Default hidden styles
hidden_modal_style = {
'display': 'none',
'position': 'fixed',
'z-index': '1001',
'left': '0',
'top': '0',
'width': '100%',
'height': '100%',
'visibility': 'hidden'
}
hidden_background_style = {
'display': 'none',
'position': 'fixed',
'z-index': '1000',
'left': '0',
'top': '0',
'width': '100%',
'height': '100%',
'background-color': 'rgba(0,0,0,0.5)',
'visibility': 'hidden'
}
# Visible styles
visible_modal_style = {
'display': 'block',
'position': 'fixed',
'z-index': '1001',
'left': '0',
'top': '0',
'width': '100%',
'height': '100%',
'visibility': 'visible'
}
visible_background_style = {
'display': 'block',
'position': 'fixed',
'z-index': '1000',
'left': '0',
'top': '0',
'width': '100%',
'height': '100%',
'background-color': 'rgba(0,0,0,0.5)',
'visibility': 'visible'
}
ctx = dash.callback_context
# If no trigger or initial load, return hidden
if not ctx.triggered:
return [hidden_modal_style, hidden_background_style]
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
# Only open modal if explicitly requested
should_open = False
# Check if add button was clicked (and has a click count > 0)
if triggered_id == 'add-indicator-btn' and add_clicks and add_clicks > 0:
should_open = True
# Check if edit button triggered and should open modal
elif triggered_id == 'edit-indicator-store' and edit_data and edit_data.get('open_modal') and edit_data.get('mode') == 'edit':
should_open = True
# Check if close/cancel buttons were clicked
elif triggered_id in ['close-modal-btn', 'cancel-indicator-btn']:
should_open = False
# Default: don't open
else:
should_open = False
if should_open:
return [visible_modal_style, visible_background_style]
else:
return [hidden_modal_style, hidden_background_style]
# Sync visible button clicks to hidden button
@app.callback(
Output('add-indicator-btn', 'n_clicks'),
Input('add-indicator-btn-visible', 'n_clicks'),
Input('save-indicator-btn', 'n_clicks'),
Input({'type': 'edit-indicator-btn', 'index': dash.ALL}, 'n_clicks')],
[State('indicator-modal', 'is_open')],
prevent_initial_call=True
)
def sync_add_button_clicks(visible_clicks):
"""Sync clicks from visible button to hidden button."""
return visible_clicks or 0
def toggle_indicator_modal(add_clicks, cancel_clicks, save_clicks, edit_clicks, is_open):
"""Toggle the visibility of the add indicator modal."""
ctx = callback_context
if not ctx.triggered:
return is_open
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
# Check for add button click
if triggered_id == 'add-indicator-btn-visible' and add_clicks:
return True
# Check for edit button clicks, ensuring a click actually happened
if 'edit-indicator-btn' in triggered_id and any(c for c in edit_clicks if c is not None):
return True
# Check for cancel or save clicks to close the modal
if triggered_id in ['cancel-indicator-btn', 'save-indicator-btn']:
return False
return is_open
# Update parameter fields based on indicator type
@app.callback(
[Output('indicator-parameters-message', 'style'),
@@ -190,7 +121,7 @@ def register_indicator_callbacks(app):
bb_period, bb_stddev, edit_data):
"""Save a new indicator or update an existing one."""
if not n_clicks or not name or not indicator_type:
return "", dash.no_update, dash.no_update
return "", no_update, no_update
try:
# Get indicator manager
@@ -218,6 +149,7 @@ def register_indicator_callbacks(app):
'std_dev': bb_stddev or 2.0
}
feedback_msg = None
# Check if this is an edit operation
is_edit = edit_data and edit_data.get('mode') == 'edit'
@@ -233,16 +165,10 @@ def register_indicator_callbacks(app):
)
if success:
success_msg = html.Div([
html.Span("", style={'color': '#28a745'}),
html.Span(f"Indicator '{name}' updated successfully!", style={'color': '#28a745'})
])
feedback_msg = dbc.Alert(f"Indicator '{name}' updated successfully!", color="success")
else:
error_msg = html.Div([
html.Span("", style={'color': '#dc3545'}),
html.Span("Failed to update indicator. Please try again.", style={'color': '#dc3545'})
])
return error_msg, dash.no_update, dash.no_update
feedback_msg = dbc.Alert("Failed to update indicator.", color="danger")
return feedback_msg, no_update, no_update
else:
# Create new indicator
new_indicator = manager.create_indicator(
@@ -254,16 +180,10 @@ def register_indicator_callbacks(app):
)
if not new_indicator:
error_msg = html.Div([
html.Span("", style={'color': '#dc3545'}),
html.Span("Failed to save indicator. Please try again.", style={'color': '#dc3545'})
])
return error_msg, dash.no_update, dash.no_update
feedback_msg = dbc.Alert("Failed to save indicator.", color="danger")
return feedback_msg, no_update, no_update
success_msg = html.Div([
html.Span("", style={'color': '#28a745'}),
html.Span(f"Indicator '{name}' saved successfully!", style={'color': '#28a745'})
])
feedback_msg = dbc.Alert(f"Indicator '{name}' saved successfully!", color="success")
# Refresh the indicator options
overlay_indicators = manager.get_indicators_by_type('overlay')
@@ -279,15 +199,12 @@ def register_indicator_callbacks(app):
display_name = f"{indicator.name} ({indicator.type.upper()})"
subplot_options.append({'label': display_name, 'value': indicator.id})
return success_msg, overlay_options, subplot_options
return feedback_msg, overlay_options, subplot_options
except Exception as e:
logger.error(f"Indicator callback: Error saving indicator: {e}")
error_msg = html.Div([
html.Span("", style={'color': '#dc3545'}),
html.Span(f"Error: {str(e)}", style={'color': '#dc3545'})
])
return error_msg, dash.no_update, dash.no_update
error_msg = dbc.Alert(f"Error: {str(e)}", color="danger")
return error_msg, no_update, no_update
# Update custom indicator lists with edit/delete buttons
@app.callback(
@@ -324,27 +241,15 @@ def register_indicator_callbacks(app):
"✏️",
id={'type': 'edit-indicator-btn', 'index': indicator_id},
title="Edit indicator",
style={
'background': 'none',
'border': 'none',
'cursor': 'pointer',
'margin-left': '5px',
'font-size': '14px',
'color': '#007bff'
}
className="btn btn-sm btn-outline-primary",
style={'margin-left': '5px'}
),
html.Button(
"🗑️",
id={'type': 'delete-indicator-btn', 'index': indicator_id},
title="Delete indicator",
style={
'background': 'none',
'border': 'none',
'cursor': 'pointer',
'margin-left': '5px',
'font-size': '14px',
'color': '#dc3545'
}
className="btn btn-sm btn-outline-danger",
style={'margin-left': '5px'}
)
], style={'display': 'inline-block', 'width': '30%', 'text-align': 'right'})
], style={
@@ -428,9 +333,9 @@ def register_indicator_callbacks(app):
)
def delete_indicator(delete_clicks, button_ids):
"""Delete an indicator when delete button is clicked."""
ctx = dash.callback_context
ctx = callback_context
if not ctx.triggered or not any(delete_clicks):
return dash.no_update, dash.no_update, dash.no_update
return no_update, no_update, no_update
# Find which button was clicked
triggered_id = ctx.triggered[0]['prop_id']
@@ -461,26 +366,17 @@ def register_indicator_callbacks(app):
display_name = f"{indicator.name} ({indicator.type.upper()})"
subplot_options.append({'label': display_name, 'value': indicator.id})
success_msg = html.Div([
html.Span("🗑️ ", style={'color': '#dc3545'}),
html.Span(f"Indicator '{indicator_name}' deleted successfully!", style={'color': '#dc3545'})
])
success_msg = dbc.Alert(f"Indicator '{indicator_name}' deleted.", color="warning")
return success_msg, overlay_options, subplot_options
else:
error_msg = html.Div([
html.Span("", style={'color': '#dc3545'}),
html.Span("Failed to delete indicator.", style={'color': '#dc3545'})
])
return error_msg, dash.no_update, dash.no_update
error_msg = dbc.Alert("Failed to delete indicator.", color="danger")
return error_msg, no_update, no_update
except Exception as e:
logger.error(f"Indicator callback: Error deleting indicator: {e}")
error_msg = html.Div([
html.Span("", style={'color': '#dc3545'}),
html.Span(f"Error: {str(e)}", style={'color': '#dc3545'})
])
return error_msg, dash.no_update, dash.no_update
error_msg = dbc.Alert(f"Error: {str(e)}", color="danger")
return error_msg, no_update, no_update
# Handle edit indicator - open modal with existing data
@app.callback(
@@ -505,9 +401,9 @@ def register_indicator_callbacks(app):
)
def edit_indicator(edit_clicks, button_ids):
"""Load indicator data for editing."""
ctx = dash.callback_context
ctx = callback_context
if not ctx.triggered or not any(edit_clicks):
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
# Find which button was clicked
triggered_id = ctx.triggered[0]['prop_id']
@@ -569,13 +465,13 @@ def register_indicator_callbacks(app):
bb_stddev
)
else:
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
except Exception as e:
logger.error(f"Indicator callback: Error loading indicator for edit: {e}")
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
# Reset modal form when closed
# Reset modal form when closed or saved
@app.callback(
[Output('indicator-name-input', 'value', allow_duplicate=True),
Output('indicator-type-dropdown', 'value', allow_duplicate=True),
@@ -593,14 +489,14 @@ def register_indicator_callbacks(app):
Output('macd-signal-period-input', 'value', allow_duplicate=True),
Output('bb-period-input', 'value', allow_duplicate=True),
Output('bb-stddev-input', 'value', allow_duplicate=True)],
[Input('close-modal-btn', 'n_clicks'),
Input('cancel-indicator-btn', 'n_clicks')],
[Input('cancel-indicator-btn', 'n_clicks'),
Input('save-indicator-btn', 'n_clicks')], # Also reset on successful save
prevent_initial_call=True
)
def reset_modal_form(close_clicks, cancel_clicks):
"""Reset the modal form when it's closed."""
if close_clicks or cancel_clicks:
def reset_modal_form(cancel_clicks, save_clicks):
"""Reset the modal form when it's closed or saved."""
if cancel_clicks or save_clicks:
return "", None, "", "#007bff", 2, "📊 Add New Indicator", None, 20, 12, 14, 12, 26, 9, 20, 2.0
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
return no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update, no_update
logger.info("Indicator callbacks: registered successfully")

View File

@@ -9,7 +9,7 @@ import psutil
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List
from dash import Output, Input, State, html, callback_context, no_update
import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from utils.logger import get_logger
from database.connection import DatabaseManager
from database.redis_manager import RedisManager
@@ -47,7 +47,7 @@ def register_system_health_callbacks(app):
except Exception as e:
logger.error(f"Error updating quick status: {e}")
error_status = dmc.Badge("🔴 Error", color="red", variant="light")
error_status = dbc.Badge("🔴 Error", color="danger", className="me-1")
return error_status, error_status, error_status, error_status
# Detailed Data Collection Service Status
@@ -67,11 +67,10 @@ def register_system_health_callbacks(app):
except Exception as e:
logger.error(f"Error updating data collection status: {e}")
error_div = dmc.Alert(
error_div = dbc.Alert(
f"Error: {str(e)}",
title="🔴 Status Check Failed",
color="red",
variant="light"
color="danger",
dismissable=True
)
return error_div, error_div
@@ -87,11 +86,10 @@ def register_system_health_callbacks(app):
return _get_individual_collectors_status()
except Exception as e:
logger.error(f"Error updating individual collectors status: {e}")
return dmc.Alert(
return dbc.Alert(
f"Error: {str(e)}",
title="🔴 Collectors Check Failed",
color="red",
variant="light"
color="danger",
dismissable=True
)
# Database Status and Statistics
@@ -110,11 +108,10 @@ def register_system_health_callbacks(app):
except Exception as e:
logger.error(f"Error updating database status: {e}")
error_alert = dmc.Alert(
error_alert = dbc.Alert(
f"Error: {str(e)}",
title="🔴 Database Check Failed",
color="red",
variant="light"
color="danger",
dismissable=True
)
return error_alert, error_alert
@@ -134,11 +131,10 @@ def register_system_health_callbacks(app):
except Exception as e:
logger.error(f"Error updating Redis status: {e}")
error_alert = dmc.Alert(
error_alert = dbc.Alert(
f"Error: {str(e)}",
title="🔴 Redis Check Failed",
color="red",
variant="light"
color="danger",
dismissable=True
)
return error_alert, error_alert
@@ -153,475 +149,365 @@ def register_system_health_callbacks(app):
return _get_system_performance_metrics()
except Exception as e:
logger.error(f"Error updating system performance: {e}")
return dmc.Alert(
return dbc.Alert(
f"Error: {str(e)}",
title="🔴 Performance Check Failed",
color="red",
variant="light"
color="danger",
dismissable=True
)
# Data Collection Details Modal
@app.callback(
[Output("collection-details-modal", "opened"),
[Output("collection-details-modal", "is_open"),
Output("collection-details-content", "children")],
[Input("view-collection-details-btn", "n_clicks")],
State("collection-details-modal", "opened")
[State("collection-details-modal", "is_open")]
)
def toggle_collection_details_modal(details_clicks, is_open):
def toggle_collection_details_modal(n_clicks, is_open):
"""Toggle and populate the collection details modal."""
if details_clicks:
# Load detailed collection information
if n_clicks:
details_content = _get_collection_details_content()
return True, details_content
return not is_open, details_content
return is_open, no_update
# Collection Logs Modal
@app.callback(
[Output("collection-logs-modal", "opened"),
[Output("collection-logs-modal", "is_open"),
Output("collection-logs-content", "children")],
[Input("view-collection-logs-btn", "n_clicks"),
Input("refresh-logs-btn", "n_clicks"),
Input("close-logs-modal", "n_clicks")],
State("collection-logs-modal", "opened")
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, close_clicks, is_open):
def toggle_collection_logs_modal(logs_clicks, refresh_clicks, is_open):
"""Toggle and populate the collection logs modal."""
if logs_clicks or refresh_clicks:
# Load recent logs
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
elif close_clicks:
return False, no_update
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
logger.info("Enhanced system health callbacks registered successfully")
# Helper Functions
def _get_data_collection_quick_status() -> dmc.Badge:
def _get_data_collection_quick_status() -> dbc.Badge:
"""Get quick data collection status."""
try:
# Check if data collection service is running (simplified check)
is_running = _check_data_collection_service_running()
if is_running:
return dmc.Badge("🟢 Active", color="green", variant="light")
return dbc.Badge("Active", color="success", className="me-1")
else:
return dmc.Badge("🔴 Stopped", color="red", variant="light")
return dbc.Badge("Stopped", color="danger", className="me-1")
except:
return dmc.Badge("🟡 Unknown", color="yellow", variant="light")
return dbc.Badge("Unknown", color="warning", className="me-1")
def _get_database_quick_status() -> dmc.Badge:
def _get_database_quick_status() -> dbc.Badge:
"""Get quick database status."""
try:
db_manager = DatabaseManager()
db_manager.initialize() # Initialize the database manager
result = db_manager.test_connection()
if result:
return dmc.Badge("🟢 Connected", color="green", variant="light")
db_manager.initialize()
if db_manager.test_connection():
return dbc.Badge("Connected", color="success", className="me-1")
else:
return dmc.Badge("🔴 Error", color="red", variant="light")
return dbc.Badge("Error", color="danger", className="me-1")
except:
return dmc.Badge("🔴 Error", color="red", variant="light")
return dbc.Badge("Error", color="danger", className="me-1")
def _get_redis_quick_status() -> dmc.Badge:
def _get_redis_quick_status() -> dbc.Badge:
"""Get quick Redis status."""
try:
redis_manager = RedisManager()
redis_manager.initialize() # Initialize the Redis manager
result = redis_manager.test_connection()
if result:
return dmc.Badge("🟢 Connected", color="green", variant="light")
redis_manager.initialize()
if redis_manager.test_connection():
return dbc.Badge("Connected", color="success", className="me-1")
else:
return dmc.Badge("🔴 Error", color="red", variant="light")
return dbc.Badge("Error", color="danger", className="me-1")
except:
return dmc.Badge("🔴 Error", color="red", variant="light")
return dbc.Badge("Error", color="danger", className="me-1")
def _get_performance_quick_status() -> dmc.Badge:
def _get_performance_quick_status() -> dbc.Badge:
"""Get quick performance status."""
try:
cpu_percent = psutil.cpu_percent(interval=0.1)
memory = psutil.virtual_memory()
if cpu_percent < 80 and memory.percent < 80:
return dmc.Badge("🟢 Good", color="green", variant="light")
return dbc.Badge("Good", color="success", className="me-1")
elif cpu_percent < 90 and memory.percent < 90:
return dmc.Badge("🟡 Warning", color="yellow", variant="light")
return dbc.Badge("Warning", color="warning", className="me-1")
else:
return dmc.Badge("🔴 High", color="red", variant="light")
return dbc.Badge("High", color="danger", className="me-1")
except:
return dmc.Badge("Unknown", color="gray", variant="light")
return dbc.Badge("Unknown", color="secondary", className="me-1")
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()
current_time = datetime.now().strftime('%H:%M:%S')
if is_running:
return dmc.Stack([
dmc.Group([
dmc.Badge("🟢 Service Running", color="green", variant="light"),
dmc.Text(f"Checked: {current_time.strftime('%H:%M:%S')}", size="xs", c="dimmed")
], justify="space-between"),
dmc.Text("Data collection service is actively collecting market data.",
size="sm", c="#2c3e50")
], gap="xs")
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:
return dmc.Stack([
dmc.Group([
dmc.Badge("🔴 Service Stopped", color="red", variant="light"),
dmc.Text(f"Checked: {current_time.strftime('%H:%M:%S')}", size="xs", c="dimmed")
], justify="space-between"),
dmc.Text("Data collection service is not running.", size="sm", c="#e74c3c"),
dmc.Code("python scripts/start_data_collection.py", style={'margin-top': '5px'})
], gap="xs")
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 dmc.Alert(
f"Error: {str(e)}",
title="🔴 Status Check Failed",
color="red",
variant="light"
)
return dbc.Alert(f"Error checking status: {e}", color="danger")
def _get_data_collection_metrics() -> html.Div:
"""Get data collection metrics."""
try:
# Get database statistics for collected data
db_manager = DatabaseManager()
db_manager.initialize() # Initialize the database manager
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()
# Count OHLCV candles from market_data table
candles_count = session.execute(
text("SELECT COUNT(*) FROM market_data")
).scalar() or 0
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
# Count raw tickers from raw_trades table
tickers_count = session.execute(
text("SELECT COUNT(*) FROM raw_trades WHERE data_type = 'ticker'")
).scalar() or 0
# Get latest data timestamp from both tables
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()
# Use the most recent timestamp
latest_data = None
if latest_market_data and latest_raw_data:
latest_data = max(latest_market_data, latest_raw_data)
elif latest_market_data:
latest_data = latest_market_data
elif latest_raw_data:
latest_data = latest_raw_data
# Calculate data freshness
data_freshness_badge = dmc.Badge("No data", color="gray", variant="light")
if latest_data:
time_diff = datetime.utcnow() - latest_data.replace(tzinfo=None) if latest_data.tzinfo else datetime.utcnow() - latest_data
time_diff = datetime.utcnow() - (latest_data.replace(tzinfo=None) if latest_data.tzinfo else latest_data)
if time_diff < timedelta(minutes=5):
data_freshness_badge = dmc.Badge(f"🟢 Fresh ({time_diff.seconds // 60}m ago)", color="green", variant="light")
freshness_badge = dbc.Badge(f"Fresh ({time_diff.seconds // 60}m ago)", color="success")
elif time_diff < timedelta(hours=1):
data_freshness_badge = dmc.Badge(f"🟡 Recent ({time_diff.seconds // 60}m ago)", color="yellow", variant="light")
freshness_badge = dbc.Badge(f"Recent ({time_diff.seconds // 60}m ago)", color="warning")
else:
data_freshness_badge = dmc.Badge(f"🔴 Stale ({time_diff.total_seconds() // 3600:.1f}h ago)", color="red", variant="light")
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 dmc.Stack([
dmc.Group([
dmc.Text(f"Candles: {candles_count:,}", fw=500),
dmc.Text(f"Tickers: {tickers_count:,}", fw=500)
], justify="space-between"),
dmc.Group([
dmc.Text("Data Freshness:", fw=500),
data_freshness_badge
], justify="space-between")
], gap="xs")
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 dmc.Alert(
f"Error: {str(e)}",
title="🔴 Metrics Unavailable",
color="red",
variant="light"
)
return dbc.Alert(f"Error loading metrics: {e}", color="danger")
def _get_individual_collectors_status() -> html.Div:
"""Get individual data collector status."""
try:
# This would connect to a running data collection service
# For now, show a placeholder indicating the status
return dmc.Alert([
dmc.Text("Individual collector health data would be displayed here when the data collection service is running.", size="sm"),
dmc.Space(h="sm"),
dmc.Group([
dmc.Text("To start monitoring:", size="sm"),
dmc.Code("python scripts/start_data_collection.py")
])
], title="📊 Collector Health Monitoring", color="blue", variant="light")
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 dmc.Alert(
f"Error: {str(e)}",
title="🔴 Collector Status Check Failed",
color="red",
variant="light"
)
return dbc.Alert(f"Error checking collector status: {e}", color="danger")
def _get_database_status() -> html.Div:
"""Get detailed database status."""
try:
db_manager = DatabaseManager()
db_manager.initialize() # Initialize the database manager
db_manager.initialize()
with db_manager.get_session() as session:
# Test connection and get basic info
from sqlalchemy import text
result = session.execute(text("SELECT version()")).fetchone()
version = result[0] if result else "Unknown"
connections = session.execute(text("SELECT count(*) FROM pg_stat_activity")).scalar() or 0
# Get connection count
connections = session.execute(
text("SELECT count(*) FROM pg_stat_activity")
).scalar() or 0
return dmc.Stack([
dmc.Group([
dmc.Badge("🟢 Database Connected", color="green", variant="light"),
dmc.Text(f"Checked: {datetime.now().strftime('%H:%M:%S')}", size="xs", c="dimmed")
], justify="space-between"),
dmc.Text(f"Version: PostgreSQL {version.split()[1] if 'PostgreSQL' in version else 'Unknown'}",
size="xs", c="dimmed"),
dmc.Text(f"Active connections: {connections}", size="xs", c="dimmed")
], gap="xs")
return html.Div([
dbc.Row([
dbc.Col(dbc.Badge("Database Connected", color="success"), width="auto"),
dbc.Col(f"Checked: {datetime.now().strftime('%H:%M:%S')}", className="text-muted")
], align="center", className="mb-2"),
html.P(f"Version: PostgreSQL {version.split()[1] if 'PostgreSQL' in version else 'Unknown'}", className="mb-1"),
html.P(f"Active connections: {connections}", className="mb-0")
])
except Exception as e:
return dmc.Alert(
f"Error: {str(e)}",
title="🔴 Database Connection Failed",
color="red",
variant="light"
)
return dbc.Alert(f"Error connecting to database: {e}", color="danger")
def _get_database_statistics() -> html.Div:
"""Get database statistics."""
try:
db_manager = DatabaseManager()
db_manager.initialize() # Initialize the database manager
db_manager.initialize()
with db_manager.get_session() as session:
# Get table sizes
from sqlalchemy import text
table_stats = session.execute(text("""
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE schemaname NOT IN ('information_schema', 'pg_catalog')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 5
""")).fetchall()
# Get recent activity from both main data tables
market_data_activity = session.execute(
text("SELECT COUNT(*) FROM market_data WHERE timestamp > NOW() - INTERVAL '1 hour'")
).scalar() or 0
raw_data_activity = session.execute(
text("SELECT COUNT(*) FROM raw_trades WHERE timestamp > NOW() - INTERVAL '1 hour'")
).scalar() or 0
table_stats_query = """
SELECT tablename, pg_size_pretty(pg_total_relation_size('public.'||tablename)) as size
FROM pg_tables WHERE schemaname = 'public'
ORDER BY pg_total_relation_size('public.'||tablename) DESC LIMIT 5
"""
table_stats = session.execute(text(table_stats_query)).fetchall()
market_data_activity = session.execute(text("SELECT COUNT(*) FROM market_data WHERE timestamp > NOW() - INTERVAL '1 hour'")).scalar() or 0
raw_data_activity = session.execute(text("SELECT COUNT(*) FROM raw_trades WHERE timestamp > NOW() - INTERVAL '1 hour'")).scalar() or 0
total_recent_activity = market_data_activity + raw_data_activity
stats_components = [
dmc.Group([
dmc.Text("Recent Activity (1h):", fw=500),
dmc.Text(f"{total_recent_activity:,} records", c="#2c3e50")
], justify="space-between"),
dmc.Group([
dmc.Text("• Market Data:", fw=400),
dmc.Text(f"{market_data_activity:,}", c="#7f8c8d")
], justify="space-between"),
dmc.Group([
dmc.Text("• Raw Data:", fw=400),
dmc.Text(f"{raw_data_activity:,}", c="#7f8c8d")
], justify="space-between")
components = [
dbc.Row([
dbc.Col(html.Strong("Recent Activity (1h):")),
dbc.Col(f"{total_recent_activity:,} records", className="text-end")
]),
html.Hr(className="my-2"),
html.Strong("Largest Tables:"),
]
if table_stats:
stats_components.append(dmc.Text("Largest Tables:", fw=500))
for schema, table, size in table_stats:
stats_components.append(
dmc.Text(f"{table}: {size}", size="xs", c="dimmed", style={'margin-left': '10px'})
)
return dmc.Stack(stats_components, gap="xs")
for table, size in table_stats:
components.append(dbc.Row([
dbc.Col(f"{table}"),
dbc.Col(size, className="text-end text-muted")
]))
else:
components.append(html.P("No table statistics available.", className="text-muted"))
return html.Div(components)
except Exception as e:
return dmc.Alert(
f"Error: {str(e)}",
title="🔴 Statistics Unavailable",
color="red",
variant="light"
)
return dbc.Alert(f"Error loading database stats: {e}", color="danger")
def _get_redis_status() -> html.Div:
"""Get Redis status."""
try:
redis_manager = RedisManager()
redis_manager.initialize() # Initialize the Redis manager
redis_manager.initialize()
info = redis_manager.get_info()
return dmc.Stack([
dmc.Group([
dmc.Badge("🟢 Redis Connected", color="green", variant="light"),
dmc.Text(f"Checked: {datetime.now().strftime('%H:%M:%S')}", size="xs", c="dimmed")
], justify="space-between"),
dmc.Text(f"Host: {redis_manager.config.host}:{redis_manager.config.port}",
size="xs", c="dimmed")
], gap="xs")
return html.Div([
dbc.Row([
dbc.Col(dbc.Badge("Redis Connected", color="success"), width="auto"),
dbc.Col(f"Checked: {datetime.now().strftime('%H:%M:%S')}", className="text-muted")
], align="center", className="mb-2"),
html.P(f"Host: {redis_manager.config.host}:{redis_manager.config.port}", className="mb-0")
])
except Exception as e:
return dmc.Alert(
f"Error: {str(e)}",
title="🔴 Redis Connection Failed",
color="red",
variant="light"
)
return dbc.Alert(f"Error connecting to Redis: {e}", color="danger")
def _get_redis_statistics() -> html.Div:
"""Get Redis statistics."""
try:
redis_manager = RedisManager()
redis_manager.initialize() # Initialize the Redis manager
# Get Redis info
redis_manager.initialize()
info = redis_manager.get_info()
return dmc.Stack([
dmc.Group([
dmc.Text("Memory Used:", fw=500),
dmc.Text(f"{info.get('used_memory_human', 'Unknown')}", c="#2c3e50")
], justify="space-between"),
dmc.Group([
dmc.Text("Connected Clients:", fw=500),
dmc.Text(f"{info.get('connected_clients', 'Unknown')}", c="#2c3e50")
], justify="space-between"),
dmc.Group([
dmc.Text("Uptime:", fw=500),
dmc.Text(f"{info.get('uptime_in_seconds', 0) // 3600}h", c="#2c3e50")
], justify="space-between")
], gap="xs")
return html.Div([
dbc.Row([dbc.Col("Memory Used:"), dbc.Col(info.get('used_memory_human', 'N/A'), className="text-end")]),
dbc.Row([dbc.Col("Connected Clients:"), dbc.Col(info.get('connected_clients', 'N/A'), className="text-end")]),
dbc.Row([dbc.Col("Uptime (hours):"), dbc.Col(f"{info.get('uptime_in_seconds', 0) // 3600}", className="text-end")])
])
except Exception as e:
return dmc.Alert(
f"Error: {str(e)}",
title="🔴 Statistics Unavailable",
color="red",
variant="light"
)
return dbc.Alert(f"Error loading Redis stats: {e}", color="danger")
def _get_system_performance_metrics() -> html.Div:
"""Get system performance metrics."""
try:
# CPU usage
cpu_percent = psutil.cpu_percent(interval=0.1)
cpu_count = psutil.cpu_count()
# Memory usage
memory = psutil.virtual_memory()
# Disk usage
disk = psutil.disk_usage('/')
# Network I/O (if available)
try:
network = psutil.net_io_counters()
network_sent = f"{network.bytes_sent / (1024**3):.2f} GB"
network_recv = f"{network.bytes_recv / (1024**3):.2f} GB"
except:
network_sent = "N/A"
network_recv = "N/A"
# Color coding for metrics
cpu_color = "green" if cpu_percent < 70 else "yellow" if cpu_percent < 85 else "red"
memory_color = "green" if memory.percent < 70 else "yellow" if memory.percent < 85 else "red"
disk_color = "green" if disk.percent < 70 else "yellow" if disk.percent < 85 else "red"
return dmc.Stack([
dmc.Group([
dmc.Text("CPU Usage:", fw=500),
dmc.Badge(f"{cpu_percent:.1f}%", color=cpu_color, variant="light"),
dmc.Text(f"({cpu_count} cores)", size="xs", c="dimmed")
], justify="space-between"),
dmc.Group([
dmc.Text("Memory:", fw=500),
dmc.Badge(f"{memory.percent:.1f}%", color=memory_color, variant="light"),
dmc.Text(f"{memory.used // (1024**3)} GB / {memory.total // (1024**3)} GB",
size="xs", c="dimmed")
], justify="space-between"),
dmc.Group([
dmc.Text("Disk Usage:", fw=500),
dmc.Badge(f"{disk.percent:.1f}%", color=disk_color, variant="light"),
dmc.Text(f"{disk.used // (1024**3)} GB / {disk.total // (1024**3)} GB",
size="xs", c="dimmed")
], justify="space-between"),
dmc.Group([
dmc.Text("Network I/O:", fw=500),
dmc.Text(f"{network_sent}{network_recv}", size="xs", c="dimmed")
], justify="space-between")
], gap="sm")
def get_color(percent):
if percent < 70: return "success"
if percent < 85: return "warning"
return "danger"
return html.Div([
html.Div([
html.Strong("CPU Usage: "),
dbc.Badge(f"{cpu_percent:.1f}%", color=get_color(cpu_percent)),
html.Span(f" ({cpu_count} cores)", className="text-muted ms-1")
], className="mb-2"),
dbc.Progress(value=cpu_percent, color=get_color(cpu_percent), style={"height": "10px"}, className="mb-3"),
html.Div([
html.Strong("Memory Usage: "),
dbc.Badge(f"{memory.percent:.1f}%", color=get_color(memory.percent)),
html.Span(f" ({memory.used / (1024**3):.1f} / {memory.total / (1024**3):.1f} GB)", className="text-muted ms-1")
], className="mb-2"),
dbc.Progress(value=memory.percent, color=get_color(memory.percent), style={"height": "10px"}, className="mb-3"),
html.Div([
html.Strong("Disk Usage: "),
dbc.Badge(f"{disk.percent:.1f}%", color=get_color(disk.percent)),
html.Span(f" ({disk.used / (1024**3):.1f} / {disk.total / (1024**3):.1f} GB)", className="text-muted ms-1")
], className="mb-2"),
dbc.Progress(value=disk.percent, color=get_color(disk.percent), style={"height": "10px"})
])
except Exception as e:
return dmc.Alert(
f"Error: {str(e)}",
title="🔴 Performance Metrics Unavailable",
color="red",
variant="light"
)
return dbc.Alert(f"Error loading performance metrics: {e}", color="danger")
def _get_collection_details_content() -> html.Div:
"""Get detailed collection information for modal."""
try:
# Detailed service and collector information
return dmc.Stack([
dmc.Title("📊 Data Collection Service Details", order=5),
dmc.Text("Comprehensive data collection service information would be displayed here."),
dmc.Divider(),
dmc.Title("Configuration", order=6),
dmc.Text("Service configuration details..."),
dmc.Title("Performance Metrics", order=6),
dmc.Text("Detailed performance analytics..."),
dmc.Title("Health Status", order=6),
dmc.Text("Individual collector health information...")
], gap="md")
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 dmc.Alert(
f"Error: {str(e)}",
title="🔴 Error Loading Details",
color="red",
variant="light"
)
return dbc.Alert(f"Error loading details: {e}", color="danger")
def _get_collection_logs_content() -> str: