File size: 26,111 Bytes
d6d843f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
"""
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

# Import local modules
from config import config
from monitor import APIMonitor, HealthStatus, HealthCheckResult
from database import Database
from scheduler import BackgroundScheduler

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Global instances
db = Database()
monitor = APIMonitor(config)
scheduler = BackgroundScheduler(monitor, db, interval_minutes=5)

# Global state for UI
current_results = []
last_check_time = None


# =============================================================================
# TAB 1: Real-Time Dashboard
# =============================================================================

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:
        # Run health checks
        logger.info("Running health checks...")
        current_results = asyncio.run(monitor.check_all())
        last_check_time = datetime.now()

        # Save to database
        db.save_health_checks(current_results)

        # Apply filters
        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]

        # Create DataFrame
        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)

        # Calculate summary stats
        stats = monitor.get_summary_stats(current_results)

        # Build summary cards HTML
        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


# =============================================================================
# TAB 2: Category View
# =============================================================================

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']

        # Color based on health
        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


# =============================================================================
# TAB 3: Health History
# =============================================================================

def get_uptime_chart(provider_name=None, hours=24):
    """Get uptime chart for provider(s)"""
    try:
        # Get data from database
        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

        # Convert to DataFrame
        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)

        # Group by provider and time
        if provider_name:
            providers = [provider_name]
        else:
            providers = df['provider_name'].unique()[:10]  # Limit to 10 providers

        fig = go.Figure()

        for provider in providers:
            provider_df = df[df['provider_name'] == provider]

            # Resample to hourly average
            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)]})


# =============================================================================
# TAB 4: Test Endpoint
# =============================================================================

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", ""

        # Override endpoint if provided
        if custom_endpoint:
            resource = resource.copy()
            resource['endpoint'] = custom_endpoint

        # Run check
        result = asyncio.run(monitor.check_endpoint(resource, use_proxy=use_proxy))

        # Format response
        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"

        # Troubleshooting hints
        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}"

    # Generate generic example based on endpoint
    endpoint = resource.get('endpoint', '')
    url = resource.get('url', '')

    if endpoint:
        return f"Example URL:\n{url}{endpoint}"

    return f"Base URL:\n{url}"


# =============================================================================
# TAB 5: Configuration
# =============================================================================

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


# =============================================================================
# Build Gradio Interface
# =============================================================================

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*
        """)

        # TAB 1: Real-Time Dashboard
        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
            )

        # TAB 2: Category View
        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
            )

        # TAB 3: Health History
        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]
            )

        # TAB 4: Test Endpoint
        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]
            )

        # TAB 5: Configuration
        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`
            """)

            # Load config info on tab open
            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
            )

        # Initial load
        app.load(
            fn=refresh_dashboard,
            inputs=[category_filter, status_filter, tier_filter],
            outputs=[status_table, summary_cards]
        )

    return app


# =============================================================================
# Main Entry Point
# =============================================================================

if __name__ == "__main__":
    logger.info("Starting Crypto API Monitor...")

    # Start background scheduler
    scheduler.start()

    # Build and launch app
    app = build_interface()

    # Launch with sharing for HF Spaces
    app.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,
        show_error=True
    )