Really-amin commited on
Commit
09ae9cc
·
verified ·
1 Parent(s): 452f691

Upload admin.html

Browse files
Files changed (1) hide show
  1. admin.html +461 -982
admin.html CHANGED
@@ -1,1017 +1,496 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Admin Dashboard - Crypto Monitor</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
-
10
- :root {
11
- --primary: #667eea;
12
- --primary-dark: #5568d3;
13
- --success: #48bb78;
14
- --warning: #ed8936;
15
- --danger: #f56565;
16
- --bg-dark: #1a202c;
17
- --bg-card: #2d3748;
18
- --text-light: #e2e8f0;
19
- --text-muted: #a0aec0;
20
- --border: #4a5568;
21
- }
22
-
23
- body {
24
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
25
- background: var(--bg-dark);
26
- color: var(--text-light);
27
- line-height: 1.6;
28
- }
29
-
30
- .container {
31
- max-width: 1400px;
32
- margin: 0 auto;
33
- padding: 20px;
34
- }
35
-
36
- header {
37
- background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
38
- padding: 20px;
39
- border-radius: 10px;
40
- margin-bottom: 30px;
41
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
42
- }
43
-
44
- header h1 {
45
- font-size: 28px;
46
- font-weight: 700;
47
- margin-bottom: 5px;
48
- }
49
-
50
- header .subtitle {
51
- color: rgba(255, 255, 255, 0.9);
52
- font-size: 14px;
53
- }
54
-
55
- .tabs {
56
- display: flex;
57
- gap: 10px;
58
- margin-bottom: 30px;
59
- flex-wrap: wrap;
60
- }
61
-
62
- .tab-btn {
63
- padding: 12px 24px;
64
- background: var(--bg-card);
65
- border: 2px solid var(--border);
66
- border-radius: 8px;
67
- cursor: pointer;
68
- font-weight: 600;
69
- color: var(--text-light);
70
- transition: all 0.3s;
71
- }
72
-
73
- .tab-btn:hover {
74
- background: var(--primary);
75
- border-color: var(--primary);
76
- }
77
-
78
- .tab-btn.active {
79
- background: var(--primary);
80
- border-color: var(--primary);
81
- }
82
-
83
- .tab-content {
84
- display: none;
85
- animation: fadeIn 0.3s;
86
- }
87
-
88
- .tab-content.active {
89
- display: block;
90
- }
91
-
92
- @keyframes fadeIn {
93
- from { opacity: 0; transform: translateY(10px); }
94
- to { opacity: 1; transform: translateY(0); }
95
- }
96
-
97
- .card {
98
- background: var(--bg-card);
99
- border-radius: 10px;
100
- padding: 20px;
101
- margin-bottom: 20px;
102
- border: 1px solid var(--border);
103
- }
104
-
105
- .card h3 {
106
- color: var(--primary);
107
- margin-bottom: 15px;
108
- font-size: 18px;
109
- }
110
-
111
- .stats-grid {
112
- display: grid;
113
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
114
- gap: 15px;
115
- margin-bottom: 20px;
116
- }
117
-
118
- .stat-card {
119
- background: var(--bg-card);
120
- padding: 20px;
121
- border-radius: 8px;
122
- border: 1px solid var(--border);
123
- }
124
-
125
- .stat-card .label {
126
- color: var(--text-muted);
127
- font-size: 12px;
128
- text-transform: uppercase;
129
- letter-spacing: 0.5px;
130
- }
131
-
132
- .stat-card .value {
133
- font-size: 32px;
134
- font-weight: 700;
135
- color: var(--primary);
136
- margin: 5px 0;
137
- }
138
-
139
- .stat-card .badge {
140
- display: inline-block;
141
- padding: 4px 8px;
142
- border-radius: 4px;
143
- font-size: 11px;
144
- font-weight: 600;
145
- }
146
-
147
- .badge-success {
148
- background: var(--success);
149
- color: white;
150
- }
151
-
152
- .badge-warning {
153
- background: var(--warning);
154
- color: white;
155
- }
156
-
157
- .badge-danger {
158
- background: var(--danger);
159
- color: white;
160
- }
161
-
162
- .btn {
163
- padding: 10px 20px;
164
- border: none;
165
- border-radius: 6px;
166
- cursor: pointer;
167
- font-weight: 600;
168
- transition: all 0.2s;
169
- margin-right: 10px;
170
- margin-bottom: 10px;
171
- }
172
-
173
- .btn-primary {
174
- background: var(--primary);
175
- color: white;
176
- }
177
-
178
- .btn-primary:hover {
179
- background: var(--primary-dark);
180
- }
181
-
182
- .btn-success {
183
- background: var(--success);
184
- color: white;
185
- }
186
-
187
- .btn-success:hover {
188
- background: #38a169;
189
- }
190
-
191
- .btn-secondary {
192
- background: var(--bg-card);
193
- color: var(--text-light);
194
- border: 1px solid var(--border);
195
- }
196
-
197
- .btn-secondary:hover {
198
- background: var(--border);
199
- }
200
-
201
- table {
202
- width: 100%;
203
- border-collapse: collapse;
204
- margin-top: 15px;
205
- }
206
-
207
- table thead {
208
- background: var(--bg-dark);
209
- }
210
-
211
- table th {
212
- padding: 12px;
213
- text-align: left;
214
- font-weight: 600;
215
- font-size: 12px;
216
- text-transform: uppercase;
217
- color: var(--text-muted);
218
- }
219
-
220
- table td {
221
- padding: 12px;
222
- border-top: 1px solid var(--border);
223
- }
224
-
225
- table tbody tr:hover {
226
- background: var(--bg-dark);
227
- }
228
-
229
- .status-online {
230
- color: var(--success);
231
- }
232
-
233
- .status-offline {
234
- color: var(--danger);
235
- }
236
-
237
- .status-degraded {
238
- color: var(--warning);
239
- }
240
-
241
- .loading {
242
- text-align: center;
243
- padding: 40px;
244
- color: var(--text-muted);
245
- }
246
-
247
- .error-message {
248
- background: var(--danger);
249
- color: white;
250
- padding: 15px;
251
- border-radius: 8px;
252
- margin-bottom: 20px;
253
- }
254
-
255
- .success-message {
256
- background: var(--success);
257
- color: white;
258
- padding: 15px;
259
- border-radius: 8px;
260
- margin-bottom: 20px;
261
- }
262
-
263
- .empty-state {
264
- text-align: center;
265
- padding: 60px 20px;
266
- color: var(--text-muted);
267
- }
268
-
269
- .empty-state svg {
270
- width: 64px;
271
- height: 64px;
272
- margin-bottom: 20px;
273
- opacity: 0.3;
274
- }
275
-
276
- .filter-bar {
277
- display: flex;
278
- gap: 10px;
279
- margin-bottom: 20px;
280
- flex-wrap: wrap;
281
- }
282
-
283
- select, input {
284
- padding: 10px;
285
- border-radius: 6px;
286
- border: 1px solid var(--border);
287
- background: var(--bg-dark);
288
- color: var(--text-light);
289
- }
290
-
291
- .log-entry {
292
- padding: 10px;
293
- border-left: 3px solid var(--primary);
294
- margin-bottom: 10px;
295
- background: var(--bg-dark);
296
- border-radius: 4px;
297
- }
298
-
299
- .log-entry.error {
300
- border-left-color: var(--danger);
301
- }
302
-
303
- .log-timestamp {
304
- color: var(--text-muted);
305
- font-size: 12px;
306
- }
307
-
308
- pre {
309
- background: var(--bg-dark);
310
- padding: 15px;
311
- border-radius: 6px;
312
- overflow-x: auto;
313
- font-size: 13px;
314
- line-height: 1.4;
315
- }
316
-
317
- .model-card {
318
- background: var(--bg-dark);
319
- padding: 15px;
320
- border-radius: 8px;
321
- margin-bottom: 15px;
322
- border-left: 4px solid var(--primary);
323
- }
324
-
325
- .model-card.valid {
326
- border-left-color: var(--success);
327
- }
328
-
329
- .model-card.conditional {
330
- border-left-color: var(--warning);
331
- }
332
-
333
- .model-card.invalid {
334
- border-left-color: var(--danger);
335
- }
336
-
337
- @media (max-width: 768px) {
338
- .stats-grid {
339
- grid-template-columns: 1fr;
340
- }
341
-
342
- .tabs {
343
- flex-direction: column;
344
- }
345
-
346
- table {
347
- font-size: 12px;
348
- }
349
-
350
- table th, table td {
351
- padding: 8px;
352
- }
353
- }
354
- </style>
355
  </head>
