Really-amin commited on
Commit
ef4ea1f
Β·
verified Β·
1 Parent(s): f1a8215

Upload 78 files

Browse files
Files changed (4) hide show
  1. api_server_extended.py +29 -2
  2. index.html +182 -16
  3. static/css/main.css +737 -71
  4. static/js/app.js +721 -40
api_server_extended.py CHANGED
@@ -1963,13 +1963,40 @@ async def run_diagnostic_test():
1963
  start_time = time.time()
1964
 
1965
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1966
  # Execute the diagnostic script
1967
  result = subprocess.run(
1968
- ["python3", "test_models_diagnostic.py"],
1969
  capture_output=True,
1970
  text=True,
1971
  timeout=60, # 60 second timeout
1972
- cwd=WORKSPACE_ROOT
1973
  )
1974
 
1975
  duration = time.time() - start_time
 
1963
  start_time = time.time()
1964
 
1965
  try:
1966
+ # Find the diagnostic script - check multiple possible locations
1967
+ diagnostic_script = None
1968
+ possible_paths = [
1969
+ WORKSPACE_ROOT / "test_models_diagnostic.py",
1970
+ Path("test_models_diagnostic.py"),
1971
+ Path(__file__).parent / "test_models_diagnostic.py",
1972
+ ]
1973
+
1974
+ for path in possible_paths:
1975
+ if path.exists():
1976
+ diagnostic_script = path
1977
+ break
1978
+
1979
+ if not diagnostic_script:
1980
+ return {
1981
+ "status": "error",
1982
+ "output": "test_models_diagnostic.py not found. Searched in:\n" + "\n".join([str(p) for p in possible_paths]),
1983
+ "timestamp": datetime.now().isoformat(),
1984
+ "duration_seconds": 0,
1985
+ "summary": {
1986
+ "transformers_available": False,
1987
+ "hf_hub_connected": False,
1988
+ "models_loaded": 0,
1989
+ "critical_issues": ["Diagnostic script not found"]
1990
+ }
1991
+ }
1992
+
1993
  # Execute the diagnostic script
1994
  result = subprocess.run(
1995
+ ["python3", str(diagnostic_script)],
1996
  capture_output=True,
1997
  text=True,
1998
  timeout=60, # 60 second timeout
1999
+ cwd=str(diagnostic_script.parent)
2000
  )
2001
 
2002
  duration = time.time() - start_time
index.html CHANGED
@@ -102,7 +102,7 @@
102
  </svg>
103
  <span>News</span>
104
  </button>
105
- <button class="nav-item" data-tab="ai-tools" onclick="window.location.href='/ai-tools'">
106
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
107
  <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
108
  <path d="M2 17l10 5 10-5"></path>
@@ -304,8 +304,21 @@
304
  <div id="trending-coins"></div>
305
  </div>
306
 
 
307
  <div class="glass-card">
308
- <h3>Fear & Greed Index</h3>
 
 
 
 
 
 
 
 
 
 
 
 
309
  <div id="fear-greed"></div>
310
  </div>
311
  </section>
@@ -570,8 +583,22 @@
570
  <!-- Test & Diagnostics Tab -->
571
  <section id="tab-diagnostics" class="tab-panel">
572
  <div class="glass-card">
573
- <h3>System Diagnostics & Model Testing</h3>
574
- <p class="text-secondary">Run comprehensive tests on AI models, transformers, and HuggingFace Hub connectivity</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  </div>
576
 
577
  <!-- Quick Status Cards -->
@@ -633,25 +660,104 @@
633
  </div>
634
  </div>
635
 
636
- <!-- Action Buttons -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  <div class="glass-card">
638
  <div class="card-header">
639
- <h3>Diagnostic Actions</h3>
640
  </div>
641
- <div style="display: flex; gap: 12px; flex-wrap: wrap;">
642
  <button class="btn-primary" id="run-diagnostics-btn" onclick="runDiagnostic()">
643
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
644
  <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
645
  </svg>
646
  Run Full Diagnostic
647
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
  <button class="btn-refresh" onclick="refreshDiagnosticStatus()">
649
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
650
  <polyline points="23 4 23 10 17 10"></polyline>
651
  <polyline points="1 20 1 14 7 14"></polyline>
652
  <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
653
  </svg>
654
- Refresh Status
655
  </button>
656
  <button class="btn-refresh" onclick="downloadDiagnosticLog()">
657
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
@@ -664,17 +770,60 @@
664
  </div>
665
  </div>
666
 
667
- <!-- Test Output Section -->
668
  <div class="glass-card">
669
- <div class="card-header">
670
- <h3>Test Output</h3>
671
- <div id="test-progress" style="display: none;">
672
- <div class="spinner"></div>
673
- <span>Running diagnostic tests...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  </div>
675
  </div>
676
- <div style="background: rgba(0, 0, 0, 0.3); border-radius: 8px; padding: 15px; max-height: 500px; overflow-y: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.6;">
677
- <pre id="diagnostic-output" style="margin: 0; color: var(--text-primary); white-space: pre-wrap; word-wrap: break-word;"></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  </div>
679
  </div>
680
 
@@ -706,6 +855,23 @@
706
  <div id="suggested-fixes" style="margin-top: 20px;"></div>
707
  </div>
708
  </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  </main>
710
  </div>
711
  </div>
 
102
  </svg>
103
  <span>News</span>
104
  </button>
105
+ <button class="nav-item" data-tab="ai-tools" onclick="switchTab('ai-tools')">
106
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
107
  <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
108
  <path d="M2 17l10 5 10-5"></path>
 
304
  <div id="trending-coins"></div>
305
  </div>
306
 
307
+ <!-- Enhanced Sentiment Analysis Section -->
308
  <div class="glass-card">
309
+ <div class="card-header">
310
+ <div>
311
+ <h3>πŸ“Š Market Sentiment Analysis</h3>
312
+ <p class="text-secondary">Real-time market sentiment indicators and Fear & Greed Index</p>
313
+ </div>
314
+ <button class="btn-refresh" onclick="loadMarketData()" title="Refresh Sentiment Data">
315
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
316
+ <polyline points="23 4 23 10 17 10"></polyline>
317
+ <polyline points="1 20 1 14 7 14"></polyline>
318
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
319
+ </svg>
320
+ </button>
321
+ </div>
322
  <div id="fear-greed"></div>
323
  </div>
324
  </section>
 
583
  <!-- Test & Diagnostics Tab -->
584
  <section id="tab-diagnostics" class="tab-panel">
585
  <div class="glass-card">
586
+ <div class="card-header">
587
+ <div>
588
+ <h3>πŸ”§ Advanced Diagnostics & Monitoring</h3>
589
+ <p class="text-secondary">Comprehensive system health monitoring, testing, and debugging tools</p>
590
+ </div>
591
+ <div style="display: flex; gap: 8px;">
592
+ <button class="btn-refresh" onclick="toggleAutoRefresh()" id="auto-refresh-btn">
593
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 4px;">
594
+ <polyline points="23 4 23 10 17 10"></polyline>
595
+ <polyline points="1 20 1 14 7 14"></polyline>
596
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
597
+ </svg>
598
+ Auto: OFF
599
+ </button>
600
+ </div>
601
+ </div>
602
  </div>
603
 
604
  <!-- Quick Status Cards -->
 
660
  </div>
661
  </div>
662
 
663
+ <!-- System Health Overview -->
664
+ <div class="glass-card">
665
+ <div class="card-header">
666
+ <h3>πŸ“Š System Health Overview</h3>
667
+ <button class="btn-refresh" onclick="loadSystemHealth()" title="Refresh Health Status">
668
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
669
+ <polyline points="23 4 23 10 17 10"></polyline>
670
+ <polyline points="1 20 1 14 7 14"></polyline>
671
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
672
+ </svg>
673
+ </button>
674
+ </div>
675
+ <div id="system-health-overview" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
676
+ <!-- Will be populated by JavaScript -->
677
+ </div>
678
+ </div>
679
+
680
+ <!-- Provider & Model Status Table -->
681
+ <div class="glass-card">
682
+ <div class="card-header">
683
+ <h3>πŸ” Provider & Model Status</h3>
684
+ <div style="display: flex; gap: 8px;">
685
+ <button class="btn-refresh" onclick="loadProviderHealth()" title="Refresh Provider Status">
686
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
687
+ <polyline points="23 4 23 10 17 10"></polyline>
688
+ <polyline points="1 20 1 14 7 14"></polyline>
689
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
690
+ </svg>
691
+ </button>
692
+ <button class="btn-refresh" onclick="triggerSelfHeal()" title="Trigger Self-Healing">
693
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
694
+ <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"></path>
695
+ </svg>
696
+ Self-Heal
697
+ </button>
698
+ </div>
699
+ </div>
700
+ <div style="overflow-x: auto;">
701
+ <table style="width: 100%; border-collapse: collapse;">
702
+ <thead>
703
+ <tr style="border-bottom: 1px solid var(--border);">
704
+ <th style="padding: 12px; text-align: left; color: var(--text-secondary); font-weight: 600;">Name</th>
705
+ <th style="padding: 12px; text-align: left; color: var(--text-secondary); font-weight: 600;">Type</th>
706
+ <th style="padding: 12px; text-align: left; color: var(--text-secondary); font-weight: 600;">Status</th>
707
+ <th style="padding: 12px; text-align: left; color: var(--text-secondary); font-weight: 600;">Last Check</th>
708
+ <th style="padding: 12px; text-align: left; color: var(--text-secondary); font-weight: 600;">Actions</th>
709
+ </tr>
710
+ </thead>
711
+ <tbody id="provider-health-table">
712
+ <tr><td colspan="5" style="padding: 20px; text-align: center; color: var(--text-secondary);">Loading...</td></tr>
713
+ </tbody>
714
+ </table>
715
+ </div>
716
+ </div>
717
+
718
+ <!-- Quick Diagnostic Actions -->
719
  <div class="glass-card">
720
  <div class="card-header">
721
+ <h3>⚑ Quick Diagnostic Actions</h3>
722
  </div>
723
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
724
  <button class="btn-primary" id="run-diagnostics-btn" onclick="runDiagnostic()">
725
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
726
  <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
727
  </svg>
728
  Run Full Diagnostic
729
  </button>
730
+ <button class="btn-refresh" onclick="testAPIEndpoints()">
731
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
732
+ <path d="M18 20V10"></path>
733
+ <path d="M12 20V4"></path>
734
+ <path d="M6 20v-6"></path>
735
+ </svg>
736
+ Test API Endpoints
737
+ </button>
738
+ <button class="btn-refresh" onclick="checkDatabaseHealth()">
739
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
740
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
741
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
742
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
743
+ </svg>
744
+ Check Database
745
+ </button>
746
+ <button class="btn-refresh" onclick="testNetworkConnectivity()">
747
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
748
+ <circle cx="12" cy="12" r="10"></circle>
749
+ <path d="M2 12h20"></path>
750
+ <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
751
+ </svg>
752
+ Network Test
753
+ </button>
754
  <button class="btn-refresh" onclick="refreshDiagnosticStatus()">
755
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
756
  <polyline points="23 4 23 10 17 10"></polyline>
757
  <polyline points="1 20 1 14 7 14"></polyline>
758
  <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
759
  </svg>
760
+ Refresh All
761
  </button>
762
  <button class="btn-refresh" onclick="downloadDiagnosticLog()">
763
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
 
770
  </div>
771
  </div>
772
 
773
+ <!-- Tabs for different diagnostic views -->
774
  <div class="glass-card">
775
+ <div style="display: flex; gap: 8px; border-bottom: 1px solid var(--border); margin-bottom: 20px;">
776
+ <button class="diagnostic-tab-btn active" data-tab="output" onclick="switchDiagnosticTab('output')">Test Output</button>
777
+ <button class="diagnostic-tab-btn" data-tab="health" onclick="switchDiagnosticTab('health')">Health Details</button>
778
+ <button class="diagnostic-tab-btn" data-tab="api-test" onclick="switchDiagnosticTab('api-test')">API Tests</button>
779
+ <button class="diagnostic-tab-btn" data-tab="logs" onclick="switchDiagnosticTab('logs')">Recent Logs</button>
780
+ </div>
781
+
782
+ <!-- Test Output Tab -->
783
+ <div id="diagnostic-tab-output" class="diagnostic-tab-content active">
784
+ <div class="card-header">
785
+ <h3>Test Output</h3>
786
+ <div id="test-progress" style="display: none;">
787
+ <div class="spinner"></div>
788
+ <span>Running diagnostic tests...</span>
789
+ </div>
790
+ </div>
791
+ <div style="background: rgba(0, 0, 0, 0.3); border-radius: 8px; padding: 15px; max-height: 500px; overflow-y: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.6;">
792
+ <pre id="diagnostic-output" style="margin: 0; color: var(--text-primary); white-space: pre-wrap; word-wrap: break-word;"></pre>
793
+ </div>
794
+ </div>
795
+
796
+ <!-- Health Details Tab -->
797
+ <div id="diagnostic-tab-health" class="diagnostic-tab-content" style="display: none;">
798
+ <h3>Detailed Health Information</h3>
799
+ <div id="health-details-content" style="margin-top: 16px;">
800
+ <p class="text-secondary">Click "Refresh All" to load detailed health information</p>
801
  </div>
