|
|
""" |
|
|
Cryptocurrency API Monitor - Gradio Application |
|
|
Production-ready monitoring dashboard for Hugging Face Spaces |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
import pandas as pd |
|
|
import plotly.graph_objects as go |
|
|
import plotly.express as px |
|
|
from datetime import datetime, timedelta |
|
|
import asyncio |
|
|
import time |
|
|
import logging |
|
|
from typing import List, Dict, Optional |
|
|
import json |
|
|
|
|
|
|
|
|
from config import config |
|
|
from monitor import APIMonitor, HealthStatus, HealthCheckResult |
|
|
from database import Database |
|
|
from scheduler import BackgroundScheduler |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
db = Database() |
|
|
monitor = APIMonitor(config) |
|
|
scheduler = BackgroundScheduler(monitor, db, interval_minutes=5) |
|
|
|
|
|
|
|
|
current_results = [] |
|
|
last_check_time = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_dashboard(category_filter="All", status_filter="All", tier_filter="All"): |
|
|
"""Refresh the main dashboard with filters""" |
|
|
global current_results, last_check_time |
|
|
|
|
|
try: |
|
|
|
|
|
logger.info("Running health checks...") |
|
|
current_results = asyncio.run(monitor.check_all()) |
|
|
last_check_time = datetime.now() |
|
|
|
|
|
|
|
|
db.save_health_checks(current_results) |
|
|
|
|
|
|
|
|
filtered_results = current_results |
|
|
|
|
|
if category_filter != "All": |
|
|
filtered_results = [r for r in filtered_results if r.category == category_filter] |
|
|
|
|
|
if status_filter != "All": |
|
|
filtered_results = [r for r in filtered_results if r.status.value == status_filter.lower()] |
|
|
|
|
|
if tier_filter != "All": |
|
|
tier_num = int(tier_filter.split()[1]) |
|
|
tier_resources = config.get_by_tier(tier_num) |
|
|
tier_names = [r['name'] for r in tier_resources] |
|
|
filtered_results = [r for r in filtered_results if r.provider_name in tier_names] |
|
|
|
|
|
|
|
|
df_data = [] |
|
|
for result in filtered_results: |
|
|
df_data.append({ |
|
|
'Status': f"{result.get_badge()} {result.status.value.upper()}", |
|
|
'Provider': result.provider_name, |
|
|
'Category': result.category, |
|
|
'Response Time': f"{result.response_time:.0f} ms", |
|
|
'Last Check': datetime.fromtimestamp(result.timestamp).strftime('%H:%M:%S'), |
|
|
'Code': result.status_code or 'N/A' |
|
|
}) |
|
|
|
|
|
df = pd.DataFrame(df_data) |
|
|
|
|
|
|
|
|
stats = monitor.get_summary_stats(current_results) |
|
|
|
|
|
|
|
|
summary_html = f""" |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 20px;"> |
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 10px; color: white;"> |
|
|
<h3 style="margin: 0;">📊 Total APIs</h3> |
|
|
<p style="font-size: 32px; margin: 10px 0 0 0; font-weight: bold;">{stats['total']}</p> |
|
|
</div> |
|
|
<div style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); padding: 20px; border-radius: 10px; color: white;"> |
|
|
<h3 style="margin: 0;">✅ Online %</h3> |
|
|
<p style="font-size: 32px; margin: 10px 0 0 0; font-weight: bold;">{stats['online_percentage']}%</p> |
|
|
</div> |
|
|
<div style="background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%); padding: 20px; border-radius: 10px; color: white;"> |
|
|
<h3 style="margin: 0;">⚠️ Critical Issues</h3> |
|
|
<p style="font-size: 32px; margin: 10px 0 0 0; font-weight: bold;">{stats['critical_issues']}</p> |
|
|
</div> |
|
|
<div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 20px; border-radius: 10px; color: white;"> |
|
|
<h3 style="margin: 0;">⚡ Avg Response</h3> |
|
|
<p style="font-size: 32px; margin: 10px 0 0 0; font-weight: bold;">{stats['avg_response_time']:.0f} ms</p> |
|
|
</div> |
|
|
</div> |
|
|
<p style="text-align: center; color: #666;">Last updated: {last_check_time.strftime('%Y-%m-%d %H:%M:%S')}</p> |
|
|
""" |
|
|
|
|
|
return df, summary_html |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error refreshing dashboard: {e}") |
|
|
return pd.DataFrame(), f"<p style='color: red;'>Error: {str(e)}</p>" |
|
|
|
|
|
|
|
|
def export_current_status(): |
|
|
"""Export current status to CSV""" |
|
|
global current_results |
|
|
|
|
|
if not current_results: |
|
|
return None |
|
|
|
|
|
try: |
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') |
|
|
filename = f"api_status_{timestamp}.csv" |
|
|
filepath = f"data/{filename}" |
|
|
|
|
|
df_data = [] |
|
|
for result in current_results: |
|
|
df_data.append({ |
|
|
'Provider': result.provider_name, |
|
|
'Category': result.category, |
|
|
'Status': result.status.value, |
|
|
'Response_Time_ms': result.response_time, |
|
|
'Status_Code': result.status_code, |
|
|
'Error': result.error_message or '', |
|
|
'Timestamp': datetime.fromtimestamp(result.timestamp).isoformat() |
|
|
}) |
|
|
|
|
|
df = pd.DataFrame(df_data) |
|
|
df.to_csv(filepath, index=False) |
|
|
|
|
|
return filepath |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error exporting: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_category_overview(): |
|
|
"""Get overview of all categories""" |
|
|
global current_results |
|
|
|
|
|
if not current_results: |
|
|
return "No data available. Please refresh the dashboard first." |
|
|
|
|
|
category_stats = monitor.get_category_stats(current_results) |
|
|
|
|
|
html_output = "<div style='padding: 20px;'>" |
|
|
|
|
|
for category, stats in category_stats.items(): |
|
|
online_pct = stats['online_percentage'] |
|
|
|
|
|
|
|
|
if online_pct >= 80: |
|
|
color = "#4CAF50" |
|
|
elif online_pct >= 50: |
|
|
color = "#FF9800" |
|
|
else: |
|
|
color = "#F44336" |
|
|
|
|
|
html_output += f""" |
|
|
<div style="margin-bottom: 30px; border: 2px solid {color}; border-radius: 10px; padding: 20px; background: #f9f9f9;"> |
|
|
<h2 style="margin-top: 0; color: {color};">📁 {category}</h2> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;"> |
|
|
<div> |
|
|
<strong>Total:</strong> {stats['total']} |
|
|
</div> |
|
|
<div> |
|
|
<strong>🟢 Online:</strong> {stats['online']} |
|
|
</div> |
|
|
<div> |
|
|
<strong>🟡 Degraded:</strong> {stats['degraded']} |
|
|
</div> |
|
|
<div> |
|
|
<strong>🔴 Offline:</strong> {stats['offline']} |
|
|
</div> |
|
|
<div> |
|
|
<strong>Availability:</strong> {online_pct}% |
|
|
</div> |
|
|
<div> |
|
|
<strong>Avg Response:</strong> {stats['avg_response_time']:.0f} ms |
|
|
</div> |
|
|
</div> |
|
|
<div style="margin-top: 15px; background: #e0e0e0; border-radius: 5px; height: 25px; overflow: hidden;"> |
|
|
<div style="background: {color}; height: 100%; width: {online_pct}%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;"> |
|
|
{online_pct}% |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
html_output += "</div>" |
|
|
|
|
|
return html_output |
|
|
|
|
|
|
|
|
def get_category_chart(): |
|
|
"""Create category availability chart""" |
|
|
global current_results |
|
|
|
|
|
if not current_results: |
|
|
return go.Figure() |
|
|
|
|
|
category_stats = monitor.get_category_stats(current_results) |
|
|
|
|
|
categories = list(category_stats.keys()) |
|
|
online_pcts = [stats['online_percentage'] for stats in category_stats.values()] |
|
|
avg_times = [stats['avg_response_time'] for stats in category_stats.values()] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
fig.add_trace(go.Bar( |
|
|
name='Availability %', |
|
|
x=categories, |
|
|
y=online_pcts, |
|
|
marker_color='lightblue', |
|
|
text=[f"{pct:.1f}%" for pct in online_pcts], |
|
|
textposition='auto', |
|
|
yaxis='y1' |
|
|
)) |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
name='Avg Response Time (ms)', |
|
|
x=categories, |
|
|
y=avg_times, |
|
|
mode='lines+markers', |
|
|
marker=dict(size=10, color='red'), |
|
|
line=dict(width=2, color='red'), |
|
|
yaxis='y2' |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title='Category Health Overview', |
|
|
xaxis=dict(title='Category'), |
|
|
yaxis=dict(title='Availability %', side='left', range=[0, 100]), |
|
|
yaxis2=dict(title='Response Time (ms)', side='right', overlaying='y'), |
|
|
hovermode='x unified', |
|
|
template='plotly_white', |
|
|
height=500 |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_uptime_chart(provider_name=None, hours=24): |
|
|
"""Get uptime chart for provider(s)""" |
|
|
try: |
|
|
|
|
|
status_data = db.get_recent_status(provider_name=provider_name, hours=hours) |
|
|
|
|
|
if not status_data: |
|
|
fig = go.Figure() |
|
|
fig.add_annotation( |
|
|
text="No historical data available. Data will accumulate over time.", |
|
|
xref="paper", yref="paper", |
|
|
x=0.5, y=0.5, showarrow=False, |
|
|
font=dict(size=16) |
|
|
) |
|
|
return fig |
|
|
|
|
|
|
|
|
df = pd.DataFrame(status_data) |
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s') |
|
|
df['uptime_value'] = df['status'].apply(lambda x: 100 if x == 'online' else 0) |
|
|
|
|
|
|
|
|
if provider_name: |
|
|
providers = [provider_name] |
|
|
else: |
|
|
providers = df['provider_name'].unique()[:10] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
for provider in providers: |
|
|
provider_df = df[df['provider_name'] == provider] |
|
|
|
|
|
|
|
|
provider_df = provider_df.set_index('timestamp') |
|
|
resampled = provider_df['uptime_value'].resample('1H').mean() |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
name=provider, |
|
|
x=resampled.index, |
|
|
y=resampled.values, |
|
|
mode='lines+markers', |
|
|
line=dict(width=2), |
|
|
marker=dict(size=6) |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title=f'Uptime History - Last {hours} Hours', |
|
|
xaxis_title='Time', |
|
|
yaxis_title='Uptime %', |
|
|
hovermode='x unified', |
|
|
template='plotly_white', |
|
|
height=500, |
|
|
yaxis=dict(range=[0, 105]) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error creating uptime chart: {e}") |
|
|
fig = go.Figure() |
|
|
fig.add_annotation( |
|
|
text=f"Error: {str(e)}", |
|
|
xref="paper", yref="paper", |
|
|
x=0.5, y=0.5, showarrow=False |
|
|
) |
|
|
return fig |
|
|
|
|
|
|
|
|
def get_response_time_chart(provider_name=None, hours=24): |
|
|
"""Get response time trends""" |
|
|
try: |
|
|
status_data = db.get_recent_status(provider_name=provider_name, hours=hours) |
|
|
|
|
|
if not status_data: |
|
|
return go.Figure() |
|
|
|
|
|
df = pd.DataFrame(status_data) |
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s') |
|
|
|
|
|
if provider_name: |
|
|
providers = [provider_name] |
|
|
else: |
|
|
providers = df['provider_name'].unique()[:10] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
for provider in providers: |
|
|
provider_df = df[df['provider_name'] == provider] |
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
name=provider, |
|
|
x=provider_df['timestamp'], |
|
|
y=provider_df['response_time'], |
|
|
mode='lines', |
|
|
line=dict(width=2) |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title=f'Response Time Trends - Last {hours} Hours', |
|
|
xaxis_title='Time', |
|
|
yaxis_title='Response Time (ms)', |
|
|
hovermode='x unified', |
|
|
template='plotly_white', |
|
|
height=500 |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error creating response time chart: {e}") |
|
|
return go.Figure() |
|
|
|
|
|
|
|
|
def get_incident_log(hours=24): |
|
|
"""Get incident log""" |
|
|
try: |
|
|
incidents = db.get_incident_history(hours=hours) |
|
|
|
|
|
if not incidents: |
|
|
return pd.DataFrame({'Message': ['No incidents in the selected period']}) |
|
|
|
|
|
df_data = [] |
|
|
for incident in incidents: |
|
|
df_data.append({ |
|
|
'Timestamp': incident['start_time'], |
|
|
'Provider': incident['provider_name'], |
|
|
'Category': incident['category'], |
|
|
'Type': incident['incident_type'], |
|
|
'Severity': incident['severity'], |
|
|
'Description': incident['description'], |
|
|
'Duration': f"{incident.get('duration_seconds', 0)} sec" if incident.get('resolved') else 'Ongoing', |
|
|
'Status': '✅ Resolved' if incident.get('resolved') else '⚠️ Active' |
|
|
}) |
|
|
|
|
|
return pd.DataFrame(df_data) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error getting incident log: {e}") |
|
|
return pd.DataFrame({'Error': [str(e)]}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_endpoint(provider_name, custom_endpoint="", use_proxy=False): |
|
|
"""Test a specific endpoint""" |
|
|
try: |
|
|
resources = config.get_all_resources() |
|
|
resource = next((r for r in resources if r['name'] == provider_name), None) |
|
|
|
|
|
if not resource: |
|
|
return "Provider not found", "" |
|
|
|
|
|
|
|
|
if custom_endpoint: |
|
|
resource = resource.copy() |
|
|
resource['endpoint'] = custom_endpoint |
|
|
|
|
|
|
|
|
result = asyncio.run(monitor.check_endpoint(resource, use_proxy=use_proxy)) |
|
|
|
|
|
|
|
|
status_emoji = result.get_badge() |
|
|
status_text = f""" |
|
|
## Test Results |
|
|
|
|
|
**Provider:** {result.provider_name} |
|
|
**Status:** {status_emoji} {result.status.value.upper()} |
|
|
**Response Time:** {result.response_time:.2f} ms |
|
|
**Status Code:** {result.status_code or 'N/A'} |
|
|
**Endpoint:** `{result.endpoint_tested}` |
|
|
|
|
|
### Details |
|
|
""" |
|
|
|
|
|
if result.error_message: |
|
|
status_text += f"\n**Error:** {result.error_message}\n" |
|
|
else: |
|
|
status_text += "\n✅ Request successful\n" |
|
|
|
|
|
|
|
|
if result.status != HealthStatus.ONLINE: |
|
|
status_text += "\n### Troubleshooting Hints\n" |
|
|
if result.status_code == 403: |
|
|
status_text += "- Check API key validity\n- Verify rate limits\n- Try using CORS proxy\n" |
|
|
elif result.status_code == 429: |
|
|
status_text += "- Rate limit exceeded\n- Wait before retrying\n- Consider using backup provider\n" |
|
|
elif result.error_message and "timeout" in result.error_message.lower(): |
|
|
status_text += "- Connection timeout\n- Service may be slow or down\n- Try increasing timeout\n" |
|
|
else: |
|
|
status_text += "- Verify endpoint URL\n- Check network connectivity\n- Review API documentation\n" |
|
|
|
|
|
return status_text, json.dumps(result.to_dict(), indent=2) |
|
|
|
|
|
except Exception as e: |
|
|
return f"Error testing endpoint: {str(e)}", "" |
|
|
|
|
|
|
|
|
def get_example_query(provider_name): |
|
|
"""Get example query for a provider""" |
|
|
resources = config.get_all_resources() |
|
|
resource = next((r for r in resources if r['name'] == provider_name), None) |
|
|
|
|
|
if not resource: |
|
|
return "" |
|
|
|
|
|
example = resource.get('example', '') |
|
|
if example: |
|
|
return f"Example:\n{example}" |
|
|
|
|
|
|
|
|
endpoint = resource.get('endpoint', '') |
|
|
url = resource.get('url', '') |
|
|
|
|
|
if endpoint: |
|
|
return f"Example URL:\n{url}{endpoint}" |
|
|
|
|
|
return f"Base URL:\n{url}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_refresh_interval(interval_minutes): |
|
|
"""Update background refresh interval""" |
|
|
try: |
|
|
scheduler.update_interval(interval_minutes) |
|
|
return f"✅ Refresh interval updated to {interval_minutes} minutes" |
|
|
except Exception as e: |
|
|
return f"❌ Error: {str(e)}" |
|
|
|
|
|
|
|
|
def clear_all_cache(): |
|
|
"""Clear all caches""" |
|
|
try: |
|
|
monitor.clear_cache() |
|
|
return "✅ Cache cleared successfully" |
|
|
except Exception as e: |
|
|
return f"❌ Error: {str(e)}" |
|
|
|
|
|
|
|
|
def get_config_info(): |
|
|
"""Get configuration information""" |
|
|
stats = config.stats() |
|
|
|
|
|
info = f""" |
|
|
## Configuration Overview |
|
|
|
|
|
**Total API Resources:** {stats['total_resources']} |
|
|
**Categories:** {stats['total_categories']} |
|
|
**Free Resources:** {stats['free_resources']} |
|
|
**Tier 1 (Critical):** {stats['tier1_count']} |
|
|
**Tier 2 (Important):** {stats['tier2_count']} |
|
|
**Tier 3 (Others):** {stats['tier3_count']} |
|
|
**Configured API Keys:** {stats['api_keys_count']} |
|
|
**CORS Proxies:** {stats['cors_proxies_count']} |
|
|
|
|
|
### Categories |
|
|
{', '.join(stats['categories'])} |
|
|
|
|
|
### Scheduler Status |
|
|
**Running:** {scheduler.is_running()} |
|
|
**Interval:** {scheduler.interval_minutes} minutes |
|
|
**Last Run:** {scheduler.last_run_time.strftime('%Y-%m-%d %H:%M:%S') if scheduler.last_run_time else 'Never'} |
|
|
""" |
|
|
|
|
|
return info |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_interface(): |
|
|
"""Build the complete Gradio interface""" |
|
|
|
|
|
with gr.Blocks( |
|
|
theme=gr.themes.Soft(primary_hue="purple", secondary_hue="blue"), |
|
|
title="Crypto API Monitor", |
|
|
css=""" |
|
|
.gradio-container { |
|
|
max-width: 1400px !important; |
|
|
} |
|
|
""" |
|
|
) as app: |
|
|
|
|
|
gr.Markdown(""" |
|
|
# 📊 Cryptocurrency API Monitor |
|
|
### Real-time health monitoring for 162+ crypto API endpoints |
|
|
*Production-ready | Auto-refreshing | Persistent metrics | Multi-tier monitoring* |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Tab("📊 Real-Time Dashboard"): |
|
|
with gr.Row(): |
|
|
refresh_btn = gr.Button("🔄 Refresh Now", variant="primary", size="lg") |
|
|
export_btn = gr.Button("💾 Export CSV", size="lg") |
|
|
|
|
|
with gr.Row(): |
|
|
category_filter = gr.Dropdown( |
|
|
choices=["All"] + config.get_categories(), |
|
|
value="All", |
|
|
label="Filter by Category" |
|
|
) |
|
|
status_filter = gr.Dropdown( |
|
|
choices=["All", "Online", "Degraded", "Offline"], |
|
|
value="All", |
|
|
label="Filter by Status" |
|
|
) |
|
|
tier_filter = gr.Dropdown( |
|
|
choices=["All", "Tier 1", "Tier 2", "Tier 3"], |
|
|
value="All", |
|
|
label="Filter by Tier" |
|
|
) |
|
|
|
|
|
summary_cards = gr.HTML() |
|
|
status_table = gr.DataFrame( |
|
|
headers=["Status", "Provider", "Category", "Response Time", "Last Check", "Code"], |
|
|
wrap=True |
|
|
) |
|
|
download_file = gr.File(label="Download CSV", visible=False) |
|
|
|
|
|
refresh_btn.click( |
|
|
fn=refresh_dashboard, |
|
|
inputs=[category_filter, status_filter, tier_filter], |
|
|
outputs=[status_table, summary_cards] |
|
|
) |
|
|
|
|
|
export_btn.click( |
|
|
fn=export_current_status, |
|
|
outputs=download_file |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("📁 Category View"): |
|
|
gr.Markdown("### API Resources by Category") |
|
|
|
|
|
with gr.Row(): |
|
|
refresh_cat_btn = gr.Button("🔄 Refresh Categories", variant="primary") |
|
|
|
|
|
category_overview = gr.HTML() |
|
|
category_chart = gr.Plot() |
|
|
|
|
|
refresh_cat_btn.click( |
|
|
fn=get_category_overview, |
|
|
outputs=category_overview |
|
|
) |
|
|
|
|
|
refresh_cat_btn.click( |
|
|
fn=get_category_chart, |
|
|
outputs=category_chart |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("📈 Health History"): |
|
|
gr.Markdown("### Historical Performance & Incidents") |
|
|
|
|
|
with gr.Row(): |
|
|
history_provider = gr.Dropdown( |
|
|
choices=["All"] + [r['name'] for r in config.get_all_resources()], |
|
|
value="All", |
|
|
label="Select Provider" |
|
|
) |
|
|
history_hours = gr.Slider( |
|
|
minimum=1, |
|
|
maximum=168, |
|
|
value=24, |
|
|
step=1, |
|
|
label="Time Range (hours)" |
|
|
) |
|
|
refresh_history_btn = gr.Button("🔄 Refresh", variant="primary") |
|
|
|
|
|
uptime_chart = gr.Plot(label="Uptime History") |
|
|
response_chart = gr.Plot(label="Response Time Trends") |
|
|
incident_table = gr.DataFrame(label="Incident Log") |
|
|
|
|
|
def update_history(provider, hours): |
|
|
prov = None if provider == "All" else provider |
|
|
uptime = get_uptime_chart(prov, hours) |
|
|
response = get_response_time_chart(prov, hours) |
|
|
incidents = get_incident_log(hours) |
|
|
return uptime, response, incidents |
|
|
|
|
|
refresh_history_btn.click( |
|
|
fn=update_history, |
|
|
inputs=[history_provider, history_hours], |
|
|
outputs=[uptime_chart, response_chart, incident_table] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("🔧 Test Endpoint"): |
|
|
gr.Markdown("### Test Individual API Endpoints") |
|
|
|
|
|
with gr.Row(): |
|
|
test_provider = gr.Dropdown( |
|
|
choices=[r['name'] for r in config.get_all_resources()], |
|
|
label="Select Provider" |
|
|
) |
|
|
test_btn = gr.Button("▶️ Run Test", variant="primary") |
|
|
|
|
|
with gr.Row(): |
|
|
custom_endpoint = gr.Textbox( |
|
|
label="Custom Endpoint (optional)", |
|
|
placeholder="/api/endpoint" |
|
|
) |
|
|
use_proxy_check = gr.Checkbox(label="Use CORS Proxy", value=False) |
|
|
|
|
|
example_query = gr.Markdown() |
|
|
test_result = gr.Markdown() |
|
|
test_json = gr.Code(label="JSON Response", language="json") |
|
|
|
|
|
test_provider.change( |
|
|
fn=get_example_query, |
|
|
inputs=test_provider, |
|
|
outputs=example_query |
|
|
) |
|
|
|
|
|
test_btn.click( |
|
|
fn=test_endpoint, |
|
|
inputs=[test_provider, custom_endpoint, use_proxy_check], |
|
|
outputs=[test_result, test_json] |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("⚙️ Configuration"): |
|
|
gr.Markdown("### System Configuration & Settings") |
|
|
|
|
|
config_info = gr.Markdown() |
|
|
|
|
|
with gr.Row(): |
|
|
refresh_interval = gr.Slider( |
|
|
minimum=1, |
|
|
maximum=60, |
|
|
value=5, |
|
|
step=1, |
|
|
label="Auto-refresh Interval (minutes)" |
|
|
) |
|
|
update_interval_btn = gr.Button("💾 Update Interval") |
|
|
|
|
|
interval_status = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
with gr.Row(): |
|
|
clear_cache_btn = gr.Button("🗑️ Clear Cache") |
|
|
cache_status = gr.Textbox(label="Cache Status", interactive=False) |
|
|
|
|
|
gr.Markdown("### API Keys Management") |
|
|
gr.Markdown(""" |
|
|
API keys are loaded from environment variables in Hugging Face Spaces. |
|
|
Go to **Settings > Repository secrets** to add keys: |
|
|
- `ETHERSCAN_KEY` |
|
|
- `BSCSCAN_KEY` |
|
|
- `TRONSCAN_KEY` |
|
|
- `CMC_KEY` (CoinMarketCap) |
|
|
- `CRYPTOCOMPARE_KEY` |
|
|
""") |
|
|
|
|
|
|
|
|
app.load(fn=get_config_info, outputs=config_info) |
|
|
|
|
|
update_interval_btn.click( |
|
|
fn=update_refresh_interval, |
|
|
inputs=refresh_interval, |
|
|
outputs=interval_status |
|
|
) |
|
|
|
|
|
clear_cache_btn.click( |
|
|
fn=clear_all_cache, |
|
|
outputs=cache_status |
|
|
) |
|
|
|
|
|
|
|
|
app.load( |
|
|
fn=refresh_dashboard, |
|
|
inputs=[category_filter, status_filter, tier_filter], |
|
|
outputs=[status_table, summary_cards] |
|
|
) |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
logger.info("Starting Crypto API Monitor...") |
|
|
|
|
|
|
|
|
scheduler.start() |
|
|
|
|
|
|
|
|
app = build_interface() |
|
|
|
|
|
|
|
|
app.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
show_error=True |
|
|
) |
|
|
|