Removed Mantine for UI, and used bootstrap for simplicity
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user