802
  </div>
803
+
804
+ <!-- API Tests Tab -->
805
+ <div id="diagnostic-tab-api-test" class="diagnostic-tab-content" style="display: none;">
806
+ <h3>API Endpoint Testing</h3>
807
+ <div id="api-test-results" style="margin-top: 16px;">
808
+ <p class="text-secondary">Click "Test API Endpoints" to run tests</p>
809
+ </div>
810
+ </div>
811
+
812
+ <!-- Logs Tab -->
813
+ <div id="diagnostic-tab-logs" class="diagnostic-tab-content" style="display: none;">
814
+ <div class="card-header">
815
+ <h3>Recent System Logs</h3>
816
+ <button class="btn-refresh" onclick="loadRecentLogs()">
817
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
818
+ <polyline points="23 4 23 10 17 10"></polyline>
819
+ <polyline points="1 20 1 14 7 14"></polyline>
820
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
821
+ </svg>
822
+ </button>
823
+ </div>
824
+ <div id="recent-logs-content" style="margin-top: 16px; max-height: 500px; overflow-y: auto;">
825
+ <p class="text-secondary">Loading logs...</p>
826
+ </div>
827
  </div>
828
  </div>
829
 
 
855
  <div id="suggested-fixes" style="margin-top: 20px;"></div>
856
  </div>
857
  </section>
858
+
859
+ <!-- AI Design Tools Tab -->
860
+ <section id="tab-ai-tools" class="tab-panel">
861
+ <div id="ai-tools-content" style="width: 100%; height: calc(100vh - 180px); min-height: 600px; position: relative;">
862
+ <iframe
863
+ id="ai-tools-iframe"
864
+ src="/ai-tools"
865
+ style="width: 100%; height: 100%; border: none; border-radius: 12px; background: transparent; display: none;"
866
+ title="AI Design Tools"
867
+ onload="handleAIToolsIframeLoad()"
868
+ ></iframe>
869
+ <div id="ai-tools-loading" style="text-align: center; padding: 60px 20px;">
870
+ <div class="spinner" style="margin: 0 auto 20px;"></div>
871
+ <p class="text-secondary">Loading AI Design Tools...</p>
872
+ </div>
873
+ </div>
874
+ </section>
875
  </main>
876
  </div>
877
  </div>
static/css/main.css CHANGED
@@ -47,6 +47,20 @@
47
  --transition-fast: 0.2s ease;
48
  --transition-normal: 0.3s ease;