356
- <body>
357
- <div class="container">
358
- <header>
359
- <h1>🚀 Crypto Monitor Admin Dashboard</h1>
360
- <p class="subtitle">Real-time provider management & system monitoring | NO MOCK DATA</p>
361
- </header>
362
-
363
- <div class="tabs">
364
- <button class="tab-btn active" onclick="switchTab('status')">📊 Status</button>
365
- <button class="tab-btn" onclick="switchTab('providers')">🔌 Providers</button>
366
- <button class="tab-btn" onclick="switchTab('market')">💰 Market Data</button>
367
- <button class="tab-btn" onclick="switchTab('apl')">🤖 APL Scanner</button>
368
- <button class="tab-btn" onclick="switchTab('hf-models')">🧠 HF Models</button>
369
- <button class="tab-btn" onclick="switchTab('diagnostics')">🔧 Diagnostics</button>
370
- <button class="tab-btn" onclick="switchTab('logs')">📝 Logs</button>
371
- </div>
372
-
373
- <!-- Status Tab -->
374
- <div id="tab-status" class="tab-content active">
375
- <div class="stats-grid" id="global-stats">
376
- <div class="stat-card">
377
- <div class="label">System Health</div>
378
- <div class="value" id="system-health">-</div>
379
- <span class="badge badge-success" id="health-badge">Healthy</span>
380
- </div>
381
- <div class="stat-card">
382
- <div class="label">Total Providers</div>
383
- <div class="value" id="total-providers">-</div>
384
- </div>
385
- <div class="stat-card">
386
- <div class="label">Validated</div>
387
- <div class="value" id="validated-providers">-</div>
388
- </div>
389
- <div class="stat-card">
390
- <div class="label">Database</div>
391
- <div class="value">✓</div>
392
- <span class="badge badge-success">Connected</span>
393
- </div>
394
  </div>
395
-
396
- <div class="card">
397
- <h3>Quick Actions</h3>
398
- <button class="btn btn-primary" onclick="refreshAllData()">🔄 Refresh All</button>
399
- <button class="btn btn-success" onclick="runAPL()">🤖 Run APL Scan</button>
400
- <button class="btn btn-secondary" onclick="runDiagnostics()">🔧 Run Diagnostics</button>
 
 
 
 
 
 
 
 
401
  </div>
402
-
403
- <div class="card">
404
- <h3>Recent Market Data</h3>
405
- <div id="quick-market-view"></div>
406
- </div>
407
- </div>
408
-
409
- <!-- Providers Tab -->
410
- <div id="tab-providers" class="tab-content">
411
- <div class="card">
412
- <h3>Providers Management</h3>
413
- <div class="filter-bar">
414
- <select id="category-filter" onchange="filterProviders()">
415
- <option value="">All Categories</option>
416
- <option value="market_data">Market Data</option>
417
- <option value="sentiment">Sentiment</option>
418
- <option value="defi">DeFi</option>
419
- <option value="exchange">Exchange</option>
420
- <option value="explorer">Explorer</option>
421
- <option value="rpc">RPC</option>
422
- <option value="news">News</option>
423
- </select>
424
- <button class="btn btn-secondary" onclick="loadProviders()">🔄 Refresh</button>
425
  </div>
