323 lines
9.2 KiB
Python
323 lines
9.2 KiB
Python
|
|
"""
|
||
|
|
Dashboard Layout Components
|
||
|
|
|
||
|
|
This module contains reusable layout components for the main dashboard interface.
|
||
|
|
These components handle the overall structure and navigation of the dashboard.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from dash import html, dcc
|
||
|
|
from typing import List, Dict, Any, Optional
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
|
||
|
|
def create_header(title: str = "Crypto Trading Bot Dashboard",
|
||
|
|
subtitle: str = "Real-time monitoring and bot management") -> html.Div:
|
||
|
|
"""
|
||
|
|
Create the main dashboard header component.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
title: Main title text
|
||
|
|
subtitle: Subtitle text
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for the header
|
||
|
|
"""
|
||
|
|
return html.Div([
|
||
|
|
html.H1(f"🚀 {title}",
|
||
|
|
style={'margin': '0', 'color': '#2c3e50', 'font-size': '28px'}),
|
||
|
|
html.P(subtitle,
|
||
|
|
style={'margin': '5px 0 0 0', 'color': '#7f8c8d', 'font-size': '14px'})
|
||
|
|
], style={
|
||
|
|
'padding': '20px',
|
||
|
|
'background-color': '#ecf0f1',
|
||
|
|
'border-bottom': '2px solid #bdc3c7',
|
||
|
|
'box-shadow': '0 2px 4px rgba(0,0,0,0.1)'
|
||
|
|
})
|
||
|
|
|
||
|
|
|
||
|
|
def create_navigation_tabs(active_tab: str = 'market-data') -> dcc.Tabs:
|
||
|
|
"""
|
||
|
|
Create the main navigation tabs component.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
active_tab: Default active tab
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash Tabs component
|
||
|
|
"""
|
||
|
|
tab_style = {
|
||
|
|
'borderBottom': '1px solid #d6d6d6',
|
||
|
|
'padding': '6px',
|
||
|
|
'fontWeight': 'bold'
|
||
|
|
}
|
||
|
|
|
||
|
|
tab_selected_style = {
|
||
|
|
'borderTop': '1px solid #d6d6d6',
|
||
|
|
'borderBottom': '1px solid #d6d6d6',
|
||
|
|
'backgroundColor': '#119DFF',
|
||
|
|
'color': 'white',
|
||
|
|
'padding': '6px'
|
||
|
|
}
|
||
|
|
|
||
|
|
return dcc.Tabs(
|
||
|
|
id="main-tabs",
|
||
|
|
value=active_tab,
|
||
|
|
children=[
|
||
|
|
dcc.Tab(
|
||
|
|
label='📊 Market Data',
|
||
|
|
value='market-data',
|
||
|
|
style=tab_style,
|
||
|
|
selected_style=tab_selected_style
|
||
|
|
),
|
||
|
|
dcc.Tab(
|
||
|
|
label='🤖 Bot Management',
|
||
|
|
value='bot-management',
|
||
|
|
style=tab_style,
|
||
|
|
selected_style=tab_selected_style
|
||
|
|
),
|
||
|
|
dcc.Tab(
|
||
|
|
label='📈 Performance',
|
||
|
|
value='performance',
|
||
|
|
style=tab_style,
|
||
|
|
selected_style=tab_selected_style
|
||
|
|
),
|
||
|
|
dcc.Tab(
|
||
|
|
label='⚙️ System Health',
|
||
|
|
value='system-health',
|
||
|
|
style=tab_style,
|
||
|
|
selected_style=tab_selected_style
|
||
|
|
),
|
||
|
|
],
|
||
|
|
style={'margin': '10px 20px'}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def create_content_container(content_id: str = 'tab-content') -> html.Div:
|
||
|
|
"""
|
||
|
|
Create the main content container.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
content_id: HTML element ID for the content area
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for content container
|
||
|
|
"""
|
||
|
|
return html.Div(
|
||
|
|
id=content_id,
|
||
|
|
style={
|
||
|
|
'padding': '20px',
|
||
|
|
'min-height': '600px',
|
||
|
|
'background-color': '#ffffff'
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def create_status_indicator(status: str, message: str,
|
||
|
|
timestamp: Optional[datetime] = None) -> html.Div:
|
||
|
|
"""
|
||
|
|
Create a status indicator component.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
status: Status type ('connected', 'error', 'warning', 'info')
|
||
|
|
message: Status message
|
||
|
|
timestamp: Optional timestamp for the status
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for status indicator
|
||
|
|
"""
|
||
|
|
status_colors = {
|
||
|
|
'connected': '#27ae60',
|
||
|
|
'error': '#e74c3c',
|
||
|
|
'warning': '#f39c12',
|
||
|
|
'info': '#3498db'
|
||
|
|
}
|
||
|
|
|
||
|
|
status_icons = {
|
||
|
|
'connected': '🟢',
|
||
|
|
'error': '🔴',
|
||
|
|
'warning': '🟡',
|
||
|
|
'info': '🔵'
|
||
|
|
}
|
||
|
|
|
||
|
|
color = status_colors.get(status, '#7f8c8d')
|
||
|
|
icon = status_icons.get(status, '⚪')
|
||
|
|
|
||
|
|
components = [
|
||
|
|
html.Span(f"{icon} {message}",
|
||
|
|
style={'color': color, 'font-weight': 'bold'})
|
||
|
|
]
|
||
|
|
|
||
|
|
if timestamp:
|
||
|
|
components.append(
|
||
|
|
html.P(f"Last updated: {timestamp.strftime('%H:%M:%S')}",
|
||
|
|
style={'margin': '5px 0', 'color': '#7f8c8d', 'font-size': '12px'})
|
||
|
|
)
|
||
|
|
|
||
|
|
return html.Div(components)
|
||
|
|
|
||
|
|
|
||
|
|
def create_card(title: str, content: Any,
|
||
|
|
card_id: Optional[str] = None) -> html.Div:
|
||
|
|
"""
|
||
|
|
Create a card component for organizing content.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
title: Card title
|
||
|
|
content: Card content (can be any Dash component)
|
||
|
|
card_id: Optional HTML element ID
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for the card
|
||
|
|
"""
|
||
|
|
return html.Div([
|
||
|
|
html.H3(title, style={
|
||
|
|
'margin': '0 0 15px 0',
|
||
|
|
'color': '#2c3e50',
|
||
|
|
'border-bottom': '2px solid #ecf0f1',
|
||
|
|
'padding-bottom': '10px'
|
||
|
|
}),
|
||
|
|
content
|
||
|
|
], style={
|
||
|
|
'border': '1px solid #ddd',
|
||
|
|
'border-radius': '8px',
|
||
|
|
'padding': '20px',
|
||
|
|
'margin': '10px 0',
|
||
|
|
'background-color': '#ffffff',
|
||
|
|
'box-shadow': '0 2px 4px rgba(0,0,0,0.1)'
|
||
|
|
}, id=card_id)
|
||
|
|
|
||
|
|
|
||
|
|
def create_metric_display(metrics: Dict[str, str]) -> html.Div:
|
||
|
|
"""
|
||
|
|
Create a metrics display component.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
metrics: Dictionary of metric names and values
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for metrics display
|
||
|
|
"""
|
||
|
|
metric_components = []
|
||
|
|
|
||
|
|
for key, value in metrics.items():
|
||
|
|
# Color coding for percentage changes
|
||
|
|
color = '#27ae60' if '+' in str(value) else '#e74c3c' if '-' in str(value) else '#2c3e50'
|
||
|
|
|
||
|
|
metric_components.append(
|
||
|
|
html.Div([
|
||
|
|
html.Strong(f"{key}: ", style={'color': '#2c3e50'}),
|
||
|
|
html.Span(str(value), style={'color': color})
|
||
|
|
], style={
|
||
|
|
'margin': '8px 0',
|
||
|
|
'padding': '5px',
|
||
|
|
'background-color': '#f8f9fa',
|
||
|
|
'border-radius': '4px'
|
||
|
|
})
|
||
|
|
)
|
||
|
|
|
||
|
|
return html.Div(metric_components, style={
|
||
|
|
'display': 'grid',
|
||
|
|
'grid-template-columns': 'repeat(auto-fit, minmax(200px, 1fr))',
|
||
|
|
'gap': '10px'
|
||
|
|
})
|
||
|
|
|
||
|
|
|
||
|
|
def create_selector_group(selectors: List[Dict[str, Any]]) -> html.Div:
|
||
|
|
"""
|
||
|
|
Create a group of selector components (dropdowns, etc.).
|
||
|
|
|
||
|
|
Args:
|
||
|
|
selectors: List of selector configurations
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for selector group
|
||
|
|
"""
|
||
|
|
selector_components = []
|
||
|
|
|
||
|
|
for selector in selectors:
|
||
|
|
selector_div = html.Div([
|
||
|
|
html.Label(
|
||
|
|
selector.get('label', ''),
|
||
|
|
style={'font-weight': 'bold', 'margin-bottom': '5px', 'display': 'block'}
|
||
|
|
),
|
||
|
|
dcc.Dropdown(
|
||
|
|
id=selector.get('id'),
|
||
|
|
options=selector.get('options', []),
|
||
|
|
value=selector.get('value'),
|
||
|
|
style={'margin-bottom': '15px'}
|
||
|
|
)
|
||
|
|
], style={'width': '250px', 'margin': '10px 20px 10px 0', 'display': 'inline-block'})
|
||
|
|
|
||
|
|
selector_components.append(selector_div)
|
||
|
|
|
||
|
|
return html.Div(selector_components, style={'margin': '20px 0'})
|
||
|
|
|
||
|
|
|
||
|
|
def create_loading_component(component_id: str, message: str = "Loading...") -> html.Div:
|
||
|
|
"""
|
||
|
|
Create a loading component for async operations.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
component_id: ID for the component that will replace this loading screen
|
||
|
|
message: Loading message
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for loading screen
|
||
|
|
"""
|
||
|
|
return html.Div([
|
||
|
|
html.Div([
|
||
|
|
html.Div(className="loading-spinner", style={
|
||
|
|
'border': '4px solid #f3f3f3',
|
||
|
|
'border-top': '4px solid #3498db',
|
||
|
|
'border-radius': '50%',
|
||
|
|
'width': '40px',
|
||
|
|
'height': '40px',
|
||
|
|
'animation': 'spin 2s linear infinite',
|
||
|
|
'margin': '0 auto 20px auto'
|
||
|
|
}),
|
||
|
|
html.P(message, style={'text-align': 'center', 'color': '#7f8c8d'})
|
||
|
|
], style={
|
||
|
|
'display': 'flex',
|
||
|
|
'flex-direction': 'column',
|
||
|
|
'align-items': 'center',
|
||
|
|
'justify-content': 'center',
|
||
|
|
'height': '200px'
|
||
|
|
})
|
||
|
|
], id=component_id)
|
||
|
|
|
||
|
|
|
||
|
|
def create_placeholder_content(title: str, description: str,
|
||
|
|
phase: str = "future implementation") -> html.Div:
|
||
|
|
"""
|
||
|
|
Create placeholder content for features not yet implemented.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
title: Section title
|
||
|
|
description: Description of what will be implemented
|
||
|
|
phase: Implementation phase information
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dash HTML component for placeholder content
|
||
|
|
"""
|
||
|
|
return html.Div([
|
||
|
|
html.H2(title, style={'color': '#2c3e50'}),
|
||
|
|
html.Div([
|
||
|
|
html.P(description, style={'color': '#7f8c8d', 'font-size': '16px'}),
|
||
|
|
html.P(f"🚧 Planned for {phase}",
|
||
|
|
style={'color': '#f39c12', 'font-weight': 'bold', 'font-style': 'italic'})
|
||
|
|
], style={
|
||
|
|
'background-color': '#f8f9fa',
|
||
|
|
'padding': '20px',
|
||
|
|
'border-radius': '8px',
|
||
|
|
'border-left': '4px solid #f39c12'
|
||
|
|
})
|
||
|
|
])
|
||
|
|
|
||
|
|
|
||
|
|
# CSS Styles for animation (to be included in assets or inline styles)
|
||
|
|
LOADING_CSS = """
|
||
|
|
@keyframes spin {
|
||
|
|
0% { transform: rotate(0deg); }
|
||
|
|
100% { transform: rotate(360deg); }
|
||
|
|
}
|
||
|
|
"""
|