49
  --transition-slow: 0.5s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
  * {
@@ -438,39 +452,88 @@ body::before {
438
  ============================================================================= */
439
 
440
  .glass-card {
441
- background: rgba(17, 24, 39, 0.6);
442
- backdrop-filter: blur(10px);
443
  border: 1px solid var(--border);
444
- border-radius: 16px;
445
- padding: 25px;
446
- margin-bottom: 20px;
447
  transition: all var(--transition-normal);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  }
449
 
450
  .glass-card:hover {
451
- border-color: rgba(102, 126, 234, 0.3);
452
- box-shadow: 0 8px 32px rgba(102, 126, 234, 0.15);
 
 
 
 
 
453
  }
454
 
455
  .glass-card h3 {
456
- font-size: 20px;
 
457
  margin-bottom: 20px;
458
  color: var(--text-primary);
459
  border-bottom: 2px solid var(--border);
460
- padding-bottom: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  }
462
 
463
  .card-header {
464
  display: flex;
465
  justify-content: space-between;
466
- align-items: center;
467
- margin-bottom: 20px;
 
 
468
  }
469
 
470
  .card-header h3 {
471
  margin-bottom: 0;
472
  border-bottom: none;
473
  padding-bottom: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  }
475
 
476
  /* Stats Grid */
@@ -496,18 +559,19 @@ body::before {
496
  }
497
 
498
  .stat-card {
499
- background: linear-gradient(135deg, rgba(17, 24, 39, 0.8), rgba(31, 41, 55, 0.6));
500
  border: 1px solid var(--border);
501
- border-radius: 16px;
502
- padding: 20px;
503
  display: flex;
504
  align-items: center;
505
- gap: 15px;
506
  transition: all var(--transition-normal);
507
- backdrop-filter: blur(10px);
508
  position: relative;
509
  overflow: hidden;
510
- min-height: 120px;
 
511
  }
512
 
513
  .stat-card::before {
@@ -523,8 +587,9 @@ body::before {
523
  }
524
 
525
  .stat-card:hover {
526
- transform: translateY(-5px);
527
- box-shadow: var(--shadow);
 
528
  }
529
 
530
  .stat-card:hover::before {
@@ -548,14 +613,19 @@ body::before {
548
  }
549
 
550
  .stat-icon {
551
- width: 60px;
552
- height: 60px;
553
- border-radius: 14px;
554
  display: flex;
555
  align-items: center;
556
  justify-content: center;
557
  font-size: 28px;
558
  flex-shrink: 0;
 
 
 
 
 
559
  }
560
 
561
  .stat-card.gradient-purple .stat-icon {
@@ -587,30 +657,39 @@ body::before {
587
  }
588
 
589
  .stat-value {
590
- font-size: 32px;
591
  font-weight: 800;
592
- color: var(--primary);
593
- margin-bottom: 5px;
594
  display: block;
595
  visibility: visible;
596
  opacity: 1;
597
  min-height: 1.2em;
598
  line-height: 1.2;
 
 
 
 
 
599
  }
600
 
601
  .stat-label {
602
  font-size: 14px;
603
  color: var(--text-secondary);
604
  font-weight: 600;
 
 
 
605
  }
606
 
607
  .stat-trend {
608
- font-size: 12px;
609
  color: var(--text-secondary);
610
- margin-top: 5px;
611
  display: flex;
612
  align-items: center;
613
- gap: 5px;
 
614
  }
615
 
616
  .stat-trend i {
@@ -629,28 +708,38 @@ body::before {
629
  ============================================================================= */
630
 
631
  .form-group {
632
- margin-bottom: 20px;
633
  }
634
 
635
  .form-group label {
636
  display: block;
637
- margin-bottom: 8px;
638
  font-weight: 600;
639
  color: var(--text-primary);
 
 
640
  }
641
 
642
  .form-group input,
643
  .form-group textarea,
644
  .form-group select {
645
  width: 100%;
646
- padding: 12px;
647
- background: rgba(31, 41, 55, 0.6);
648
- border: 1px solid var(--border);
649
- border-radius: 10px;
650
  color: var(--text-primary);
651
  font-family: inherit;
652
- font-size: 14px;
653
- transition: var(--transition-fast);
 
 
 
 
 
 
 
 
654
  }
655
 
656
  .form-group input:focus,
@@ -658,12 +747,24 @@ body::before {
658
  .form-group select:focus {
659
  outline: none;
660
  border-color: var(--primary);
661
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 
 
662
  }
663
 
664
  .form-group textarea {
665
  resize: vertical;
666
- min-height: 80px;
 
 
 
 
 
 
 
 
 
 
667
  }
668
 
669
  /* =============================================================================
@@ -671,17 +772,23 @@ body::before {
671
  ============================================================================= */
672
 
673
  .btn-primary, .btn-refresh {
674
- padding: 12px 24px;
675
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
676
  border: none;
677
- border-radius: 10px;
678
  color: white;
679
  font-weight: 600;
680
  cursor: pointer;
681
  transition: all var(--transition-fast);
682
- font-size: 14px;
683
  position: relative;
684
  overflow: hidden;
 
 
 
 
 
 
685
  }
686
 
687
  .btn-primary::before, .btn-refresh::before {
@@ -692,14 +799,20 @@ body::before {
692
  width: 0;
693
  height: 0;
694
  border-radius: 50%;
695
- background: rgba(255, 255, 255, 0.2);
696
  transform: translate(-50%, -50%);
697
  transition: width 0.6s, height 0.6s;
698
  }
699
 
700
  .btn-primary:hover, .btn-refresh:hover {
701
- transform: translateY(-2px);
702
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
 
 
 
 
 
 
703
  }
704
 
705
  .btn-primary:hover::before, .btn-refresh:hover::before {
@@ -708,8 +821,14 @@ body::before {
708
  }
709
 
710
  .btn-refresh {
711
- background: rgba(102, 126, 234, 0.2);
712
- border: 1px solid var(--primary);
 
 
 
 
 
 
713
  }
714
 
715
  /* =============================================================================
@@ -718,15 +837,19 @@ body::before {
718
 
719
  table {
720
  width: 100%;
721
- border-collapse: collapse;
 
 
 
722
  }
723
 
724
  table th,
725
  table td {
726
- padding: 12px;
727
  text-align: left;
728
  border-bottom: 1px solid var(--border);
729
  white-space: nowrap;
 
730
  }
731
 
732
  table td:nth-child(3),
@@ -737,13 +860,33 @@ table td:nth-child(6) {
737
  }
738
 
739
  table th {
740
- background: rgba(31, 41, 55, 0.6);
741
- font-weight: 600;
742
  color: var(--text-primary);
 
 
 
 
 
 
 
 
 
 
 
743
  }
744
 
745
- table tr:hover {
746
- background: rgba(102, 126, 234, 0.05);
 
 
 
 
 
 
 
 
 
747
  }
748
 
749
  /* =============================================================================
@@ -751,29 +894,55 @@ table tr:hover {
751
  ============================================================================= */
752
 
753
  .alert {
754
- padding: 15px;
755
- border-radius: 10px;
756
- margin-bottom: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
757
  }
758
 
759
  .alert-success {
760
  background: rgba(16, 185, 129, 0.15);
 
761
  border: 1px solid rgba(16, 185, 129, 0.3);
 
762
  color: var(--success);
763
  }
764
 
765
  .alert-error {
766
  background: rgba(239, 68, 68, 0.15);
 
767
  border: 1px solid rgba(239, 68, 68, 0.3);
 
768
  color: var(--danger);
769
  }
770
 
771
  .alert-warning {
772
  background: rgba(245, 158, 11, 0.15);
 
773
  border: 1px solid rgba(245, 158, 11, 0.3);
 
774
  color: var(--warning);
775
  }
776
 
 
 
 
 
 
 
 
 
777
  /* =============================================================================
778
  Loading States
779
  ============================================================================= */
@@ -790,11 +959,13 @@ table tr:hover {
790
  .spinner {
791
  border: 3px solid var(--border);
792
  border-top: 3px solid var(--primary);
 
793
  border-radius: 50%;
794
  width: 40px;
795
  height: 40px;
796
- animation: spin 1s linear infinite;
797
  margin: 0 auto 15px;
 
798
  }
799
 
800
  @keyframes spin {
@@ -1541,11 +1712,12 @@ body.light-theme ::-webkit-scrollbar-track {
1541
  }
1542
 
1543
  .news-card-title {
1544
- font-size: 18px;
1545
  font-weight: 700;
1546
  color: var(--text-primary);
1547
- margin-bottom: 10px;
1548
- line-height: 1.4;
 
1549
  }
1550
 
1551
  .news-card-title a {
@@ -1969,6 +2141,157 @@ body.light-theme ::-webkit-scrollbar-track {
1969
 
1970
  /* Diagnostic Actions - Removed custom styles, using standard button classes */
1971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1972
  /* Test Progress */
1973
  #test-progress {
1974
  display: flex;
@@ -2093,16 +2416,20 @@ body.light-theme ::-webkit-scrollbar-track {
2093
  flex-direction: column;
2094
  align-items: center;
2095
  justify-content: center;
2096
- min-height: 400px;
2097
- padding: 60px 20px;
2098
  text-align: center;
 
 
 
2099
  }
2100
 
2101
  .placeholder-icon {
2102
- font-size: 80px;
2103
- margin-bottom: 24px;
2104
- opacity: 0.6;
2105
  animation: float 3s ease-in-out infinite;
 
2106
  }
2107
 
2108
  @keyframes float {
@@ -2116,27 +2443,366 @@ body.light-theme ::-webkit-scrollbar-track {
2116
 
2117
  .placeholder-page h2 {
2118
  color: var(--text-primary);
2119
- font-size: 32px;
2120
- font-weight: 700;
2121
- margin: 0 0 16px 0;
 
 
 
 
 
2122
  }
2123
 
2124
  .placeholder-page p {
2125
  color: var(--text-secondary);
2126
  font-size: 18px;
2127
- margin: 0 0 8px 0;
2128
  max-width: 600px;
 
2129
  }
2130
 
2131
  .placeholder-page .text-secondary {
2132
  color: var(--text-muted);
2133
- font-size: 14px;
2134
- margin-bottom: 32px;
 
2135
  }
2136
 
2137
  .placeholder-page .btn-primary {
2138
  margin-top: 16px;
2139
  }
2140
- max-width: 300px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2141
  }
2142
  }
 
47
  --transition-fast: 0.2s ease;
48
  --transition-normal: 0.3s ease;
49
  --transition-slow: 0.5s ease;
50
+
51
+ /* Spacing */
52
+ --spacing-xs: 4px;
53
+ --spacing-sm: 8px;
54
+ --spacing-md: 16px;
55
+ --spacing-lg: 24px;
56
+ --spacing-xl: 32px;
57
+ --spacing-2xl: 48px;
58
+
59
+ /* Border Radius */
60
+ --radius-sm: 8px;
61
+ --radius-md: 12px;
62
+ --radius-lg: 16px;
63
+ --radius-xl: 20px;
64
  }
65
 
66
  * {
 
452
  ============================================================================= */
453
 
454
  .glass-card {
455
+ background: rgba(17, 24, 39, 0.7);
456
+ backdrop-filter: blur(12px) saturate(180%);
457
  border: 1px solid var(--border);
458
+ border-radius: 18px;
459
+ padding: 28px;
460
+ margin-bottom: 24px;
461
  transition: all var(--transition-normal);
462
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
463
+ position: relative;
464
+ overflow: hidden;
465
+ }
466
+
467
+ .glass-card::before {
468
+ content: '';
469
+ position: absolute;
470
+ top: 0;
471
+ left: 0;
472
+ right: 0;
473
+ height: 2px;
474
+ background: linear-gradient(90deg, transparent, var(--primary), transparent);
475
+ opacity: 0;
476
+ transition: opacity var(--transition-normal);
477
  }
478
 
479
  .glass-card:hover {
480
+ border-color: rgba(102, 126, 234, 0.4);
481
+ box-shadow: 0 12px 40px rgba(102, 126, 234, 0.2), 0 4px 20px rgba(0, 0, 0, 0.3);
482
+ transform: translateY(-2px);
483
+ }
484
+
485
+ .glass-card:hover::before {
486
+ opacity: 1;
487
  }
488
 
489
  .glass-card h3 {
490
+ font-size: 22px;
491
+ font-weight: 700;
492
  margin-bottom: 20px;
493
  color: var(--text-primary);
494
  border-bottom: 2px solid var(--border);
495
+ padding-bottom: 12px;
496
+ letter-spacing: -0.02em;
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 10px;
500
+ }
501
+
502
+ .glass-card h3::before {
503
+ content: '';
504
+ width: 4px;
505
+ height: 22px;
506
+ background: var(--gradient-purple);
507
+ border-radius: 2px;
508
+ flex-shrink: 0;
509
  }
510
 
511
  .card-header {
512
  display: flex;
513
  justify-content: space-between;
514
+ align-items: flex-start;
515
+ margin-bottom: 24px;
516
+ gap: 16px;
517
+ flex-wrap: wrap;
518
  }
519
 
520
  .card-header h3 {
521
  margin-bottom: 0;
522
  border-bottom: none;
523
  padding-bottom: 0;
524
+ flex: 1;
525
+ min-width: 200px;
526
+ }
527
+
528
+ .card-header > div:first-child {
529
+ flex: 1;
530
+ min-width: 200px;
531
+ }
532
+
533
+ .card-header > div:first-child p {
534
+ margin-top: 6px;
535
+ font-size: 14px;
536
+ line-height: 1.5;
537
  }
538
 
539
  /* Stats Grid */
 
559
  }
560
 
561
  .stat-card {
562
+ background: linear-gradient(135deg, rgba(17, 24, 39, 0.85), rgba(31, 41, 55, 0.7));
563
  border: 1px solid var(--border);
564
+ border-radius: 18px;
565
+ padding: 24px;
566
  display: flex;
567
  align-items: center;
568
+ gap: 18px;
569
  transition: all var(--transition-normal);
570
+ backdrop-filter: blur(12px) saturate(180%);
571
  position: relative;
572
  overflow: hidden;
573
+ min-height: 130px;
574
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
575
  }
576
 
577
  .stat-card::before {
 
587
  }
588
 
589
  .stat-card:hover {
590
+ transform: translateY(-6px);
591
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3), 0 4px 16px rgba(102, 126, 234, 0.2);
592
+ border-color: rgba(102, 126, 234, 0.4);
593
  }
594
 
595
  .stat-card:hover::before {
 
613
  }
614
 
615
  .stat-icon {
616
+ width: 64px;
617
+ height: 64px;
618
+ border-radius: 16px;
619
  display: flex;
620
  align-items: center;
621
  justify-content: center;
622
  font-size: 28px;
623
  flex-shrink: 0;
624
+ transition: transform var(--transition-normal);
625
+ }
626
+
627
+ .stat-card:hover .stat-icon {
628
+ transform: scale(1.1) rotate(5deg);
629
  }
630
 
631
  .stat-card.gradient-purple .stat-icon {
 
657
  }
658
 
659
  .stat-value {
660
+ font-size: 36px;
661
  font-weight: 800;
662
+ color: var(--text-primary);
663
+ margin-bottom: 6px;
664
  display: block;
665
  visibility: visible;
666
  opacity: 1;
667
  min-height: 1.2em;
668
  line-height: 1.2;
669
+ letter-spacing: -0.03em;
670
+ background: linear-gradient(135deg, var(--text-primary), rgba(255, 255, 255, 0.8));
671
+ -webkit-background-clip: text;
672
+ -webkit-text-fill-color: transparent;
673
+ background-clip: text;
674
  }
675
 
676
  .stat-label {
677
  font-size: 14px;
678
  color: var(--text-secondary);
679
  font-weight: 600;
680
+ text-transform: uppercase;
681
+ letter-spacing: 0.05em;
682
+ margin-bottom: 8px;
683
  }
684
 
685
  .stat-trend {
686
+ font-size: 13px;
687
  color: var(--text-secondary);
688
+ margin-top: 8px;
689
  display: flex;
690
  align-items: center;
691
+ gap: 6px;
692
+ font-weight: 500;
693
  }
694
 
695
  .stat-trend i {
 
708
  ============================================================================= */
709
 
710
  .form-group {
711
+ margin-bottom: 24px;
712
  }
713
 
714
  .form-group label {
715
  display: block;
716
+ margin-bottom: 10px;
717
  font-weight: 600;
718
  color: var(--text-primary);
719
+ font-size: 14px;
720
+ letter-spacing: 0.01em;
721
  }
722
 
723
  .form-group input,
724
  .form-group textarea,
725
  .form-group select {
726
  width: 100%;
727
+ padding: 14px 16px;
728
+ background: rgba(31, 41, 55, 0.7);
729
+ border: 1.5px solid var(--border);
730
+ border-radius: 12px;
731
  color: var(--text-primary);
732
  font-family: inherit;
733
+ font-size: 15px;
734
+ transition: all var(--transition-fast);
735
+ backdrop-filter: blur(8px);
736
+ }
737
+
738
+ .form-group input:hover,
739
+ .form-group textarea:hover,
740
+ .form-group select:hover {
741
+ border-color: rgba(102, 126, 234, 0.3);
742
+ background: rgba(31, 41, 55, 0.8);
743
  }
744
 
745
  .form-group input:focus,
 
747
  .form-group select:focus {
748
  outline: none;
749
  border-color: var(--primary);
750
+ box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.15), 0 4px 12px rgba(102, 126, 234, 0.1);
751
+ background: rgba(31, 41, 55, 0.9);
752
+ transform: translateY(-1px);
753
  }
754
 
755
  .form-group textarea {
756
  resize: vertical;
757
+ min-height: 100px;
758
+ line-height: 1.6;
759
+ }
760
+
761
+ .form-group select {
762
+ cursor: pointer;
763
+ appearance: none;
764
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239ca3af' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
765
+ background-repeat: no-repeat;
766
+ background-position: right 14px center;
767
+ padding-right: 40px;
768
  }
769
 
770
  /* =============================================================================
 
772
  ============================================================================= */
773
 
774
  .btn-primary, .btn-refresh {
775
+ padding: 14px 28px;
776
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
777
  border: none;
778
+ border-radius: 12px;
779
  color: white;
780
  font-weight: 600;
781
  cursor: pointer;
782
  transition: all var(--transition-fast);
783
+ font-size: 15px;
784
  position: relative;
785
  overflow: hidden;
786
+ display: inline-flex;
787
+ align-items: center;
788
+ justify-content: center;
789
+ gap: 8px;
790
+ letter-spacing: 0.01em;
791
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
792
  }
793
 
794
  .btn-primary::before, .btn-refresh::before {
 
799
  width: 0;
800
  height: 0;
801
  border-radius: 50%;
802
+ background: rgba(255, 255, 255, 0.25);
803
  transform: translate(-50%, -50%);
804
  transition: width 0.6s, height 0.6s;
805
  }
806
 
807
  .btn-primary:hover, .btn-refresh:hover {
808
+ transform: translateY(-3px);
809
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.5);
810
+ background: linear-gradient(135deg, var(--primary-light), var(--primary));
811
+ }
812
+
813
+ .btn-primary:active, .btn-refresh:active {
814
+ transform: translateY(-1px);
815
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
816
  }
817
 
818
  .btn-primary:hover::before, .btn-refresh:hover::before {
 
821
  }
822
 
823
  .btn-refresh {
824
+ background: rgba(102, 126, 234, 0.15);
825
+ border: 1.5px solid var(--primary);
826
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
827
+ }
828
+
829
+ .btn-refresh:hover {
830
+ background: rgba(102, 126, 234, 0.25);
831
+ border-color: var(--primary-light);
832
  }
833
 
834
  /* =============================================================================
 
837
 
838
  table {
839
  width: 100%;
840
+ border-collapse: separate;
841
+ border-spacing: 0;
842
+ border-radius: 12px;
843
+ overflow: hidden;
844
  }
845
 
846
  table th,
847
  table td {
848
+ padding: 16px;
849
  text-align: left;
850
  border-bottom: 1px solid var(--border);
851
  white-space: nowrap;
852
+ transition: background-color var(--transition-fast);
853
  }
854
 
855
  table td:nth-child(3),
 
860
  }
861
 
862
  table th {
863
+ background: linear-gradient(135deg, rgba(31, 41, 55, 0.8), rgba(17, 24, 39, 0.8));
864
+ font-weight: 700;
865
  color: var(--text-primary);
866
+ text-transform: uppercase;
867
+ font-size: 12px;
868
+ letter-spacing: 0.05em;
869
+ border-bottom: 2px solid var(--border);
870
+ position: sticky;
871
+ top: 0;
872
+ z-index: 10;
873
+ }
874
+
875
+ table tbody tr {
876
+ transition: all var(--transition-fast);
877
  }
878
 
879
+ table tbody tr:hover {
880
+ background: rgba(102, 126, 234, 0.08);
881
+ transform: scale(1.01);
882
+ }
883
+
884
+ table tbody tr:last-child td {
885
+ border-bottom: none;
886
+ }
887
+
888
+ table tbody tr td:first-child {
889
+ font-weight: 600;
890
  }
891
 
892
  /* =============================================================================
 
894
  ============================================================================= */
895
 
896
  .alert {
897
+ padding: 16px 20px;
898
+ border-radius: 12px;
899
+ margin-bottom: 20px;
900
+ border-left: 4px solid;
901
+ display: flex;
902
+ align-items: flex-start;
903
+ gap: 12px;
904
+ backdrop-filter: blur(8px);
905
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
906
+ transition: all var(--transition-fast);
907
+ }
908
+
909
+ .alert:hover {
910
+ transform: translateX(4px);
911
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
912
  }
913
 
914
  .alert-success {
915
  background: rgba(16, 185, 129, 0.15);
916
+ border-left-color: var(--success);
917
  border: 1px solid rgba(16, 185, 129, 0.3);
918
+ border-left-width: 4px;
919
  color: var(--success);
920
  }
921
 
922
  .alert-error {
923
  background: rgba(239, 68, 68, 0.15);
924
+ border-left-color: var(--danger);
925
  border: 1px solid rgba(239, 68, 68, 0.3);
926
+ border-left-width: 4px;
927
  color: var(--danger);
928
  }
929
 
930
  .alert-warning {
931
  background: rgba(245, 158, 11, 0.15);
932
+ border-left-color: var(--warning);
933
  border: 1px solid rgba(245, 158, 11, 0.3);
934
+ border-left-width: 4px;
935
  color: var(--warning);
936
  }
937
 
938
+ .alert-info {
939
+ background: rgba(59, 130, 246, 0.15);
940
+ border-left-color: var(--info);
941
+ border: 1px solid rgba(59, 130, 246, 0.3);
942
+ border-left-width: 4px;
943
+ color: var(--info);
944
+ }
945
+
946
  /* =============================================================================
947
  Loading States
948
  ============================================================================= */
 
959
  .spinner {
960
  border: 3px solid var(--border);
961
  border-top: 3px solid var(--primary);
962
+ border-right: 3px solid transparent;
963
  border-radius: 50%;
964
  width: 40px;
965
  height: 40px;
966
+ animation: spin 0.8s linear infinite;
967
  margin: 0 auto 15px;
968
+ box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
969
  }
970
 
971
  @keyframes spin {
 
1712
  }
1713
 
1714
  .news-card-title {
1715
+ font-size: 20px;
1716
  font-weight: 700;
1717
  color: var(--text-primary);
1718
+ margin-bottom: 12px;
1719
+ line-height: 1.5;
1720
+ letter-spacing: -0.01em;
1721
  }
1722
 
1723
  .news-card-title a {
 
2141
 
2142
  /* Diagnostic Actions - Removed custom styles, using standard button classes */
2143
 
2144
+ /* Diagnostic Tab Buttons */
2145
+ .diagnostic-tab-btn {
2146
+ padding: 12px 24px;
2147
+ background: transparent;
2148
+ border: none;
2149
+ border-bottom: 3px solid transparent;
2150
+ color: var(--text-secondary);
2151
+ font-weight: 600;
2152
+ cursor: pointer;
2153
+ transition: all var(--transition-fast);
2154
+ font-size: 14px;
2155
+ position: relative;
2156
+ letter-spacing: 0.01em;
2157
+ }
2158
+
2159
+ .diagnostic-tab-btn::after {
2160
+ content: '';
2161
+ position: absolute;
2162
+ bottom: -3px;
2163
+ left: 0;
2164
+ width: 0;
2165
+ height: 3px;
2166
+ background: var(--gradient-purple);
2167
+ transition: width var(--transition-normal);
2168
+ }
2169
+
2170
+ .diagnostic-tab-btn:hover {
2171
+ color: var(--text-primary);
2172
+ background: rgba(102, 126, 234, 0.08);
2173
+ }
2174
+
2175
+ .diagnostic-tab-btn:hover::after {
2176
+ width: 100%;
2177
+ }
2178
+
2179
+ .diagnostic-tab-btn.active {
2180
+ color: var(--primary);
2181
+ background: rgba(102, 126, 234, 0.1);
2182
+ }
2183
+
2184
+ .diagnostic-tab-btn.active::after {
2185
+ width: 100%;
2186
+ }
2187
+
2188
+ .diagnostic-tab-content {
2189
+ display: none;
2190
+ }
2191
+
2192
+ .diagnostic-tab-content.active {
2193
+ display: block;
2194
+ }
2195
+
2196
+ /* Sentiment Analysis Enhancements */
2197
+ @keyframes pulse-glow {
2198
+ 0%, 100% {
2199
+ box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
2200
+ }
2201
+ 50% {
2202
+ box-shadow: 0 0 40px rgba(102, 126, 234, 0.6);
2203
+ }
2204
+ }
2205
+
2206
+ @keyframes progress-fill {
2207
+ from {
2208
+ width: 0%;
2209
+ }
2210
+ }
2211
+
2212
+ .sentiment-gauge {
2213
+ position: relative;
2214
+ width: 200px;
2215
+ height: 200px;
2216
+ margin: 0 auto;
2217
+ }
2218
+
2219
+ .sentiment-progress-bar {
2220
+ position: relative;
2221
+ height: 24px;
2222
+ background: rgba(0, 0, 0, 0.3);
2223
+ border-radius: 12px;
2224
+ overflow: hidden;
2225
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
2226
+ }
2227
+
2228
+ .sentiment-progress-fill {
2229
+ height: 100%;
2230
+ border-radius: 12px;
2231
+ transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
2232
+ position: relative;
2233
+ overflow: hidden;
2234
+ }
2235
+
2236
+ .sentiment-progress-fill::after {
2237
+ content: '';
2238
+ position: absolute;
2239
+ top: 0;
2240
+ left: 0;
2241
+ right: 0;
2242
+ bottom: 0;
2243
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
2244
+ animation: shimmer 2s infinite;
2245
+ }
2246
+
2247
+ @keyframes shimmer {
2248
+ 0% {
2249
+ transform: translateX(-100%);
2250
+ }
2251
+ 100% {
2252
+ transform: translateX(100%);
2253
+ }
2254
+ }
2255
+
2256
+ @keyframes pulse {
2257
+ 0%, 100% {
2258
+ opacity: 0.3;
2259
+ }
2260
+ 50% {
2261
+ opacity: 0.6;
2262
+ }
2263
+ }
2264
+
2265
+ /* Health Status Badge */
2266
+ .health-badge {
2267
+ display: inline-block;
2268
+ padding: 4px 12px;
2269
+ border-radius: 12px;
2270
+ font-size: 12px;
2271
+ font-weight: 600;
2272
+ text-transform: uppercase;
2273
+ }
2274
+
2275
+ .health-badge.healthy {
2276
+ background: rgba(16, 185, 129, 0.2);
2277
+ color: var(--success);
2278
+ }
2279
+
2280
+ .health-badge.degraded {
2281
+ background: rgba(245, 158, 11, 0.2);
2282
+ color: var(--warning);
2283
+ }
2284
+
2285
+ .health-badge.unavailable {
2286
+ background: rgba(239, 68, 68, 0.2);
2287
+ color: var(--danger);
2288
+ }
2289
+
2290
+ .health-badge.unknown {
2291
+ background: rgba(107, 114, 128, 0.2);
2292
+ color: var(--text-secondary);
2293
+ }
2294
+
2295
  /* Test Progress */
2296
  #test-progress {
2297
  display: flex;
 
2416
  flex-direction: column;
2417
  align-items: center;
2418
  justify-content: center;
2419
+ min-height: 500px;
2420
+ padding: 80px 20px;
2421
  text-align: center;
2422
+ background: rgba(17, 24, 39, 0.3);
2423
+ border-radius: 18px;
2424
+ border: 2px dashed var(--border);
2425
  }
2426
 
2427
  .placeholder-icon {
2428
+ font-size: 96px;
2429
+ margin-bottom: 32px;
2430
+ opacity: 0.7;
2431
  animation: float 3s ease-in-out infinite;
2432
+ filter: drop-shadow(0 4px 12px rgba(102, 126, 234, 0.3));
2433
  }
2434
 
2435
  @keyframes float {
 
2443
 
2444
  .placeholder-page h2 {
2445
  color: var(--text-primary);
2446
+ font-size: 36px;
2447
+ font-weight: 800;
2448
+ margin: 0 0 20px 0;
2449
+ letter-spacing: -0.02em;
2450
+ background: linear-gradient(135deg, var(--text-primary), rgba(255, 255, 255, 0.7));
2451
+ -webkit-background-clip: text;
2452
+ -webkit-text-fill-color: transparent;
2453
+ background-clip: text;
2454
  }
2455
 
2456
  .placeholder-page p {
2457
  color: var(--text-secondary);
2458
  font-size: 18px;
2459
+ margin: 0 0 12px 0;
2460
  max-width: 600px;
2461
+ line-height: 1.6;
2462
  }
2463
 
2464
  .placeholder-page .text-secondary {
2465
  color: var(--text-muted);
2466
+ font-size: 15px;
2467
+ margin-bottom: 40px;
2468
+ line-height: 1.6;
2469
  }
2470
 
2471
  .placeholder-page .btn-primary {
2472
  margin-top: 16px;
2473
  }
2474
+
2475
+ /* =============================================================================
2476
+ System Status Display
2477
+ ============================================================================= */
2478
+
2479
+ .system-status-container {
2480
+ display: flex;
2481
+ flex-direction: column;
2482
+ gap: 20px;
2483
+ }
2484
+
2485
+ .system-status-header {
2486
+ display: flex;
2487
+ align-items: center;
2488
+ gap: 16px;
2489
+ padding: 20px;
2490
+ border-radius: 12px;
2491
+ background: rgba(31, 41, 55, 0.5);
2492
+ border: 1px solid var(--border);
2493
+ backdrop-filter: blur(8px);
2494
+ }
2495
+
2496
+ .system-status-header.alert-success {
2497
+ background: rgba(16, 185, 129, 0.1);
2498
+ border-color: rgba(16, 185, 129, 0.3);
2499
+ }
2500
+
2501
+ .system-status-header.alert-warning {
2502
+ background: rgba(245, 158, 11, 0.1);
2503
+ border-color: rgba(245, 158, 11, 0.3);
2504
+ }
2505
+
2506
+ .system-status-header.alert-error {
2507
+ background: rgba(239, 68, 68, 0.1);
2508
+ border-color: rgba(239, 68, 68, 0.3);
2509
+ }
2510
+
2511
+ .status-icon-wrapper {
2512
+ width: 48px;
2513
+ height: 48px;
2514
+ border-radius: 12px;
2515
+ display: flex;
2516
+ align-items: center;
2517
+ justify-content: center;
2518
+ background: rgba(102, 126, 234, 0.2);
2519
+ flex-shrink: 0;
2520
+ }
2521
+
2522
+ .system-status-header.alert-success .status-icon-wrapper {
2523
+ background: rgba(16, 185, 129, 0.2);
2524
+ color: var(--success);
2525
+ }
2526
+
2527
+ .system-status-header.alert-warning .status-icon-wrapper {
2528
+ background: rgba(245, 158, 11, 0.2);
2529
+ color: var(--warning);
2530
+ }
2531
+
2532
+ .system-status-header.alert-error .status-icon-wrapper {
2533
+ background: rgba(239, 68, 68, 0.2);
2534
+ color: var(--danger);
2535
+ }
2536
+
2537
+ .status-icon-wrapper svg {
2538
+ width: 24px;
2539
+ height: 24px;
2540
+ }
2541
+
2542
+ .status-title {
2543
+ font-size: 13px;
2544
+ color: var(--text-secondary);
2545
+ text-transform: uppercase;
2546
+ letter-spacing: 0.05em;
2547
+ font-weight: 600;
2548
+ margin-bottom: 4px;
2549
+ }
2550
+
2551
+ .status-value {
2552
+ font-size: 24px;
2553
+ font-weight: 700;
2554
+ color: var(--text-primary);
2555
+ letter-spacing: -0.02em;
2556
+ }
2557
+
2558
+ .system-status-grid {
2559
+ display: grid;
2560
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
2561
+ gap: 16px;
2562
+ }
2563
+
2564
+ .status-item {
2565
+ padding: 16px;
2566
+ background: rgba(31, 41, 55, 0.4);
2567
+ border: 1px solid var(--border);
2568
+ border-radius: 12px;
2569
+ backdrop-filter: blur(8px);
2570
+ transition: all var(--transition-fast);
2571
+ }
2572
+
2573
+ .status-item:hover {
2574
+ background: rgba(31, 41, 55, 0.6);
2575
+ border-color: rgba(102, 126, 234, 0.3);
2576
+ transform: translateY(-2px);
2577
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
2578
+ }
2579
+
2580
+ .status-item.status-full-width {
2581
+ grid-column: 1 / -1;
2582
+ }
2583
+
2584
+ .status-item.status-online {
2585
+ border-left: 3px solid var(--success);
2586
+ }
2587
+
2588
+ .status-item.status-degraded {
2589
+ border-left: 3px solid var(--warning);
2590
+ }
2591
+
2592
+ .status-item.status-offline {
2593
+ border-left: 3px solid var(--danger);
2594
+ }
2595
+
2596
+ .status-item-label {
2597
+ font-size: 12px;
2598
+ color: var(--text-secondary);
2599
+ text-transform: uppercase;
2600
+ letter-spacing: 0.05em;
2601
+ font-weight: 600;
2602
+ margin-bottom: 8px;
2603
+ }
2604
+
2605
+ .status-item-value {
2606
+ font-size: 28px;
2607
+ font-weight: 800;
2608
+ color: var(--text-primary);
2609
+ letter-spacing: -0.03em;
2610
+ line-height: 1.2;
2611
+ }
2612
+
2613
+ .status-item-value.status-time {
2614
+ font-size: 16px;
2615
+ font-weight: 600;
2616
+ font-family: 'JetBrains Mono', monospace;
2617
+ color: var(--text-secondary);
2618
+ }
2619
+
2620
+ .status-online .status-item-value {
2621
+ color: var(--success);
2622
+ }
2623
+
2624
+ .status-degraded .status-item-value {
2625
+ color: var(--warning);
2626
+ }
2627
+
2628
+ .status-offline .status-item-value {
2629
+ color: var(--danger);
2630
+ }
2631
+
2632
+ @media (max-width: 768px) {
2633
+ .system-status-grid {
2634
+ grid-template-columns: repeat(2, 1fr);
2635
+ }
2636
+
2637
+ .status-item-value {
2638
+ font-size: 24px;
2639
+ }
2640
+
2641
+ .status-value {
2642
+ font-size: 20px;
2643
+ }
2644
+ }
2645
+
2646
+ /* =============================================================================
2647
+ Trending Coins Display
2648
+ ============================================================================= */
2649
+
2650
+ .trending-coins-grid {
2651
+ display: grid;
2652
+ gap: 16px;
2653
+ }
2654
+
2655
+ .trending-coin-card {
2656
+ display: flex;
2657
+ align-items: center;
2658
+ gap: 16px;
2659
+ padding: 18px;
2660
+ background: rgba(31, 41, 55, 0.6);
2661
+ border: 1px solid var(--border);
2662
+ border-radius: 14px;
2663
+ border-left: 4px solid var(--primary);
2664
+ backdrop-filter: blur(8px);
2665
+ transition: all var(--transition-normal);
2666
+ position: relative;
2667
+ overflow: hidden;
2668
+ }
2669
+
2670
+ .trending-coin-card::before {
2671
+ content: '';
2672
+ position: absolute;
2673
+ top: 0;
2674
+ left: 0;
2675
+ width: 4px;
2676
+ height: 100%;
2677
+ background: var(--gradient-purple);
2678
+ opacity: 0;
2679
+ transition: opacity var(--transition-normal);
2680
+ }
2681
+
2682
+ .trending-coin-card:hover {
2683
+ background: rgba(31, 41, 55, 0.8);
2684
+ border-color: rgba(102, 126, 234, 0.4);
2685
+ transform: translateY(-2px);
2686
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.2);
2687
+ }
2688
+
2689
+ .trending-coin-card:hover::before {
2690
+ opacity: 1;
2691
+ }
2692
+
2693
+ .trending-coin-rank {
2694
+ font-size: 20px;
2695
+ font-weight: 800;
2696
+ color: var(--primary);
2697
+ min-width: 40px;
2698
+ text-align: center;
2699
+ background: rgba(102, 126, 234, 0.15);
2700
+ border-radius: 10px;
2701
+ padding: 8px 12px;
2702
+ flex-shrink: 0;
2703
+ }
2704
+
2705
+ .trending-coin-content {
2706
+ display: flex;
2707
+ align-items: center;
2708
+ gap: 14px;
2709
+ flex: 1;
2710
+ min-width: 0;
2711
+ }
2712
+
2713
+ .trending-coin-thumb {
2714
+ width: 40px;
2715
+ height: 40px;
2716
+ border-radius: 10px;
2717
+ object-fit: cover;
2718
+ flex-shrink: 0;
2719
+ border: 1px solid var(--border);
2720
+ }
2721
+
2722
+ .trending-coin-info {
2723
+ flex: 1;
2724
+ min-width: 0;
2725
+ }
2726
+
2727
+ .trending-coin-name {
2728
+ display: flex;
2729
+ align-items: baseline;
2730
+ gap: 10px;
2731
+ margin-bottom: 6px;
2732
+ flex-wrap: wrap;
2733
+ }
2734
+
2735
+ .trending-coin-name strong {
2736
+ font-size: 16px;
2737
+ font-weight: 700;
2738
+ color: var(--text-primary);
2739
+ letter-spacing: -0.01em;
2740
+ }
2741
+
2742
+ .trending-coin-fullname {
2743
+ font-size: 14px;
2744
+ color: var(--text-secondary);
2745
+ font-weight: 500;
2746
+ }
2747
+
2748
+ .trending-coin-meta {
2749
+ font-size: 12px;
2750
+ color: var(--text-muted);
2751
+ font-weight: 500;
2752
+ margin-top: 4px;
2753
+ }
2754
+
2755
+ .trending-coin-score {
2756
+ display: flex;
2757
+ flex-direction: column;
2758
+ align-items: flex-end;
2759
+ gap: 4px;
2760
+ flex-shrink: 0;
2761
+ padding-left: 16px;
2762
+ border-left: 1px solid var(--border);
2763
+ }
2764
+
2765
+ .trending-coin-score-value {
2766
+ font-size: 22px;
2767
+ font-weight: 800;
2768
+ color: var(--success);
2769
+ letter-spacing: -0.02em;
2770
+ line-height: 1;
2771
+ }
2772
+
2773
+ .trending-coin-score-label {
2774
+ font-size: 11px;
2775
+ color: var(--text-muted);
2776
+ text-transform: uppercase;
2777
+ letter-spacing: 0.05em;
2778
+ font-weight: 600;
2779
+ }
2780
+
2781
+ @media (max-width: 768px) {
2782
+ .trending-coin-card {
2783
+ flex-direction: column;
2784
+ align-items: flex-start;
2785
+ gap: 12px;
2786
+ }
2787
+
2788
+ .trending-coin-rank {
2789
+ align-self: flex-start;
2790
+ }
2791
+
2792
+ .trending-coin-score {
2793
+ align-self: flex-end;
2794
+ border-left: none;
2795
+ border-top: 1px solid var(--border);
2796
+ padding-left: 0;
2797
+ padding-top: 12px;
2798
+ width: 100%;
2799
+ flex-direction: row;
2800
+ justify-content: space-between;
2801
+ align-items: center;
2802
+ }
2803
+
2804
+ .trending-coin-name {
2805
+ flex-direction: column;
2806
+ gap: 4px;
2807
  }
2808
  }
static/js/app.js CHANGED
@@ -110,6 +110,7 @@ function switchTab(tabId) {
110
  'news': { title: 'Crypto News', subtitle: 'Latest News & Updates' },
111
  'settings': { title: 'Settings', subtitle: 'System Configuration' },
112
  'diagnostics': { title: 'Test & Diagnostics', subtitle: 'System Diagnostics & Model Testing' },
 
113
  'providers': { title: 'Providers', subtitle: 'Provider Management' },
114
  'resources': { title: 'Resources', subtitle: 'Resource Management' },
115
  'defi': { title: 'DeFi Analytics', subtitle: 'DeFi Protocol Analytics' },
@@ -233,6 +234,9 @@ function loadTabData(tabId) {
233
  case 'diagnostics':
234
  refreshDiagnosticStatus();
235
  break;
 
 
 
236
  default:
237
  console.log('No specific loader for tab:', tabId);
238
  }
@@ -446,15 +450,55 @@ async function loadDashboard() {
446
  formattedTime = lastUpdate;
447
  }
448
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  systemStatusDiv.innerHTML = `
450
- <div class="alert ${healthClass}">
451
- <strong>System Status:</strong> ${healthStatus === 'ok' ? 'Healthy' : healthStatus}<br>
452
- <strong>Total Providers:</strong> ${totalProviders}<br>
453
- <strong>Online APIs:</strong> ${onlineProviders}<br>
454
- <strong>Degraded APIs:</strong> ${degradedProviders}<br>
455
- <strong>Offline APIs:</strong> ${offlineProviders}<br>
456
- <strong>Avg Response Time:</strong> ${avgResponseTime}ms<br>
457
- <strong>Last Update:</strong> ${formattedTime}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  </div>
459
  `;
460
  }
@@ -611,19 +655,36 @@ async function loadMarketData() {
611
 
612
  if (trendingData.trending && trendingData.trending.length > 0) {
613
  trendingDiv.innerHTML = `
614
- <div style="display: grid; gap: 10px;">
615
- ${trendingData.trending.map((coin, index) => `
616
- <div style="padding: 15px; background: rgba(31, 41, 55, 0.6); border-radius: 10px; display: flex; justify-content: space-between; align-items: center; border-left: 4px solid var(--primary);">
617
- <div style="display: flex; align-items: center; gap: 10px;">
618
- <span style="font-size: 18px; font-weight: 800; color: var(--primary);">#${index + 1}</span>
619
- <div>
620
- <strong>${coin.symbol || coin.id}</strong> - ${coin.name || 'Unknown'}
621
- ${coin.market_cap_rank ? `<div style="font-size: 12px; color: var(--text-secondary);">Market Cap Rank: ${coin.market_cap_rank}</div>` : ''}
 
 
 
 
 
 
 
 
 
 
 
622
  </div>
623
  </div>
624
- <div style="font-size: 20px; font-weight: 700; color: var(--success);">${coin.score ? coin.score.toFixed(2) : 'N/A'}</div>
 
 
 
 
 
625
  </div>
626
- `).join('')}
 
627
  </div>
628
  `;
629
  } else {
@@ -637,7 +698,7 @@ async function loadMarketData() {
637
  trendingDiv.innerHTML = '<div class="alert alert-warning">Trending data unavailable</div>';
638
  }
639
 
640
- // Load Fear & Greed Index
641
  try {
642
  const sentimentRes = await fetch('/api/sentiment');
643
  if (sentimentRes.ok) {
@@ -647,26 +708,171 @@ async function loadMarketData() {
647
  const fgValue = sentimentData.fear_greed_index;
648
  const fgLabel = sentimentData.fear_greed_label || 'Unknown';
649
 
650
- let fgColor = 'var(--warning)';
651
- if (fgValue >= 75) fgColor = 'var(--success)';
652
- else if (fgValue >= 50) fgColor = 'var(--info)';
653
- else if (fgValue >= 25) fgColor = 'var(--warning)';
654
- else fgColor = 'var(--danger)';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
 
656
  fgDiv.innerHTML = `
657
- <div style="text-align: center; padding: 30px;">
658
- <div style="font-size: 72px; font-weight: 800; margin-bottom: 10px; color: ${fgColor};">
659
- ${fgValue}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  </div>
661
- <div style="font-size: 24px; font-weight: 600; color: var(--text-primary); margin-bottom: 10px;">
662
- ${fgLabel}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  </div>
664
- <div style="font-size: 14px; color: var(--text-secondary);">
665
- Market Fear & Greed Index
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  </div>
667
- ${sentimentData.timestamp ? `<div style="font-size: 12px; color: var(--text-secondary); margin-top: 10px;">
668
- Last Update: ${new Date(sentimentData.timestamp).toLocaleString('en-US')}
669
- </div>` : ''}
670
  </div>
671
  `;
672
  } else {
@@ -677,7 +883,13 @@ async function loadMarketData() {
677
  }
678
  } catch (fgError) {
679
  console.warn('Fear & Greed endpoint error:', fgError);
680
- fgDiv.innerHTML = '<div class="alert alert-warning">Fear & Greed Index unavailable</div>';
 
 
 
 
 
 
681
  }
682
 
683
  console.log('βœ… Market data loaded successfully');
@@ -1663,15 +1875,484 @@ function downloadDiagnosticLog() {
1663
  showToast('βœ… Log downloaded', 'success');
1664
  }
1665
 
1666
- // Initialize diagnostics on page load
1667
- document.addEventListener('DOMContentLoaded', function() {
1668
- // Update status cards on page load
1669
- refreshDiagnosticStatus();
1670
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1671
 
1672
  // Export diagnostic functions to window
1673
  window.runDiagnostic = runDiagnostic;
1674
  window.refreshDiagnosticStatus = refreshDiagnosticStatus;
1675
  window.downloadDiagnosticLog = downloadDiagnosticLog;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1676
 
1677
  console.log('βœ… App.js loaded successfully');
 
110
  'news': { title: 'Crypto News', subtitle: 'Latest News & Updates' },
111
  'settings': { title: 'Settings', subtitle: 'System Configuration' },
112
  'diagnostics': { title: 'Test & Diagnostics', subtitle: 'System Diagnostics & Model Testing' },
113
+ 'ai-tools': { title: 'AI Design Tools', subtitle: 'AI-Powered Tools & Utilities' },
114
  'providers': { title: 'Providers', subtitle: 'Provider Management' },
115
  'resources': { title: 'Resources', subtitle: 'Resource Management' },
116
  'defi': { title: 'DeFi Analytics', subtitle: 'DeFi Protocol Analytics' },
 
234
  case 'diagnostics':
235
  refreshDiagnosticStatus();
236
  break;
237
+ case 'ai-tools':
238
+ loadAITools();
239
+ break;
240
  default:
241
  console.log('No specific loader for tab:', tabId);
242
  }
 
450
  formattedTime = lastUpdate;
451
  }
452
 
453
+ // Create a properly formatted system status display
454
+ const statusIcon = healthStatus === 'healthy' || healthStatus === 'ok' ?
455
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>' :
456
+ healthStatus === 'degraded' ?
457
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>' :
458
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';
459
+
460
+ const statusText = healthStatus === 'ok' ? 'Healthy' :
461
+ healthStatus === 'healthy' ? 'Healthy' :
462
+ healthStatus === 'degraded' ? 'Degraded' :
463
+ healthStatus === 'error' ? 'Error' : 'Unknown';
464
+
465
  systemStatusDiv.innerHTML = `
466
+ <div class="system-status-container">
467
+ <div class="system-status-header ${healthClass}">
468
+ <div class="status-icon-wrapper">
469
+ ${statusIcon}
470
+ </div>
471
+ <div>
472
+ <div class="status-title">System Status</div>
473
+ <div class="status-value">${statusText}</div>
474
+ </div>
475
+ </div>
476
+ <div class="system-status-grid">
477
+ <div class="status-item">
478
+ <div class="status-item-label">Total Providers</div>
479
+ <div class="status-item-value">${totalProviders}</div>
480
+ </div>
481
+ <div class="status-item status-online">
482
+ <div class="status-item-label">Online APIs</div>
483
+ <div class="status-item-value">${onlineProviders}</div>
484
+ </div>
485
+ <div class="status-item status-degraded">
486
+ <div class="status-item-label">Degraded APIs</div>
487
+ <div class="status-item-value">${degradedProviders}</div>
488
+ </div>
489
+ <div class="status-item status-offline">
490
+ <div class="status-item-label">Offline APIs</div>
491
+ <div class="status-item-value">${offlineProviders}</div>
492
+ </div>
493
+ <div class="status-item">
494
+ <div class="status-item-label">Avg Response Time</div>
495
+ <div class="status-item-value">${avgResponseTime}ms</div>
496
+ </div>
497
+ <div class="status-item status-full-width">
498
+ <div class="status-item-label">Last Update</div>
499
+ <div class="status-item-value status-time">${formattedTime}</div>
500
+ </div>
501
+ </div>
502
  </div>
503
  `;
504
  }
 
655
 
656
  if (trendingData.trending && trendingData.trending.length > 0) {
657
  trendingDiv.innerHTML = `
658
+ <div class="trending-coins-grid">
659
+ ${trendingData.trending.map((coin, index) => {
660
+ const coinSymbol = coin.symbol || coin.id || 'N/A';
661
+ const coinName = coin.name || 'Unknown';
662
+ const marketCapRank = coin.market_cap_rank || null;
663
+ const score = coin.score !== undefined && coin.score !== null ? coin.score : null;
664
+ const thumb = coin.thumb || null;
665
+
666
+ return `
667
+ <div class="trending-coin-card">
668
+ <div class="trending-coin-rank">#${index + 1}</div>
669
+ <div class="trending-coin-content">
670
+ ${thumb ? `<img src="${thumb}" alt="${coinName}" class="trending-coin-thumb" onerror="this.style.display='none'">` : ''}
671
+ <div class="trending-coin-info">
672
+ <div class="trending-coin-name">
673
+ <strong>${coinSymbol}</strong>
674
+ <span class="trending-coin-fullname">${coinName}</span>
675
+ </div>
676
+ ${marketCapRank ? `<div class="trending-coin-meta">Market Cap Rank: #${marketCapRank}</div>` : ''}
677
  </div>
678
  </div>
679
+ ${score !== null && score > 0 ? `
680
+ <div class="trending-coin-score">
681
+ <div class="trending-coin-score-value">${score.toFixed(2)}</div>
682
+ <div class="trending-coin-score-label">Score</div>
683
+ </div>
684
+ ` : ''}
685
  </div>
686
+ `;
687
+ }).join('')}
688
  </div>
689
  `;
690
  } else {
 
698
  trendingDiv.innerHTML = '<div class="alert alert-warning">Trending data unavailable</div>';
699
  }
700
 
701
+ // Load Fear & Greed Index with enhanced visualization
702
  try {
703
  const sentimentRes = await fetch('/api/sentiment');
704
  if (sentimentRes.ok) {
 
708
  const fgValue = sentimentData.fear_greed_index;
709
  const fgLabel = sentimentData.fear_greed_label || 'Unknown';
710
 
711
+ // Determine sentiment classification and colors
712
+ let sentimentClass = '';
713
+ let sentimentIcon = '';
714
+ let fgColor = '';
715
+ let bgGradient = '';
716
+ let description = '';
717
+
718
+ if (fgValue >= 75) {
719
+ sentimentClass = 'Extreme Greed';
720
+ sentimentIcon = 'πŸš€';
721
+ fgColor = '#10b981';
722
+ bgGradient = 'linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.1) 100%)';
723
+ description = 'Market shows extreme greed. Consider taking profits.';
724
+ } else if (fgValue >= 50) {
725
+ sentimentClass = 'Greed';
726
+ sentimentIcon = 'πŸ“ˆ';
727
+ fgColor = '#3b82f6';
728
+ bgGradient = 'linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(37, 99, 235, 0.1) 100%)';
729
+ description = 'Market sentiment is bullish. Optimistic outlook.';
730
+ } else if (fgValue >= 25) {
731
+ sentimentClass = 'Fear';
732
+ sentimentIcon = '⚠️';
733
+ fgColor = '#f59e0b';
734
+ bgGradient = 'linear-gradient(135deg, rgba(245, 158, 11, 0.2) 0%, rgba(217, 119, 6, 0.1) 100%)';
735
+ description = 'Market shows fear. Caution advised.';
736
+ } else {
737
+ sentimentClass = 'Extreme Fear';
738
+ sentimentIcon = '😱';
739
+ fgColor = '#ef4444';
740
+ bgGradient = 'linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.1) 100%)';
741
+ description = 'Extreme fear in market. Potential buying opportunity.';
742
+ }
743
+
744
+ // Calculate progress bar percentage (0-100)
745
+ const progressPercent = fgValue;
746
+
747
+ // Create circular gauge SVG
748
+ const circumference = 2 * Math.PI * 90; // radius = 90
749
+ const offset = circumference - (progressPercent / 100) * circumference;
750
 
751
  fgDiv.innerHTML = `
752
+ <div style="display: grid; gap: 24px;">
753
+ <!-- Main Fear & Greed Index Display with Circular Gauge -->
754
+ <div style="background: ${bgGradient}; border: 2px solid ${fgColor}40; border-radius: 20px; padding: 40px; text-align: center; position: relative; overflow: hidden;">
755
+ <!-- Background Pattern -->
756
+ <div style="position: absolute; top: -50%; right: -50%; width: 200%; height: 200%; background: radial-gradient(circle, ${fgColor}15 0%, transparent 70%); pointer-events: none; animation: pulse 3s ease-in-out infinite;"></div>
757
+
758
+ <div style="position: relative; z-index: 1;">
759
+ <!-- Circular Gauge -->
760
+ <div style="position: relative; width: 240px; height: 240px; margin: 0 auto 24px;">
761
+ <svg width="240" height="240" style="transform: rotate(-90deg);">
762
+ <!-- Background Circle -->
763
+ <circle cx="120" cy="120" r="90" fill="none" stroke="rgba(255, 255, 255, 0.1)" stroke-width="12" />
764
+ <!-- Progress Circle -->
765
+ <circle
766
+ cx="120"
767
+ cy="120"
768
+ r="90"
769
+ fill="none"
770
+ stroke="${fgColor}"
771
+ stroke-width="12"
772
+ stroke-linecap="round"
773
+ stroke-dasharray="${circumference}"
774
+ stroke-dashoffset="${offset}"
775
+ style="transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1); filter: drop-shadow(0 0 10px ${fgColor}60);"
776
+ />
777
+ </svg>
778
+ <!-- Center Content -->
779
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center;">
780
+ <div style="font-size: 48px; margin-bottom: 8px; filter: drop-shadow(0 2px 4px ${fgColor}40);">
781
+ ${sentimentIcon}
782
+ </div>
783
+ <div style="font-size: 56px; font-weight: 900; line-height: 1; background: linear-gradient(135deg, ${fgColor}, ${fgColor}dd); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
784
+ ${fgValue}
785
+ </div>
786
+ <div style="font-size: 12px; color: var(--text-secondary); margin-top: 4px;">/ 100</div>
787
+ </div>
788
+ </div>
789
+
790
+ <!-- Classification Label -->
791
+ <div style="font-size: 28px; font-weight: 700; color: ${fgColor}; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 2px; text-shadow: 0 0 20px ${fgColor}40;">
792
+ ${sentimentClass}
793
+ </div>
794
+
795
+ <!-- Description -->
796
+ <div style="font-size: 14px; color: var(--text-secondary); max-width: 400px; margin: 0 auto; line-height: 1.6;">
797
+ ${description}
798
+ </div>
799
+ </div>
800
  </div>
801
+
802
+ <!-- Progress Bar Visualization -->
803
+ <div>
804
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
805
+ <span style="font-size: 14px; font-weight: 600; color: var(--text-secondary);">Fear & Greed Index</span>
806
+ <span style="font-size: 14px; font-weight: 700; color: ${fgColor};">${fgValue}/100</span>
807
+ </div>
808
+
809
+ <!-- Progress Bar Container -->
810
+ <div class="sentiment-progress-bar">
811
+ <!-- Progress Fill with Gradient -->
812
+ <div class="sentiment-progress-fill" style="width: ${progressPercent}%; background: linear-gradient(90deg, ${fgColor} 0%, ${fgColor}dd 100%); box-shadow: 0 0 20px ${fgColor}40;">
813
+ <div style="position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: 4px; height: 60%; background: rgba(255, 255, 255, 0.5); border-radius: 2px;"></div>
814
+ </div>
815
+
816
+ <!-- Scale Markers -->
817
+ <div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; align-items: center; padding: 0 8px; pointer-events: none;">
818
+ <span style="font-size: 10px; color: var(--text-secondary); font-weight: 600;">0</span>
819
+ <span style="font-size: 10px; color: var(--text-secondary); font-weight: 600;">25</span>
820
+ <span style="font-size: 10px; color: var(--text-secondary); font-weight: 600;">50</span>
821
+ <span style="font-size: 10px; color: var(--text-secondary); font-weight: 600;">75</span>
822
+ <span style="font-size: 10px; color: var(--text-secondary); font-weight: 600;">100</span>
823
+ </div>
824
+ </div>
825
+
826
+ <!-- Scale Labels -->
827
+ <div style="display: flex; justify-content: space-between; margin-top: 8px; font-size: 11px; color: var(--text-secondary);">
828
+ <span>😱 Extreme Fear</span>
829
+ <span>⚠️ Fear</span>
830
+ <span>😐 Neutral</span>
831
+ <span>πŸ“ˆ Greed</span>
832
+ <span>πŸš€ Extreme Greed</span>
833
+ </div>
834
  </div>
835
+
836
+ <!-- Additional Info Grid -->
837
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-top: 8px;">
838
+ <div style="padding: 16px; background: rgba(0, 0, 0, 0.2); border-radius: 12px; text-align: center; border: 1px solid var(--border);">
839
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">Current Value</div>
840
+ <div style="font-size: 24px; font-weight: 700; color: ${fgColor};">${fgValue}</div>
841
+ </div>
842
+ <div style="padding: 16px; background: rgba(0, 0, 0, 0.2); border-radius: 12px; text-align: center; border: 1px solid var(--border);">
843
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">Classification</div>
844
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);">${fgLabel}</div>
845
+ </div>
846
+ ${sentimentData.timestamp ? `
847
+ <div style="padding: 16px; background: rgba(0, 0, 0, 0.2); border-radius: 12px; text-align: center; border: 1px solid var(--border);">
848
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">Last Update</div>
849
+ <div style="font-size: 12px; font-weight: 600; color: var(--text-primary);">${new Date(sentimentData.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}</div>
850
+ </div>
851
+ ` : ''}
852
+ <div style="padding: 16px; background: rgba(0, 0, 0, 0.2); border-radius: 12px; text-align: center; border: 1px solid var(--border);">
853
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">Source</div>
854
+ <div style="font-size: 12px; font-weight: 600; color: var(--text-primary);">Alternative.me</div>
855
+ </div>
856
+ </div>
857
+
858
+ <!-- Market Interpretation -->
859
+ <div style="padding: 20px; background: rgba(0, 0, 0, 0.3); border-radius: 12px; border-left: 4px solid ${fgColor};">
860
+ <div style="display: flex; align-items: start; gap: 12px;">
861
+ <div style="font-size: 24px;">πŸ’‘</div>
862
+ <div>
863
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">Market Interpretation</div>
864
+ <div style="font-size: 13px; color: var(--text-secondary); line-height: 1.6;">
865
+ ${fgValue >= 75 ?
866
+ 'The market is showing extreme greed. Historically, this may indicate a potential market top. Consider taking profits and being cautious with new positions.' :
867
+ fgValue >= 50 ?
868
+ 'Market sentiment is positive with greed prevailing. This suggests bullish momentum, but monitor for overbought conditions.' :
869
+ fgValue >= 25 ?
870
+ 'Fear is present in the market. This could indicate a buying opportunity for long-term investors, but exercise caution.' :
871
+ 'Extreme fear dominates the market. Historically, this has often been a good time to buy, but ensure you have a solid risk management strategy.'}
872
+ </div>
873
+ </div>
874
+ </div>
875
  </div>
 
 
 
876
  </div>
877
  `;
878
  } else {
 
883
  }
884
  } catch (fgError) {
885
  console.warn('Fear & Greed endpoint error:', fgError);
886
+ fgDiv.innerHTML = `
887
+ <div style="padding: 40px; text-align: center;">
888
+ <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
889
+ <div style="font-size: 18px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">Fear & Greed Index Unavailable</div>
890
+ <div style="font-size: 14px; color: var(--text-secondary);">Unable to fetch sentiment data at this time. Please try again later.</div>
891
+ </div>
892
+ `;
893
  }
894
 
895
  console.log('βœ… Market data loaded successfully');
 
1875
  showToast('βœ… Log downloaded', 'success');
1876
  }
1877
 
1878
+ // Note: Diagnostics initialization is handled in the enhanced section below
1879
+
1880
+ // ===== ENHANCED DIAGNOSTIC FUNCTIONS =====
1881
+
1882
+ let autoRefreshInterval = null;
1883
+ let autoRefreshEnabled = false;
1884
+
1885
+ function toggleAutoRefresh() {
1886
+ autoRefreshEnabled = !autoRefreshEnabled;
1887
+ const btn = document.getElementById('auto-refresh-btn');
1888
+
1889
+ if (autoRefreshEnabled) {
1890
+ btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 4px;"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>Auto: ON (30s)';
1891
+ btn.style.background = 'rgba(16, 185, 129, 0.2)';
1892
+ btn.style.borderColor = 'var(--success)';
1893
+ autoRefreshInterval = setInterval(() => {
1894
+ refreshDiagnosticStatus();
1895
+ loadSystemHealth();
1896
+ loadProviderHealth();
1897
+ }, 30000);
1898
+ getToast().success('Auto-refresh enabled (30s interval)');
1899
+ } else {
1900
+ btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; vertical-align: middle; margin-right: 4px;"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>Auto: OFF';
1901
+ btn.style.background = '';
1902
+ btn.style.borderColor = '';
1903
+ if (autoRefreshInterval) {
1904
+ clearInterval(autoRefreshInterval);
1905
+ autoRefreshInterval = null;
1906
+ }
1907
+ getToast().info('Auto-refresh disabled');
1908
+ }
1909
+ }
1910
+
1911
+ function switchDiagnosticTab(tabName) {
1912
+ // Hide all tabs
1913
+ document.querySelectorAll('.diagnostic-tab-content').forEach(tab => {
1914
+ tab.classList.remove('active');
1915
+ });
1916
+ document.querySelectorAll('.diagnostic-tab-btn').forEach(btn => {
1917
+ btn.classList.remove('active');
1918
+ });
1919
+
1920
+ // Show selected tab
1921
+ document.getElementById(`diagnostic-tab-${tabName}`).classList.add('active');
1922
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
1923
+
1924
+ // Load content if needed
1925
+ if (tabName === 'health' && document.getElementById('health-details-content').innerHTML.includes('Click')) {
1926
+ loadSystemHealth();
1927
+ } else if (tabName === 'logs') {
1928
+ loadRecentLogs();
1929
+ }
1930
+ }
1931
+
1932
+ async function loadSystemHealth() {
1933
+ try {
1934
+ const response = await fetch('/api/diagnostics/health');
1935
+ if (!response.ok) throw new Error('Failed to fetch health data');
1936
+
1937
+ const data = await response.json();
1938
+ const container = document.getElementById('system-health-overview');
1939
+
1940
+ if (!container) return;
1941
+
1942
+ const providers = data.providers?.summary || {};
1943
+ const models = data.models?.summary || {};
1944
+ const overall = data.overall_health || {};
1945
+
1946
+ container.innerHTML = `
1947
+ <div class="stat-card gradient-blue">
1948
+ <div class="stat-icon">
1949
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1950
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
1951
+ <polyline points="22 4 12 14.01 9 11.01"></polyline>
1952
+ </svg>
1953
+ </div>
1954
+ <div class="stat-content">
1955
+ <div class="stat-label">Providers</div>
1956
+ <div class="stat-value">${providers.healthy || 0}/${providers.total || 0}</div>
1957
+ <div class="stat-trend" style="color: ${overall.providers_ok ? 'var(--success)' : 'var(--warning)'};">
1958
+ ${overall.providers_ok ? 'βœ… Healthy' : '⚠️ Degraded'}
1959
+ </div>
1960
+ </div>
1961
+ </div>
1962
+ <div class="stat-card gradient-purple">
1963
+ <div class="stat-icon">
1964
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1965
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
1966
+ </svg>
1967
+ </div>
1968
+ <div class="stat-content">
1969
+ <div class="stat-label">AI Models</div>
1970
+ <div class="stat-value">${models.healthy || 0}/${models.total || 0}</div>
1971
+ <div class="stat-trend" style="color: ${overall.models_ok ? 'var(--success)' : 'var(--warning)'};">
1972
+ ${overall.models_ok ? 'βœ… Healthy' : '⚠️ Degraded'}
1973
+ </div>
1974
+ </div>
1975
+ </div>
1976
+ <div class="stat-card gradient-orange">
1977
+ <div class="stat-icon">
1978
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1979
+ <circle cx="12" cy="12" r="10"></circle>
1980
+ <path d="M12 6v6l4 2"></path>
1981
+ </svg>
1982
+ </div>
1983
+ <div class="stat-content">
1984
+ <div class="stat-label">In Cooldown</div>
1985
+ <div class="stat-value">${(providers.in_cooldown || 0) + (models.in_cooldown || 0)}</div>
1986
+ <div class="stat-trend">${(providers.in_cooldown || 0) + (models.in_cooldown || 0) > 0 ? '⚠️ Some services cooling' : 'βœ… All active'}</div>
1987
+ </div>
1988
+ </div>
1989
+ <div class="stat-card gradient-green">
1990
+ <div class="stat-icon">
1991
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1992
+ <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
1993
+ </svg>
1994
+ </div>
1995
+ <div class="stat-content">
1996
+ <div class="stat-label">Degraded</div>
1997
+ <div class="stat-value">${(providers.degraded || 0) + (models.degraded || 0)}</div>
1998
+ <div class="stat-trend">${(providers.degraded || 0) + (models.degraded || 0) > 0 ? '⚠️ Needs attention' : 'βœ… All optimal'}</div>
1999
+ </div>
2000
+ </div>
2001
+ `;
2002
+
2003
+ // Update health details tab
2004
+ const healthDetails = document.getElementById('health-details-content');
2005
+ if (healthDetails) {
2006
+ healthDetails.innerHTML = `
2007
+ <div style="display: grid; gap: 16px;">
2008
+ <div>
2009
+ <h4 style="margin-bottom: 12px;">Provider Health Summary</h4>
2010
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
2011
+ <div style="padding: 12px; background: rgba(0,0,0,0.2); border-radius: 8px;">
2012
+ <div style="color: var(--text-secondary); font-size: 12px;">Total</div>
2013
+ <div style="font-size: 24px; font-weight: 700;">${providers.total || 0}</div>
2014
+ </div>
2015
+ <div style="padding: 12px; background: rgba(16,185,129,0.1); border-radius: 8px; border: 1px solid rgba(16,185,129,0.3);">
2016
+ <div style="color: var(--success); font-size: 12px;">Healthy</div>
2017
+ <div style="font-size: 24px; font-weight: 700; color: var(--success);">${providers.healthy || 0}</div>
2018
+ </div>
2019
+ <div style="padding: 12px; background: rgba(245,158,11,0.1); border-radius: 8px; border: 1px solid rgba(245,158,11,0.3);">
2020
+ <div style="color: var(--warning); font-size: 12px;">Degraded</div>
2021
+ <div style="font-size: 24px; font-weight: 700; color: var(--warning);">${providers.degraded || 0}</div>
2022
+ </div>
2023
+ <div style="padding: 12px; background: rgba(239,68,68,0.1); border-radius: 8px; border: 1px solid rgba(239,68,68,0.3);">
2024
+ <div style="color: var(--danger); font-size: 12px;">Unavailable</div>
2025
+ <div style="font-size: 24px; font-weight: 700; color: var(--danger);">${providers.unavailable || 0}</div>
2026
+ </div>
2027
+ </div>
2028
+ </div>
2029
+ <div>
2030
+ <h4 style="margin-bottom: 12px;">Model Health Summary</h4>
2031
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
2032
+ <div style="padding: 12px; background: rgba(0,0,0,0.2); border-radius: 8px;">
2033
+ <div style="color: var(--text-secondary); font-size: 12px;">Total</div>
2034
+ <div style="font-size: 24px; font-weight: 700;">${models.total || 0}</div>
2035
+ </div>
2036
+ <div style="padding: 12px; background: rgba(16,185,129,0.1); border-radius: 8px; border: 1px solid rgba(16,185,129,0.3);">
2037
+ <div style="color: var(--success); font-size: 12px;">Healthy</div>
2038
+ <div style="font-size: 24px; font-weight: 700; color: var(--success);">${models.healthy || 0}</div>
2039
+ </div>
2040
+ <div style="padding: 12px; background: rgba(245,158,11,0.1); border-radius: 8px; border: 1px solid rgba(245,158,11,0.3);">
2041
+ <div style="color: var(--warning); font-size: 12px;">Degraded</div>
2042
+ <div style="font-size: 24px; font-weight: 700; color: var(--warning);">${models.degraded || 0}</div>
2043
+ </div>
2044
+ <div style="padding: 12px; background: rgba(239,68,68,0.1); border-radius: 8px; border: 1px solid rgba(239,68,68,0.3);">
2045
+ <div style="color: var(--danger); font-size: 12px;">Unavailable</div>
2046
+ <div style="font-size: 24px; font-weight: 700; color: var(--danger);">${models.unavailable || 0}</div>
2047
+ </div>
2048
+ </div>
2049
+ </div>
2050
+ </div>
2051
+ `;
2052
+ }
2053
+ } catch (error) {
2054
+ console.error('Error loading system health:', error);
2055
+ getToast().error('Failed to load system health');
2056
+ }
2057
+ }
2058
+
2059
+ async function loadProviderHealth() {
2060
+ try {
2061
+ const response = await fetch('/api/diagnostics/health');
2062
+ if (!response.ok) throw new Error('Failed to fetch provider health');
2063
+
2064
+ const data = await response.json();
2065
+ const tbody = document.getElementById('provider-health-table');
2066
+ if (!tbody) return;
2067
+
2068
+ const providers = data.providers?.entries || [];
2069
+ const models = data.models?.entries || [];
2070
+
2071
+ let html = '';
2072
+
2073
+ // Add providers
2074
+ providers.slice(0, 10).forEach(entry => {
2075
+ const statusClass = entry.status === 'healthy' ? 'healthy' :
2076
+ entry.status === 'degraded' ? 'degraded' :
2077
+ entry.status === 'unavailable' ? 'unavailable' : 'unknown';
2078
+ const lastCheck = entry.last_success ? new Date(entry.last_success * 1000).toLocaleString() : 'Never';
2079
+ html += `
2080
+ <tr style="border-bottom: 1px solid var(--border);">
2081
+ <td style="padding: 12px;">${entry.name || entry.id}</td>
2082
+ <td style="padding: 12px;"><span style="color: var(--text-secondary);">Provider</span></td>
2083
+ <td style="padding: 12px;"><span class="health-badge ${statusClass}">${entry.status || 'unknown'}</span></td>
2084
+ <td style="padding: 12px; color: var(--text-secondary); font-size: 12px;">${lastCheck}</td>
2085
+ <td style="padding: 12px;">
2086
+ ${entry.in_cooldown ? '<span style="color: var(--warning); font-size: 12px;">⏳ Cooldown</span>' : '-'}
2087
+ </td>
2088
+ </tr>
2089
+ `;
2090
+ });
2091
+
2092
+ // Add models
2093
+ models.slice(0, 10).forEach(entry => {
2094
+ const statusClass = entry.status === 'healthy' ? 'healthy' :
2095
+ entry.status === 'degraded' ? 'degraded' :
2096
+ entry.status === 'unavailable' ? 'unavailable' : 'unknown';
2097
+ html += `
2098
+ <tr style="border-bottom: 1px solid var(--border);">
2099
+ <td style="padding: 12px;">${entry.name || entry.key || 'Unknown'}</td>
2100
+ <td style="padding: 12px;"><span style="color: var(--text-secondary);">AI Model</span></td>
2101
+ <td style="padding: 12px;"><span class="health-badge ${statusClass}">${entry.status || 'unknown'}</span></td>
2102
+ <td style="padding: 12px; color: var(--text-secondary); font-size: 12px;">-</td>
2103
+ <td style="padding: 12px;">
2104
+ ${entry.in_cooldown ? '<span style="color: var(--warning); font-size: 12px;">⏳ Cooldown</span>' : '-'}
2105
+ </td>
2106
+ </tr>
2107
+ `;
2108
+ });
2109
+
2110
+ if (html === '') {
2111
+ html = '<tr><td colspan="5" style="padding: 20px; text-align: center; color: var(--text-secondary);">No health data available</td></tr>';
2112
+ }
2113
+
2114
+ tbody.innerHTML = html;
2115
+ } catch (error) {
2116
+ console.error('Error loading provider health:', error);
2117
+ getToast().error('Failed to load provider health');
2118
+ }
2119
+ }
2120
+
2121
+ async function triggerSelfHeal() {
2122
+ try {
2123
+ getToast().info('Triggering self-healing...');
2124
+ const response = await fetch('/api/diagnostics/self-heal', { method: 'POST' });
2125
+ const data = await response.json();
2126
+
2127
+ if (data.status === 'completed') {
2128
+ getToast().success(`Self-healing completed: ${data.summary.successful} successful, ${data.summary.failed} failed`);
2129
+ loadProviderHealth();
2130
+ loadSystemHealth();
2131
+ } else {
2132
+ getToast().error('Self-healing failed: ' + (data.error || 'Unknown error'));
2133
+ }
2134
+ } catch (error) {
2135
+ console.error('Error triggering self-heal:', error);
2136
+ getToast().error('Failed to trigger self-healing');
2137
+ }
2138
+ }
2139
+
2140
+ async function testAPIEndpoints() {
2141
+ const resultsDiv = document.getElementById('api-test-results');
2142
+ if (!resultsDiv) return;
2143
+
2144
+ resultsDiv.innerHTML = '<div class="spinner"></div> <span>Testing API endpoints...</span>';
2145
+
2146
+ const endpoints = [
2147
+ { name: 'Health Check', url: '/api/health' },
2148
+ { name: 'System Status', url: '/api/status' },
2149
+ { name: 'Market Data', url: '/api/market' },
2150
+ { name: 'Models Status', url: '/api/models/status' },
2151
+ { name: 'Providers', url: '/api/providers' },
2152
+ ];
2153
+
2154
+ let html = '<div style="display: grid; gap: 12px;">';
2155
+ let passed = 0;
2156
+ let failed = 0;
2157
+
2158
+ for (const endpoint of endpoints) {
2159
+ try {
2160
+ const startTime = performance.now();
2161
+ const response = await fetch(endpoint.url);
2162
+ const duration = (performance.now() - startTime).toFixed(0);
2163
+
2164
+ if (response.ok) {
2165
+ passed++;
2166
+ html += `
2167
+ <div style="padding: 12px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); border-radius: 8px;">
2168
+ <div style="display: flex; justify-content: space-between; align-items: center;">
2169
+ <div>
2170
+ <strong style="color: var(--success);">βœ… ${endpoint.name}</strong>
2171
+ <div style="color: var(--text-secondary); font-size: 12px; margin-top: 4px;">${endpoint.url}</div>
2172
+ </div>
2173
+ <div style="color: var(--text-secondary); font-size: 12px;">${duration}ms</div>
2174
+ </div>
2175
+ </div>
2176
+ `;
2177
+ } else {
2178
+ failed++;
2179
+ html += `
2180
+ <div style="padding: 12px; background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 8px;">
2181
+ <div style="display: flex; justify-content: space-between; align-items: center;">
2182
+ <div>
2183
+ <strong style="color: var(--danger);">❌ ${endpoint.name}</strong>
2184
+ <div style="color: var(--text-secondary); font-size: 12px; margin-top: 4px;">${endpoint.url} - HTTP ${response.status}</div>
2185
+ </div>
2186
+ </div>
2187
+ </div>
2188
+ `;
2189
+ }
2190
+ } catch (error) {
2191
+ failed++;
2192
+ html += `
2193
+ <div style="padding: 12px; background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 8px;">
2194
+ <div>
2195
+ <strong style="color: var(--danger);">❌ ${endpoint.name}</strong>
2196
+ <div style="color: var(--text-secondary); font-size: 12px; margin-top: 4px;">${endpoint.url} - ${error.message}</div>
2197
+ </div>
2198
+ </div>
2199
+ `;
2200
+ }
2201
+ }
2202
+
2203
+ html += `</div><div style="margin-top: 16px; padding: 12px; background: rgba(0,0,0,0.2); border-radius: 8px;">
2204
+ <strong>Summary:</strong> ${passed} passed, ${failed} failed
2205
+ </div>`;
2206
+
2207
+ resultsDiv.innerHTML = html;
2208
+ getToast().success(`API tests completed: ${passed} passed, ${failed} failed`);
2209
+ }
2210
+
2211
+ async function checkDatabaseHealth() {
2212
+ try {
2213
+ getToast().info('Checking database health...');
2214
+ const response = await fetch('/api/diagnostics/run?auto_fix=false');
2215
+ const data = await response.json();
2216
+
2217
+ const output = document.getElementById('diagnostic-output');
2218
+ if (output) {
2219
+ output.textContent = JSON.stringify(data, null, 2);
2220
+ }
2221
+
2222
+ if (data.issues_found === 0) {
2223
+ getToast().success('Database health check passed');
2224
+ } else {
2225
+ getToast().warning(`Database health check found ${data.issues_found} issues`);
2226
+ }
2227
+ } catch (error) {
2228
+ console.error('Error checking database:', error);
2229
+ getToast().error('Failed to check database health');
2230
+ }
2231
+ }
2232
+
2233
+ async function testNetworkConnectivity() {
2234
+ const output = document.getElementById('diagnostic-output');
2235
+ if (output) {
2236
+ output.textContent = 'Testing network connectivity...\n';
2237
+ }
2238
+
2239
+ const endpoints = [
2240
+ { name: 'HuggingFace Hub', url: 'https://huggingface.co' },
2241
+ { name: 'CoinGecko API', url: 'https://api.coingecko.com/api/v3/ping' },
2242
+ { name: 'Alternative.me', url: 'https://api.alternative.me/fng/' },
2243
+ ];
2244
+
2245
+ let results = 'Network Connectivity Test Results:\n' + '='.repeat(50) + '\n\n';
2246
+
2247
+ for (const endpoint of endpoints) {
2248
+ try {
2249
+ const startTime = performance.now();
2250
+ const response = await fetch(endpoint.url, { method: 'HEAD', mode: 'no-cors' });
2251
+ const duration = (performance.now() - startTime).toFixed(0);
2252
+ results += `βœ… ${endpoint.name}: Reachable (${duration}ms)\n`;
2253
+ } catch (error) {
2254
+ results += `❌ ${endpoint.name}: ${error.message}\n`;
2255
+ }
2256
+ }
2257
+
2258
+ if (output) {
2259
+ output.textContent = results;
2260
+ }
2261
+ getToast().success('Network connectivity test completed');
2262
+ }
2263
+
2264
+ async function loadRecentLogs() {
2265
+ try {
2266
+ const response = await fetch('/api/logs/recent');
2267
+ const data = await response.json();
2268
+ const container = document.getElementById('recent-logs-content');
2269
+
2270
+ if (!container) return;
2271
+
2272
+ if (data.logs && data.logs.length > 0) {
2273
+ let html = '<div style="display: grid; gap: 8px;">';
2274
+ data.logs.slice(0, 20).forEach(log => {
2275
+ const level = log.level || 'INFO';
2276
+ const levelColor = level === 'ERROR' ? 'var(--danger)' :
2277
+ level === 'WARNING' ? 'var(--warning)' :
2278
+ level === 'INFO' ? 'var(--info)' : 'var(--text-secondary)';
2279
+ html += `
2280
+ <div style="padding: 10px; background: rgba(0,0,0,0.2); border-left: 3px solid ${levelColor}; border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 12px;">
2281
+ <div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
2282
+ <span style="color: ${levelColor}; font-weight: 600;">[${level}]</span>
2283
+ <span style="color: var(--text-secondary);">${log.timestamp || ''}</span>
2284
+ </div>
2285
+ <div style="color: var(--text-primary);">${log.message || JSON.stringify(log)}</div>
2286
+ </div>
2287
+ `;
2288
+ });
2289
+ html += '</div>';
2290
+ container.innerHTML = html;
2291
+ } else {
2292
+ container.innerHTML = '<p class="text-secondary">No recent logs available</p>';
2293
+ }
2294
+ } catch (error) {
2295
+ console.error('Error loading logs:', error);
2296
+ document.getElementById('recent-logs-content').innerHTML = '<p style="color: var(--danger);">Failed to load logs</p>';
2297
+ }
2298
+ }
2299
 
2300
  // Export diagnostic functions to window
2301
  window.runDiagnostic = runDiagnostic;
2302
  window.refreshDiagnosticStatus = refreshDiagnosticStatus;
2303
  window.downloadDiagnosticLog = downloadDiagnosticLog;
2304
+ window.toggleAutoRefresh = toggleAutoRefresh;
2305
+ window.switchDiagnosticTab = switchDiagnosticTab;
2306
+ window.loadSystemHealth = loadSystemHealth;
2307
+ window.loadProviderHealth = loadProviderHealth;
2308
+ window.triggerSelfHeal = triggerSelfHeal;
2309
+ window.testAPIEndpoints = testAPIEndpoints;
2310
+ window.checkDatabaseHealth = checkDatabaseHealth;
2311
+ window.testNetworkConnectivity = testNetworkConnectivity;
2312
+ window.loadRecentLogs = loadRecentLogs;
2313
+
2314
+ // ===== AI TOOLS LOADER =====
2315
+ function loadAITools() {
2316
+ const iframe = document.getElementById('ai-tools-iframe');
2317
+ const loading = document.getElementById('ai-tools-loading');
2318
+
2319
+ if (!iframe) return;
2320
+
2321
+ // Show loading, hide iframe
2322
+ if (loading) loading.style.display = 'block';
2323
+ iframe.style.display = 'none';
2324
+
2325
+ // Reload iframe if it already has content, or just show it
2326
+ if (iframe.src && iframe.src.includes('/ai-tools')) {
2327
+ // Iframe already loaded, just show it
2328
+ setTimeout(() => {
2329
+ if (loading) loading.style.display = 'none';
2330
+ iframe.style.display = 'block';
2331
+ }, 100);
2332
+ } else {
2333
+ // Set src to load the page
2334
+ iframe.src = '/ai-tools';
2335
+ }
2336
+ }
2337
+
2338
+ function handleAIToolsIframeLoad() {
2339
+ const iframe = document.getElementById('ai-tools-iframe');
2340
+ const loading = document.getElementById('ai-tools-loading');
2341
+
2342
+ if (loading) loading.style.display = 'none';
2343
+ if (iframe) iframe.style.display = 'block';
2344
+
2345
+ console.log('βœ… AI Tools iframe loaded successfully');
2346
+ }
2347
+
2348
+ window.loadAITools = loadAITools;
2349
+ window.handleAIToolsIframeLoad = handleAIToolsIframeLoad;
2350
+
2351
+ // Initialize diagnostics on page load
2352
+ document.addEventListener('DOMContentLoaded', function() {
2353
+ refreshDiagnosticStatus();
2354
+ loadSystemHealth();
2355
+ loadProviderHealth();
2356
+ });
2357
 
2358
  console.log('βœ… App.js loaded successfully');