426
- <div id="providers-table"></div>
427
- </div>
428
- </div>
429
-
430
- <!-- Market Data Tab -->
431
- <div id="tab-market" class="tab-content">
432
- <div class="card">
433
- <h3>Live Market Data</h3>
434
- <button class="btn btn-primary" onclick="loadMarketData()">🔄 Refresh Prices</button>
435
- <div id="market-data-container"></div>
436
- </div>
437
-
438
- <div class="card">
439
- <h3>Sentiment Analysis</h3>
440
- <div id="sentiment-data"></div>
441
- </div>
442
-
443
- <div class="card">
444
- <h3>Trending Coins</h3>
445
- <div id="trending-coins"></div>
446
- </div>
447
- </div>
448
-
449
- <!-- APL Tab -->
450
- <div id="tab-apl" class="tab-content">
451
- <div class="card">
452
- <h3>Auto Provider Loader (APL)</h3>
453
- <p style="color: var(--text-muted); margin-bottom: 20px;">
454
- APL automatically discovers, validates, and integrates cryptocurrency data providers.
455
- All validations use REAL API calls - NO MOCK DATA.
456
- </p>
457
-
458
- <button class="btn btn-success" onclick="runAPL()" id="apl-run-btn">
459
- 🤖 Run APL Scan
460
- </button>
461
- <button class="btn btn-secondary" onclick="loadAPLReport()">📊 View Last Report</button>
462
-
463
- <div id="apl-status" style="margin-top: 20px;"></div>
464
- </div>
465
-
466
- <div class="card">
467
- <h3>APL Summary Statistics</h3>
468
- <div id="apl-summary"></div>
469
- </div>
470
-
471
- <div class="card">
472
- <h3>APL Output</h3>
473
- <pre id="apl-output" style="max-height: 400px; overflow-y: auto;">No output yet. Click "Run APL Scan" to start.</pre>
474
- </div>
475
- </div>
476
-
477
- <!-- HF Models Tab -->
478
- <div id="tab-hf-models" class="tab-content">
479
- <div class="card">
480
- <h3>Hugging Face Models</h3>
481
- <p style="color: var(--text-muted); margin-bottom: 20px;">
482
- HuggingFace models validated by APL for crypto sentiment analysis and NLP tasks.
483
- </p>
484
- <button class="btn btn-primary" onclick="loadHFModels()">🔄 Refresh Models</button>
485
- <div id="hf-models-container"></div>
486
- </div>
487
-
488
- <div class="card">
489
- <h3>HF Services Health</h3>
490
- <div id="hf-health"></div>
491
- </div>
492
- </div>
493
-
494
- <!-- Diagnostics Tab -->
495
- <div id="tab-diagnostics" class="tab-content">
496
- <div class="card">
497
- <h3>System Diagnostics</h3>
498
- <button class="btn btn-primary" onclick="runDiagnostics(true)">🔧 Run with Auto-Fix</button>
499
- <button class="btn btn-secondary" onclick="runDiagnostics(false)">🔍 Run Scan Only</button>
500
- <button class="btn btn-secondary" onclick="loadLastDiagnostics()">📋 View Last Results</button>
501
-
502
- <div id="diagnostics-results" style="margin-top: 20px;"></div>
503
- </div>
504
- </div>
505
-
506
- <!-- Logs Tab -->
507
- <div id="tab-logs" class="tab-content">
508
- <div class="card">
509
- <h3>System Logs</h3>
510
- <button class="btn btn-primary" onclick="loadRecentLogs()">🔄 Refresh</button>
511
- <button class="btn btn-danger" onclick="loadErrorLogs()">❌ Errors Only</button>
512
-
513
- <div id="logs-container" style="margin-top: 20px;"></div>
514
- </div>
515
- </div>
516
- </div>
517
-
518
- <script src="/static/js/api-client.js"></script>
519
- <script>
520
- // Tab switching
521
- function switchTab(tabName) {
522
- // Hide all tabs
523
- document.querySelectorAll('.tab-content').forEach(tab => {
524
- tab.classList.remove('active');
525
- });
526
- document.querySelectorAll('.tab-btn').forEach(btn => {
527
- btn.classList.remove('active');
528
- });
529
-
530
- // Show selected tab
531
- document.getElementById(`tab-${tabName}`).classList.add('active');
532
- event.target.classList.add('active');
533
-
534
- // Load data for tab
535
- switch(tabName) {
536
- case 'status':
537
- loadGlobalStatus();
538
- break;
539
- case 'providers':
540
- loadProviders();
541
- break;
542
- case 'market':
543
- loadMarketData();
544
- loadSentiment();
545
- loadTrending();
546
- break;
547
- case 'apl':
548
- loadAPLSummary();
549
- break;
550
- case 'hf-models':
551
- loadHFModels();
552
- loadHFHealth();
553
- break;
554
- case 'diagnostics':
555
- loadLastDiagnostics();
556
- break;
557
- case 'logs':
558
- loadRecentLogs();
559
- break;
560
- }
561
- }
562
-
563
- // Global Status
564
- async function loadGlobalStatus() {
565
- try {
566
- const [status, stats] = await Promise.all([
567
- apiClient.get('/api/status'),
568
- apiClient.get('/api/stats')
569
- ]);
570
-
571
- document.getElementById('system-health').textContent = status.system_health.toUpperCase();
572
- document.getElementById('total-providers').textContent = status.total_providers;
573
- document.getElementById('validated-providers').textContent = status.validated_providers;
574
-
575
- // Quick market view
576
- const market = await apiClient.get('/api/market');
577
- let marketHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">';
578
- market.cryptocurrencies.forEach(coin => {
579
- const changeClass = coin.change_24h >= 0 ? 'status-online' : 'status-offline';
580
- marketHTML += `
581
- <div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
582
- <div style="font-weight: 600;">${coin.name} (${coin.symbol})</div>
583
- <div style="font-size: 24px; margin: 10px 0;">$${coin.price.toLocaleString()}</div>
584
- <div class="${changeClass}">${coin.change_24h >= 0 ? '↑' : '↓'} ${Math.abs(coin.change_24h).toFixed(2)}%</div>
585
  </div>
586
- `;
587
- });
588
- marketHTML += '</div>';
589
- document.getElementById('quick-market-view').innerHTML = marketHTML;
590
-
591
- } catch (error) {
592
- console.error('Error loading global status:', error);
593
- showError('Failed to load global status');
594
- }
595
- }
596
 
597
- // Providers
598
- async function loadProviders() {
599
- try {
600
- const response = await apiClient.get('/api/providers');
601
- const providers = response.providers;
602
-
603
- if (providers.length === 0) {
604
- document.getElementById('providers-table').innerHTML = '<div class="empty-state">No providers found. Run APL scan to discover providers.</div>';
605
- return;
606
- }
607
-
608
- let html = `
609
- <table>
610
- <thead>
611
- <tr>
612
- <th>Provider ID</th>
613
- <th>Name</th>
614
- <th>Category</th>
615
- <th>Type</th>
616
- <th>Status</th>
617
- <th>Response Time</th>
618
- </tr>
619
- </thead>
620
- <tbody>
621
- `;
622
-
623
- providers.forEach(p => {
624
- const statusClass = p.status === 'validated' ? 'status-online' : 'status-degraded';
625
- html += `
626
- <tr>
627
- <td><code>${p.provider_id}</code></td>
628
- <td>${p.name}</td>
629
- <td>${p.category}</td>
630
- <td>${p.type}</td>
631
- <td class="${statusClass}">${p.status}</td>
632
- <td>${p.response_time_ms ? p.response_time_ms.toFixed(0) + 'ms' : 'N/A'}</td>
633
- </tr>
634
- `;
635
- });
636
-
637
- html += '</tbody></table>';
638
- document.getElementById('providers-table').innerHTML = html;
639
-
640
- } catch (error) {
641
- console.error('Error loading providers:', error);
642
- showError('Failed to load providers');
643
- }
644
- }
 
 
 
 
 
645
 
646
- function filterProviders() {
647
- // Would filter the providers table
648
- loadProviders();
649
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
 
651
- // Market Data
652
- async function loadMarketData() {
653
- try {
654
- const data = await apiClient.get('/api/market');
655
-
656
- let html = '<table><thead><tr><th>Rank</th><th>Coin</th><th>Price</th><th>24h Change</th><th>Market Cap</th><th>Volume 24h</th></tr></thead><tbody>';
657
-
658
- data.cryptocurrencies.forEach(coin => {
659
- const changeClass = coin.change_24h >= 0 ? 'status-online' : 'status-offline';
660
- html += `
661
- <tr>
662
- <td>${coin.rank}</td>
663
- <td><strong>${coin.name}</strong> (${coin.symbol})</td>
664
- <td>$${coin.price.toLocaleString()}</td>
665
- <td class="${changeClass}">${coin.change_24h >= 0 ? '+' : ''}${coin.change_24h.toFixed(2)}%</td>
666
- <td>$${(coin.market_cap / 1e9).toFixed(2)}B</td>
667
- <td>$${(coin.volume_24h / 1e9).toFixed(2)}B</td>
668
- </tr>
669
- `;
670
- });
671
-
672
- html += '</tbody></table>';
673
- html += `<p style="margin-top: 15px; color: var(--text-muted);">Source: ${data.source}</p>`;
674
-
675
- document.getElementById('market-data-container').innerHTML = html;
676
- } catch (error) {
677
- console.error('Error loading market data:', error);
678
- document.getElementById('market-data-container').innerHTML = '<div class="error-message">Failed to load market data: ' + error.message + '</div>';
679
- }
680
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
 
682
- async function loadSentiment() {
683
- try {
684
- const data = await apiClient.get('/api/sentiment');
685
-
686
- const fngValue = data.fear_greed_index;
687
- let color = '--success';
688
- if (fngValue < 30) color = '--danger';
689
- else if (fngValue < 50) color = '--warning';
690
-
691
- document.getElementById('sentiment-data').innerHTML = `
692
- <div style="text-align: center; padding: 20px;">
693
- <div style="font-size: 64px; color: var(${color}); font-weight: 700;">${fngValue}</div>
694
- <div style="font-size: 24px; margin: 10px 0;">${data.fear_greed_label}</div>
695
- <p style="color: var(--text-muted);">Source: ${data.source}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  </div>
697
- `;
698
- } catch (error) {
699
- console.error('Error loading sentiment:', error);
700
- document.getElementById('sentiment-data').innerHTML = '<div class="error-message">Failed to load sentiment: ' + error.message + '</div>';
701
- }
702
- }
703
 
704
- async function loadTrending() {
705
- try {
706
- const data = await apiClient.get('/api/trending');
707
-
708
- if (data.trending.length === 0) {
709
- document.getElementById('trending-coins').innerHTML = '<div class="empty-state">No trending coins available</div>';
710
- return;
711
- }
712
-
713
- let html = '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;">';
714
- data.trending.forEach(coin => {
715
- html += `
716
- <div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
717
- <div style="font-weight: 600;">${coin.name}</div>
718
- <div style="color: var(--text-muted);">${coin.symbol}</div>
719
- ${coin.market_cap_rank ? `<div style="margin-top: 10px;">Rank: #${coin.market_cap_rank}</div>` : ''}
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  </div>
721
- `;
722
- });
723
- html += '</div>';
724
- html += `<p style="margin-top: 15px; color: var(--text-muted);">Source: ${data.source}</p>`;
725
-
726
- document.getElementById('trending-coins').innerHTML = html;
727
- } catch (error) {
728
- console.error('Error loading trending:', error);
729
- document.getElementById('trending-coins').innerHTML = '<div class="error-message">Failed to load trending: ' + error.message + '</div>';
730
- }
731
- }
732
 
733
- // APL Functions
734
- async function runAPL() {
735
- const btn = document.getElementById('apl-run-btn');
736
- btn.disabled = true;
737
- btn.textContent = '⏳ Running APL Scan...';
738
-
739
- document.getElementById('apl-status').innerHTML = '<div class="loading">Running APL scan... This may take 1-2 minutes...</div>';
740
- document.getElementById('apl-output').textContent = 'Executing APL scan...';
741
-
742
- try {
743
- const result = await apiClient.post('/api/apl/run');
744
-
745
- if (result.status === 'completed') {
746
- document.getElementById('apl-status').innerHTML = `
747
- <div class="success-message">
748
- ✓ APL scan completed successfully!<br>
749
- Providers count: ${result.providers_count}<br>
750
- Time: ${result.timestamp}
 
 
 
 
751
  </div>
752
- `;
753
- document.getElementById('apl-output').textContent = result.stdout || 'Scan completed.';
754
-
755
- // Reload summary
756
- await loadAPLSummary();
757
-
758
- } else {
759
- document.getElementById('apl-status').innerHTML = `<div class="error-message">APL scan ${result.status}: ${result.message || 'Unknown error'}</div>`;
760
- document.getElementById('apl-output').textContent = result.stdout || 'No output';
761
- }
762
- } catch (error) {
763
- console.error('Error running APL:', error);
764
- document.getElementById('apl-status').innerHTML = '<div class="error-message">Failed to run APL: ' + error.message + '</div>';
765
- } finally {
766
- btn.disabled = false;
767
- btn.textContent = '🤖 Run APL Scan';
768
- }
769
- }
770
 
771
- async function loadAPLSummary() {
772
- try {
773
- const summary = await apiClient.get('/api/apl/summary');
774
-
775
- if (summary.status === 'not_available') {
776
- document.getElementById('apl-summary').innerHTML = '<div class="empty-state">No APL report available. Run APL scan first.</div>';
777
- return;
778
- }
779
-
780
- document.getElementById('apl-summary').innerHTML = `
781
  <div class="stats-grid">
782
- <div class="stat-card">
783
- <div class="label">HTTP Candidates</div>
784
- <div class="value">${summary.http_candidates}</div>
785
- <span class="badge badge-success">Valid: ${summary.http_valid}</span>
786
  </div>
787
- <div class="stat-card">
788
- <div class="label">HTTP Invalid</div>
789
- <div class="value">${summary.http_invalid}</div>
790
- <span class="badge badge-warning">Conditional: ${summary.http_conditional}</span>
791
  </div>
792
- <div class="stat-card">
793
- <div class="label">HF Models</div>
794
- <div class="value">${summary.hf_candidates}</div>
795
- <span class="badge badge-success">Valid: ${summary.hf_valid}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  </div>
797
- <div class="stat-card">
798
- <div class="label">Total Active</div>
799
- <div class="value">${summary.total_active}</div>
800
- <span class="badge badge-success">Providers</span>
 
 
 
 
 
 
 
 
 
 
801
  </div>
802
  </div>
803
- <p style="margin-top: 15px; color: var(--text-muted);">Last updated: ${summary.timestamp}</p>
804
- `;
805
- } catch (error) {
806
- console.error('Error loading APL summary:', error);
807
- document.getElementById('apl-summary').innerHTML = '<div class="error-message">Failed to load APL summary</div>';
808
- }
809
- }
810
-
811
- async function loadAPLReport() {
812
- try {
813
- const report = await apiClient.get('/api/apl/report');
814
-
815
- if (report.status === 'not_available') {
816
- showError('APL report not available. Run APL scan first.');
817
- return;
818
- }
819
-
820
- document.getElementById('apl-output').textContent = JSON.stringify(report, null, 2);
821
- } catch (error) {
822
- console.error('Error loading APL report:', error);
823
- showError('Failed to load APL report');
824
- }
825
- }
826
 
827
- // HF Models
828
- async function loadHFModels() {
829
- try {
830
- const response = await apiClient.get('/api/hf/models');
831
-
832
- if (response.count === 0) {
833
- document.getElementById('hf-models-container').innerHTML = '<div class="empty-state">No HF models found. Run APL scan to discover models.</div>';
834
- return;
835
- }
836
-
837
- let html = '';
838
- response.models.forEach(model => {
839
- const statusClass = model.status === 'VALID' ? 'valid' : model.status === 'CONDITIONALLY_AVAILABLE' ? 'conditional' : 'invalid';
840
- html += `
841
- <div class="model-card ${statusClass}">
842
- <div style="font-weight: 600; margin-bottom: 5px;">${model.provider_name}</div>
843
- <div style="color: var(--text-muted); font-size: 13px;">${model.provider_id}</div>
844
- <div style="margin-top: 10px;">
845
- <span class="badge badge-${statusClass === 'valid' ? 'success' : statusClass === 'conditional' ? 'warning' : 'danger'}">${model.status}</span>
846
  </div>
847
- ${model.error_reason ? `<div style="margin-top: 10px; color: var(--warning);">${model.error_reason}</div>` : ''}
848
  </div>
849
- `;
850
- });
851
-
852
- document.getElementById('hf-models-container').innerHTML = html;
853
- } catch (error) {
854
- console.error('Error loading HF models:', error);
855
- document.getElementById('hf-models-container').innerHTML = '<div class="error-message">Failed to load HF models</div>';
856
- }
857
- }
858
-
859
- async function loadHFHealth() {
860
- try {
861
- const health = await apiClient.get('/api/hf/health');
862
-
863
- const statusClass = health.ok ? 'status-online' : 'status-offline';
864
- document.getElementById('hf-health').innerHTML = `
865
- <div style="padding: 20px; background: var(--bg-dark); border-radius: 8px;">
866
- <div class="${statusClass}" style="font-size: 24px; font-weight: 700;">${health.ok ? '✓ Healthy' : '✗ Unhealthy'}</div>
867
- ${health.ok ? `
868
- <div style="margin-top: 15px;">
869
- <div>Models: ${health.counts.models}</div>
870
- <div>Datasets: ${health.counts.datasets}</div>
871
- <div>Last refresh: ${new Date(health.last_refresh_epoch * 1000).toLocaleString()}</div>
872
  </div>
873
- ` : `
874
- <div style="margin-top: 15px; color: var(--danger);">Error: ${health.error || health.fail_reason}</div>
875
- `}
876
  </div>
877
- `;
878
- } catch (error) {
879
- console.error('Error loading HF health:', error);
880
- document.getElementById('hf-health').innerHTML = '<div class="error-message">Failed to load HF health</div>';
881
- }
882
- }
883
-
884
- // Diagnostics
885
- async function runDiagnostics(autoFix = false) {
886
- try {
887
- const result = await apiClient.post('/api/diagnostics/run?auto_fix=' + autoFix);
888
-
889
- let html = `
890
- <div class="success-message">
891
- ✓ Diagnostics completed<br>
892
- Issues found: ${result.issues_found}<br>
893
- Time: ${result.timestamp}
894
  </div>
895
- `;
896
-
897
- if (result.issues.length > 0) {
898
- html += '<div style="margin-top: 20px;"><h4>Issues Found:</h4>';
899
- result.issues.forEach(issue => {
900
- html += `<div class="log-entry error">${issue.type}: ${issue.message}</div>`;
901
- });
902
- html += '</div>';
903
- }
904
-
905
- if (result.fixes_applied.length > 0) {
906
- html += '<div style="margin-top: 20px;"><h4>Fixes Applied:</h4>';
907
- result.fixes_applied.forEach(fix => {
908
- html += `<div class="log-entry">${fix}</div>`;
909
- });
910
- html += '</div>';
911
- }
912
-
913
- document.getElementById('diagnostics-results').innerHTML = html;
914
- } catch (error) {
915
- console.error('Error running diagnostics:', error);
916
- showError('Failed to run diagnostics');
917
- }
918
- }
919
-
920
- async function loadLastDiagnostics() {
921
- try {
922
- const result = await apiClient.get('/api/diagnostics/last');
923
-
924
- if (result.status === 'no_previous_run') {
925
- document.getElementById('diagnostics-results').innerHTML = '<div class="empty-state">No previous diagnostics run found</div>';
926
- return;
927
- }
928
-
929
- // Display last diagnostics
930
- document.getElementById('diagnostics-results').innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
931
- } catch (error) {
932
- console.error('Error loading diagnostics:', error);
933
- showError('Failed to load diagnostics');
934
- }
935
- }
936
-
937
- // Logs
938
- async function loadRecentLogs() {
939
- try {
940
- const response = await apiClient.get('/api/logs/recent');
941
-
942
- if (response.count === 0) {
943
- document.getElementById('logs-container').innerHTML = '<div class="empty-state">No logs available</div>';
944
- return;
945
- }
946
-
947
- let html = '';
948
- response.logs.forEach(log => {
949
- const className = log.level === 'ERROR' ? 'error' : '';
950
- html += `
951
- <div class="log-entry ${className}">
952
- <div class="log-timestamp">${log.timestamp || new Date().toISOString()}</div>
953
- <div>${log.message || JSON.stringify(log)}</div>
954
  </div>
955
- `;
956
- });
957
-
958
- document.getElementById('logs-container').innerHTML = html;
959
- } catch (error) {
960
- console.error('Error loading logs:', error);
961
- document.getElementById('logs-container').innerHTML = '<div class="error-message">Failed to load logs</div>';
962
- }
963
- }
964
 
965
- async function loadErrorLogs() {
966
- try {
967
- const response = await apiClient.get('/api/logs/errors');
968
-
969
- if (response.count === 0) {
970
- document.getElementById('logs-container').innerHTML = '<div class="empty-state">No error logs found</div>';
971
- return;
972
- }
973
-
974
- let html = '';
975
- response.errors.forEach(log => {
976
- html += `
977
- <div class="log-entry error">
978
- <div class="log-timestamp">${log.timestamp || new Date().toISOString()}</div>
979
- <div>${log.message || JSON.stringify(log)}</div>
 
 
 
 
 
 
 
 
 
980
  </div>
981
- `;
982
- });
983
-
984
- document.getElementById('logs-container').innerHTML = html;
985
- } catch (error) {
986
- console.error('Error loading error logs:', error);
987
- showError('Failed to load error logs');
988
- }
989
- }
990
-
991
- // Utility functions
992
- function showError(message) {
993
- alert('Error: ' + message);
994
- }
995
-
996
- function refreshAllData() {
997
- loadGlobalStatus();
998
- loadProviders();
999
- loadAPLSummary();
1000
- }
1001
-
1002
- // Auto-refresh every 30 seconds
1003
- setInterval(() => {
1004
- const activeTab = document.querySelector('.tab-content.active').id;
1005
- if (activeTab === 'tab-status') {
1006
- loadGlobalStatus();
1007
- }
1008
- }, 30000);
1009
-
1010
- // Load initial data
1011
- window.addEventListener('DOMContentLoaded', () => {
1012
- console.log('✓ Admin Dashboard Loaded - Real Data Only');
1013
- loadGlobalStatus();
1014
- });
1015
- </script>
1016
  </body>
1017
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Crypto Monitor HF - Unified Dashboard</title>
7
+ <link rel="stylesheet" href="static/css/design-tokens.css" />
8
+ <link rel="stylesheet" href="static/css/design-system.css" />
9
+ <link rel="stylesheet" href="static/css/dashboard.css" />
10
+ <link rel="stylesheet" href="static/css/pro-dashboard.css" />
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" defer></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  </head>
13
+ <body data-theme="dark">
14
+ <div class="app-shell">
15
+ <aside class="sidebar">
16
+ <div class="brand">
17
+ <strong>Crypto Monitor HF</strong>
18
+ <span class="env-pill">
19
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
20
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5" />
21
+ <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5" />
22
+ <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5" />
23
+ </svg>
24
+ HF Space
25
+ </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
27
+ <nav class="nav">
28
+ <button class="nav-button active" data-nav="page-overview">Overview</button>
29
+ <button class="nav-button" data-nav="page-market">Market</button>
30
+ <button class="nav-button" data-nav="page-chart">Chart Lab</button>
31
+ <button class="nav-button" data-nav="page-ai">Sentiment & AI</button>
32
+ <button class="nav-button" data-nav="page-news">News</button>
33
+ <button class="nav-button" data-nav="page-providers">Providers</button>
34
+ <button class="nav-button" data-nav="page-api">API Explorer</button>
35
+ <button class="nav-button" data-nav="page-debug">Diagnostics</button>
36
+ <button class="nav-button" data-nav="page-datasets">Datasets & Models</button>
37
+ <button class="nav-button" data-nav="page-settings">Settings</button>
38
+ </nav>
39
+ <div class="sidebar-footer">
40
+ Unified crypto intelligence console<br />Realtime data • HF optimized
41
  </div>
42
+ </aside>
43
+ <main class="main-area">
44
+ <header class="topbar">
45
+ <div>
46
+ <h1>Unified Intelligence Dashboard</h1>
47
+ <p class="text-muted">Live market telemetry, AI signals, diagnostics, and provider health.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </div>
49
+ <div class="status-group">
50
+ <div class="status-pill" data-api-health data-state="warn">
51
+ <span class="status-dot"></span>
52
+ <span>checking</span>
53
+ </div>
54
+ <div class="status-pill" data-ws-status data-state="warn">
55
+ <span class="status-dot"></span>
56
+ <span>connecting</span>
57
+ </div>
58
+ </div>
59
+ </header>
60
+ <div class="page-container">
61
+ <section id="page-overview" class="page active">
62
+ <div class="section-header">
63
+ <h2 class="section-title">Global Overview</h2>
64
+ <span class="chip">Powered by /api/market/stats</span>
65
+ </div>
66
+ <div class="stats-grid" data-overview-stats></div>
67
+ <div class="grid-two">
68
+ <div class="glass-card">
69
+ <div class="section-header">
70
+ <h3>Top Coins</h3>
71
+ <span class="text-muted">Market movers</span>
72
+ </div>
73
+ <div class="table-wrapper">
74
+ <table>
75
+ <thead>
76
+ <tr>
77
+ <th>#</th>
78
+ <th>Symbol</th>
79
+ <th>Name</th>
80
+ <th>Price</th>
81
+ <th>24h %</th>
82
+ <th>Volume</th>
83
+ <th>Market Cap</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody data-top-coins-body></tbody>
87
+ </table>
88
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  </div>
90
+ <div class="glass-card">
91
+ <div class="section-header">
92
+ <h3>Global Sentiment</h3>
93
+ <span class="text-muted">CryptoBERT stack</span>
94
+ </div>
95
+ <canvas id="sentiment-chart" height="220"></canvas>
96
+ </div>
97
+ </div>
98
+ </section>
 
99
 
100
+ <section id="page-market" class="page">
101
+ <div class="section-header">
102
+ <h2 class="section-title">Market Intelligence</h2>
103
+ <div class="controls-bar">
104
+ <div class="input-chip">
105
+ <svg viewBox="0 0 24 24" width="16" height="16"><path d="M21 20l-5.6-5.6A6.5 6.5 0 1 0 15.4 16L21 21zM5 10.5a5.5 5.5 0 1 1 11 0a5.5 5.5 0 0 1-11 0z" fill="currentColor"/></svg>
106
+ <input type="text" placeholder="Search symbol" data-market-search />
107
+ </div>
108
+ <div class="input-chip">
109
+ Timeframe:
110
+ <button class="ghost" data-timeframe="1d">1D</button>
111
+ <button class="ghost active" data-timeframe="7d">7D</button>
112
+ <button class="ghost" data-timeframe="30d">30D</button>
113
+ </div>
114
+ <label class="input-chip"> Live updates
115
+ <div class="toggle">
116
+ <input type="checkbox" data-live-toggle />
117
+ <span></span>
118
+ </div>
119
+ </label>
120
+ </div>
121
+ </div>
122
+ <div class="glass-card">
123
+ <div class="table-wrapper">
124
+ <table>
125
+ <thead>
126
+ <tr>
127
+ <th>#</th>
128
+ <th>Symbol</th>
129
+ <th>Name</th>
130
+ <th>Price</th>
131
+ <th>24h %</th>
132
+ <th>Volume</th>
133
+ <th>Market Cap</th>
134
+ </tr>
135
+ </thead>
136
+ <tbody data-market-body></tbody>
137
+ </table>
138
+ </div>
139
+ </div>
140
+ <div class="drawer" data-market-drawer>
141
+ <button class="ghost" data-close-drawer>Close</button>
142
+ <h3 data-drawer-symbol>—</h3>
143
+ <div data-drawer-stats></div>
144
+ <div class="glass-card" data-chart-wrapper>
145
+ <canvas id="market-detail-chart" height="180"></canvas>
146
+ </div>
147
+ <div class="glass-card">
148
+ <h4>Related Headlines</h4>
149
+ <div data-drawer-news></div>
150
+ </div>
151
+ </div>
152
+ </section>
153
 
154
+ <section id="page-chart" class="page">
155
+ <div class="section-header">
156
+ <h2 class="section-title">Chart Lab</h2>
157
+ <div class="controls-bar">
158
+ <select data-chart-symbol>
159
+ <option value="BTC">BTC</option>
160
+ <option value="ETH">ETH</option>
161
+ <option value="SOL">SOL</option>
162
+ <option value="BNB">BNB</option>
163
+ </select>
164
+ <div class="input-chip">
165
+ <button class="ghost active" data-chart-timeframe="7d">7D</button>
166
+ <button class="ghost" data-chart-timeframe="30d">30D</button>
167
+ <button class="ghost" data-chart-timeframe="90d">90D</button>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <div class="glass-card">
172
+ <canvas id="chart-lab-canvas" height="260"></canvas>
173
+ </div>
174
+ <div class="glass-card">
175
+ <div class="controls-bar">
176
+ <label><input type="checkbox" data-indicator value="MA20" checked /> MA 20</label>
177
+ <label><input type="checkbox" data-indicator value="MA50" /> MA 50</label>
178
+ <label><input type="checkbox" data-indicator value="RSI" /> RSI</label>
179
+ <label><input type="checkbox" data-indicator value="Volume" /> Volume</label>
180
+ </div>
181
+ <button class="primary" data-run-analysis>Analyze Chart with AI</button>
182
+ <div data-ai-insights class="ai-insights"></div>
183
+ </div>
184
+ </section>
185
 
186
+ <section id="page-ai" class="page">
187
+ <div class="section-header">
188
+ <h2 class="section-title">Sentiment & AI Advisor</h2>
189
+ </div>
190
+ <div class="glass-card">
191
+ <form data-ai-form class="ai-form">
192
+ <div class="grid-two">
193
+ <label>Symbol
194
+ <select name="symbol">
195
+ <option value="BTC">BTC</option>
196
+ <option value="ETH">ETH</option>
197
+ <option value="SOL">SOL</option>
198
+ </select>
199
+ </label>
200
+ <label>Time Horizon
201
+ <select name="horizon">
202
+ <option value="intraday">Intraday</option>
203
+ <option value="swing" selected>Swing</option>
204
+ <option value="long">Long Term</option>
205
+ </select>
206
+ </label>
207
+ <label>Risk Profile
208
+ <select name="risk">
209
+ <option value="conservative">Conservative</option>
210
+ <option value="moderate" selected>Moderate</option>
211
+ <option value="aggressive">Aggressive</option>
212
+ </select>
213
+ </label>
214
+ <label>Sentiment Model
215
+ <select name="model">
216
+ <option value="auto">Auto</option>
217
+ <option value="crypto">CryptoBERT</option>
218
+ <option value="financial">FinBERT</option>
219
+ <option value="social">Twitter Sentiment</option>
220
+ </select>
221
+ </label>
222
+ </div>
223
+ <label>Context or Headline
224
+ <textarea name="context" placeholder="Paste a headline or trade thesis for AI analysis"></textarea>
225
+ </label>
226
+ <button class="primary" type="submit">Generate Guidance</button>
227
+ </form>
228
+ <div class="grid-two">
229
+ <div data-ai-result class="ai-result"></div>
230
+ <div data-sentiment-result></div>
231
+ </div>
232
+ <div class="inline-message inline-info" data-ai-disclaimer>
233
+ Experimental AI output. Not financial advice.
234
+ </div>
235
+ </div>
236
+ </section>
237
 
238
+ <section id="page-news" class="page">
239
+ <div class="section-header">
240
+ <h2 class="section-title">News & Summaries</h2>
241
+ </div>
242
+ <div class="controls-bar">
243
+ <select data-news-range>
244
+ <option value="24h">Last 24h</option>
245
+ <option value="7d">7 Days</option>
246
+ <option value="30d">30 Days</option>
247
+ </select>
248
+ <input type="text" placeholder="Search headline" data-news-search />
249
+ <input type="text" placeholder="Filter symbol (e.g. BTC)" data-news-symbol />
250
+ </div>
251
+ <div class="glass-card">
252
+ <div class="table-wrapper">
253
+ <table>
254
+ <thead>
255
+ <tr>
256
+ <th>Time</th>
257
+ <th>Source</th>
258
+ <th>Title</th>
259
+ <th>Symbols</th>
260
+ <th>Sentiment</th>
261
+ <th>AI</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody data-news-body></tbody>
265
+ </table>
266
+ </div>
267
+ </div>
268
+ <div class="modal-backdrop" data-news-modal>
269
+ <div class="modal">
270
+ <button class="ghost" data-close-news-modal>Close</button>
271
+ <div data-news-modal-content></div>
272
+ </div>
273
  </div>
274
+ </section>
 
 
 
 
 
275
 
276
+ <section id="page-providers" class="page">
277
+ <div class="section-header">
278
+ <h2 class="section-title">Provider Health</h2>
279
+ <button class="ghost" data-provider-refresh>Refresh</button>
280
+ </div>
281
+ <div class="stats-grid" data-provider-summary></div>
282
+ <div class="controls-bar">
283
+ <input type="search" placeholder="Search provider" data-provider-search />
284
+ <select data-provider-category>
285
+ <option value="all">All Categories</option>
286
+ <option value="market">Market Data</option>
287
+ <option value="news">News</option>
288
+ <option value="ai">AI</option>
289
+ </select>
290
+ </div>
291
+ <div class="glass-card">
292
+ <div class="table-wrapper">
293
+ <table>
294
+ <thead>
295
+ <tr>
296
+ <th>Name</th>
297
+ <th>Category</th>
298
+ <th>Status</th>
299
+ <th>Latency</th>
300
+ <th>Details</th>
301
+ </tr>
302
+ </thead>
303
+ <tbody data-providers-table></tbody>
304
+ </table>
305
  </div>
306
+ </div>
307
+ </section>
 
 
 
 
 
 
 
 
 
308
 
309
+ <section id="page-api" class="page">
310
+ <div class="section-header">
311
+ <h2 class="section-title">API Explorer</h2>
312
+ <span class="chip">Test live endpoints</span>
313
+ </div>
314
+ <div class="glass-card">
315
+ <div class="grid-two">
316
+ <label>Endpoint
317
+ <select data-api-endpoint></select>
318
+ </label>
319
+ <label>Method
320
+ <select data-api-method>
321
+ <option value="GET">GET</option>
322
+ <option value="POST">POST</option>
323
+ </select>
324
+ </label>
325
+ <label>Query Params
326
+ <input type="text" placeholder="limit=10&symbol=BTC" data-api-params />
327
+ </label>
328
+ <label>Body (JSON)
329
+ <textarea data-api-body placeholder='{ "text": "Bitcoin" }'></textarea>
330
+ </label>
331
  </div>
332
+ <p class="text-muted">Path: <span data-api-path></span> — <span data-api-description></span></p>
333
+ <button class="primary" data-api-send>Send Request</button>
334
+ <div class="inline-message" data-api-meta>Ready</div>
335
+ <pre data-api-response class="api-response"></pre>
336
+ </div>
337
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ <section id="page-debug" class="page">
340
+ <div class="section-header">
341
+ <h2 class="section-title">Diagnostics</h2>
342
+ <button class="ghost" data-refresh-health>Refresh</button>
343
+ </div>
 
 
 
 
 
344
  <div class="stats-grid">
345
+ <div class="glass-card">
346
+ <h3>API Health</h3>
347
+ <div class="stat-value" data-health-status>—</div>
 
348
  </div>
349
+ <div class="glass-card">
350
+ <h3>Providers</h3>
351
+ <div data-providers class="grid-two"></div>
 
352
  </div>
353
+ </div>
354
+ <div class="grid-two">
355
+ <div class="glass-card">
356
+ <h4>Request Log</h4>
357
+ <div class="table-wrapper log-table">
358
+ <table>
359
+ <thead>
360
+ <tr>
361
+ <th>Time</th>
362
+ <th>Method</th>
363
+ <th>Endpoint</th>
364
+ <th>Status</th>
365
+ <th>Latency</th>
366
+ </tr>
367
+ </thead>
368
+ <tbody data-request-log></tbody>
369
+ </table>
370
+ </div>
371
  </div>
372
+ <div class="glass-card">
373
+ <h4>Error Log</h4>
374
+ <div class="table-wrapper log-table">
375
+ <table>
376
+ <thead>
377
+ <tr>
378
+ <th>Time</th>
379
+ <th>Endpoint</th>
380
+ <th>Message</th>
381
+ </tr>
382
+ </thead>
383
+ <tbody data-error-log></tbody>
384
+ </table>
385
+ </div>
386
  </div>
387
  </div>
388
+ <div class="glass-card">
389
+ <h4>WebSocket Events</h4>
390
+ <div class="table-wrapper log-table">
391
+ <table>
392
+ <thead>
393
+ <tr>
394
+ <th>Time</th>
395
+ <th>Type</th>
396
+ <th>Detail</th>
397
+ </tr>
398
+ </thead>
399
+ <tbody data-ws-log></tbody>
400
+ </table>
401
+ </div>
402
+ </div>
403
+ </section>
 
 
 
 
 
 
 
404
 
405
+ <section id="page-datasets" class="page">
406
+ <div class="section-header">
407
+ <h2 class="section-title">Datasets & Models</h2>
408
+ </div>
409
+ <div class="grid-two">
410
+ <div class="glass-card">
411
+ <h3>Datasets</h3>
412
+ <div class="table-wrapper">
413
+ <table>
414
+ <thead>
415
+ <tr>
416
+ <th>Name</th>
417
+ <th>Records</th>
418
+ <th>Updated</th>
419
+ <th>Actions</th>
420
+ </tr>
421
+ </thead>
422
+ <tbody data-datasets-body></tbody>
423
+ </table>
424
  </div>
 
425
  </div>
426
+ <div class="glass-card">
427
+ <h3>Models</h3>
428
+ <div class="table-wrapper">
429
+ <table>
430
+ <thead>
431
+ <tr>
432
+ <th>Name</th>
433
+ <th>Task</th>
434
+ <th>Status</th>
435
+ <th>Notes</th>
436
+ </tr>
437
+ </thead>
438
+ <tbody data-models-body></tbody>
439
+ </table>
 
 
 
 
 
 
 
 
 
440
  </div>
441
+ </div>
 
 
442
  </div>
443
+ <div class="glass-card">
444
+ <h4>Test a Model</h4>
445
+ <form data-model-test-form class="grid-two">
446
+ <label>Model
447
+ <select data-model-select name="model"></select>
448
+ </label>
449
+ <label>Input
450
+ <textarea name="input" placeholder="Type a prompt"></textarea>
451
+ </label>
452
+ <button class="primary" type="submit">Run Test</button>
453
+ </form>
454
+ <div data-model-test-output></div>
 
 
 
 
 
455
  </div>
456
+ <div class="modal-backdrop" data-dataset-modal>
457
+ <div class="modal">
458
+ <button class="ghost" data-close-dataset-modal>Close</button>
459
+ <div data-dataset-modal-content></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  </div>
461
+ </div>
462
+ </section>
 
 
 
 
 
 
 
463
 
464
+ <section id="page-settings" class="page">
465
+ <div class="section-header">
466
+ <h2 class="section-title">Settings</h2>
467
+ </div>
468
+ <div class="glass-card">
469
+ <div class="grid-two">
470
+ <label class="input-chip">Light Theme
471
+ <div class="toggle">
472
+ <input type="checkbox" data-theme-toggle />
473
+ <span></span>
474
+ </div>
475
+ </label>
476
+ <label>Market Refresh (sec)
477
+ <input type="number" min="15" step="5" data-market-interval />
478
+ </label>
479
+ <label>News Refresh (sec)
480
+ <input type="number" min="30" step="10" data-news-interval />
481
+ </label>
482
+ <label class="input-chip">Compact Layout
483
+ <div class="toggle">
484
+ <input type="checkbox" data-layout-toggle />
485
+ <span></span>
486
+ </div>
487
+ </label>
488
  </div>
489
+ </div>
490
+ </section>
491
+ </div>
492
+ </main>
493
+ </div>
494
+ <script type="module" src="static/js/app.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  </body>
496
  </html>