parse-dashboard-analytics 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,710 @@
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>Remote Analytics Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ color: white;
18
+ min-height: 100vh;
19
+ padding: 20px;
20
+ }
21
+
22
+ .container {
23
+ max-width: 1400px;
24
+ margin: 0 auto;
25
+ }
26
+
27
+ .header {
28
+ background: rgba(255, 255, 255, 0.1);
29
+ backdrop-filter: blur(15px);
30
+ border-radius: 16px;
31
+ padding: 32px;
32
+ margin-bottom: 32px;
33
+ border: 1px solid rgba(255, 255, 255, 0.2);
34
+ text-align: center;
35
+ position: relative;
36
+ }
37
+
38
+ .connection-status {
39
+ position: absolute;
40
+ top: 20px;
41
+ right: 20px;
42
+ padding: 8px 16px;
43
+ border-radius: 20px;
44
+ font-size: 12px;
45
+ font-weight: 600;
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.5px;
48
+ }
49
+
50
+ .connection-status.connected {
51
+ background: rgba(40, 167, 69, 0.3);
52
+ color: #28a745;
53
+ border: 1px solid rgba(40, 167, 69, 0.5);
54
+ }
55
+
56
+ .connection-status.disconnected {
57
+ background: rgba(220, 53, 69, 0.3);
58
+ color: #dc3545;
59
+ border: 1px solid rgba(220, 53, 69, 0.5);
60
+ }
61
+
62
+ .connection-status.connecting {
63
+ background: rgba(255, 193, 7, 0.3);
64
+ color: #ffc107;
65
+ border: 1px solid rgba(255, 193, 7, 0.5);
66
+ }
67
+
68
+ .back-button {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ padding: 12px 20px;
72
+ background: rgba(255, 255, 255, 0.2);
73
+ color: white;
74
+ text-decoration: none;
75
+ border-radius: 25px;
76
+ font-weight: 600;
77
+ margin-bottom: 32px;
78
+ transition: all 0.3s ease;
79
+ border: 1px solid rgba(255, 255, 255, 0.3);
80
+ }
81
+
82
+ .back-button:hover {
83
+ background: rgba(255, 255, 255, 0.3);
84
+ color: white;
85
+ text-decoration: none;
86
+ transform: translateX(-5px);
87
+ }
88
+
89
+ .server-info {
90
+ background: rgba(255, 255, 255, 0.1);
91
+ backdrop-filter: blur(10px);
92
+ border-radius: 12px;
93
+ padding: 20px;
94
+ margin-bottom: 24px;
95
+ border: 1px solid rgba(255, 255, 255, 0.2);
96
+ }
97
+
98
+ .server-info h3 {
99
+ margin-bottom: 12px;
100
+ font-size: 18px;
101
+ }
102
+
103
+ .server-endpoint {
104
+ font-family: 'Monaco', 'Menlo', monospace;
105
+ background: rgba(0, 0, 0, 0.3);
106
+ padding: 8px 12px;
107
+ border-radius: 6px;
108
+ font-size: 14px;
109
+ }
110
+
111
+ .metrics-grid {
112
+ display: grid;
113
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
114
+ gap: 24px;
115
+ margin-bottom: 40px;
116
+ }
117
+
118
+ .metric-card {
119
+ background: rgba(255, 255, 255, 0.1);
120
+ backdrop-filter: blur(15px);
121
+ border-radius: 16px;
122
+ padding: 28px;
123
+ border: 1px solid rgba(255, 255, 255, 0.2);
124
+ transition: all 0.3s ease;
125
+ position: relative;
126
+ overflow: hidden;
127
+ }
128
+
129
+ .metric-card::before {
130
+ content: '';
131
+ position: absolute;
132
+ top: 0;
133
+ left: 0;
134
+ right: 0;
135
+ height: 3px;
136
+ background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4);
137
+ }
138
+
139
+ .metric-card:hover {
140
+ transform: translateY(-5px);
141
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
142
+ }
143
+
144
+ .metric-value {
145
+ font-size: 42px;
146
+ font-weight: 800;
147
+ margin-bottom: 8px;
148
+ background: linear-gradient(45deg, #fff, #e0e7ff);
149
+ -webkit-background-clip: text;
150
+ -webkit-text-fill-color: transparent;
151
+ background-clip: text;
152
+ }
153
+
154
+ .metric-label {
155
+ font-size: 16px;
156
+ opacity: 0.9;
157
+ margin-bottom: 8px;
158
+ font-weight: 500;
159
+ }
160
+
161
+ .metric-change {
162
+ font-size: 14px;
163
+ font-weight: 600;
164
+ padding: 4px 12px;
165
+ border-radius: 20px;
166
+ display: inline-block;
167
+ }
168
+
169
+ .metric-change.positive {
170
+ background: rgba(40, 167, 69, 0.3);
171
+ color: #28a745;
172
+ }
173
+
174
+ .metric-change.negative {
175
+ background: rgba(220, 53, 69, 0.3);
176
+ color: #dc3545;
177
+ }
178
+
179
+ .metric-timestamp {
180
+ font-size: 12px;
181
+ opacity: 0.7;
182
+ margin-top: 8px;
183
+ font-style: italic;
184
+ }
185
+
186
+ .charts-section {
187
+ display: grid;
188
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
189
+ gap: 32px;
190
+ }
191
+
192
+ .chart-card {
193
+ background: rgba(255, 255, 255, 0.1);
194
+ backdrop-filter: blur(15px);
195
+ border-radius: 16px;
196
+ padding: 32px;
197
+ border: 1px solid rgba(255, 255, 255, 0.2);
198
+ min-height: 350px;
199
+ }
200
+
201
+ .chart-title {
202
+ font-size: 20px;
203
+ font-weight: 600;
204
+ margin-bottom: 20px;
205
+ opacity: 0.95;
206
+ }
207
+
208
+ .chart-placeholder {
209
+ height: 260px;
210
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
211
+ border-radius: 12px;
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ font-size: 16px;
216
+ font-weight: 500;
217
+ opacity: 0.8;
218
+ border: 2px dashed rgba(255, 255, 255, 0.3);
219
+ position: relative;
220
+ }
221
+
222
+ .loading {
223
+ opacity: 0.6;
224
+ animation: pulse 2s infinite;
225
+ }
226
+
227
+ @keyframes pulse {
228
+ 0%, 100% { opacity: 0.6; }
229
+ 50% { opacity: 1; }
230
+ }
231
+
232
+ .error-message {
233
+ background: rgba(220, 53, 69, 0.2);
234
+ border: 1px solid rgba(220, 53, 69, 0.5);
235
+ color: #dc3545;
236
+ padding: 16px;
237
+ border-radius: 8px;
238
+ margin: 20px 0;
239
+ text-align: center;
240
+ }
241
+
242
+ .refresh-button {
243
+ background: rgba(255, 255, 255, 0.2);
244
+ border: 1px solid rgba(255, 255, 255, 0.3);
245
+ color: white;
246
+ padding: 10px 20px;
247
+ border-radius: 20px;
248
+ cursor: pointer;
249
+ font-weight: 600;
250
+ transition: all 0.3s ease;
251
+ margin-left: 16px;
252
+ }
253
+
254
+ .refresh-button:hover {
255
+ background: rgba(255, 255, 255, 0.3);
256
+ }
257
+
258
+ .data-source {
259
+ font-size: 12px;
260
+ opacity: 0.7;
261
+ text-align: center;
262
+ margin-top: 20px;
263
+ font-style: italic;
264
+ }
265
+
266
+ .realtime-indicator {
267
+ display: inline-block;
268
+ width: 8px;
269
+ height: 8px;
270
+ background: #28a745;
271
+ border-radius: 50%;
272
+ margin-right: 8px;
273
+ animation: blink 2s infinite;
274
+ }
275
+
276
+ @keyframes blink {
277
+ 0%, 50% { opacity: 1; }
278
+ 51%, 100% { opacity: 0.3; }
279
+ }
280
+
281
+ .config-info {
282
+ background: rgba(255, 255, 255, 0.1);
283
+ backdrop-filter: blur(10px);
284
+ border-radius: 12px;
285
+ padding: 24px;
286
+ margin-top: 32px;
287
+ border: 1px solid rgba(255, 255, 255, 0.2);
288
+ font-size: 14px;
289
+ line-height: 1.6;
290
+ }
291
+
292
+ .config-info h3 {
293
+ margin-bottom: 16px;
294
+ font-size: 18px;
295
+ }
296
+
297
+ .config-info code {
298
+ background: rgba(0, 0, 0, 0.3);
299
+ padding: 2px 6px;
300
+ border-radius: 4px;
301
+ font-family: 'Monaco', 'Menlo', monospace;
302
+ }
303
+ </style>
304
+ </head>
305
+ <body>
306
+ <div class="container">
307
+ <a href="#" onclick="history.back()" class="back-button">
308
+ ← Back to Dashboard
309
+ </a>
310
+
311
+ <div class="header">
312
+ <div class="connection-status" id="connectionStatus">Connecting...</div>
313
+ <h1>🌐 Remote Analytics Dashboard</h1>
314
+ <p>Real-time analytics from your dedicated analytics server</p>
315
+ <button class="refresh-button" onclick="refreshData()">Refresh Data</button>
316
+ </div>
317
+
318
+ <div class="server-info">
319
+ <h3>📡 Analytics Server Connection</h3>
320
+ <div class="server-endpoint" id="serverEndpoint">Connecting to analytics server...</div>
321
+ </div>
322
+
323
+ <div id="errorContainer"></div>
324
+
325
+ <div class="metrics-grid">
326
+ <div class="metric-card">
327
+ <div class="metric-value" id="totalUsers">--</div>
328
+ <div class="metric-label">Total Users</div>
329
+ <div class="metric-change positive" id="usersChange">Loading...</div>
330
+ <div class="metric-timestamp" id="usersTimestamp"></div>
331
+ </div>
332
+
333
+ <div class="metric-card">
334
+ <div class="metric-value" id="totalObjects">--</div>
335
+ <div class="metric-label">Total Objects</div>
336
+ <div class="metric-change positive" id="objectsChange">Loading...</div>
337
+ <div class="metric-timestamp" id="objectsTimestamp"></div>
338
+ </div>
339
+
340
+ <div class="metric-card">
341
+ <div class="metric-value" id="apiCalls">--</div>
342
+ <div class="metric-label">API Calls (24h)</div>
343
+ <div class="metric-change positive" id="apiChange">Loading...</div>
344
+ <div class="metric-timestamp" id="apiTimestamp"></div>
345
+ </div>
346
+
347
+ <div class="metric-card">
348
+ <div class="metric-value" id="activeSessions">--</div>
349
+ <div class="metric-label">Active Sessions</div>
350
+ <div class="metric-change positive" id="sessionsChange">Loading...</div>
351
+ <div class="metric-timestamp" id="sessionsTimestamp"></div>
352
+ </div>
353
+ </div>
354
+
355
+ <div class="charts-section">
356
+ <div class="chart-card">
357
+ <div class="chart-title">
358
+ <span class="realtime-indicator"></span>
359
+ User Growth Trend
360
+ </div>
361
+ <div class="chart-placeholder" id="userGrowthChart">
362
+ <div class="loading">Loading user growth data...</div>
363
+ </div>
364
+ </div>
365
+
366
+ <div class="chart-card">
367
+ <div class="chart-title">
368
+ <span class="realtime-indicator"></span>
369
+ API Usage Distribution
370
+ </div>
371
+ <div class="chart-placeholder" id="apiUsageChart">
372
+ <div class="loading">Loading API usage data...</div>
373
+ </div>
374
+ </div>
375
+
376
+ <div class="chart-card">
377
+ <div class="chart-title">
378
+ <span class="realtime-indicator"></span>
379
+ Server Performance
380
+ </div>
381
+ <div class="chart-placeholder" id="performanceChart">
382
+ <div class="loading">Loading performance metrics...</div>
383
+ </div>
384
+ </div>
385
+
386
+ <div class="chart-card">
387
+ <div class="chart-title">
388
+ <span class="realtime-indicator"></span>
389
+ Data Storage Trends
390
+ </div>
391
+ <div class="chart-placeholder" id="storageChart">
392
+ <div class="loading">Loading storage data...</div>
393
+ </div>
394
+ </div>
395
+ </div>
396
+
397
+ <div class="data-source">
398
+ <span class="realtime-indicator"></span>
399
+ Data refreshed every 30 seconds from remote analytics server
400
+ </div>
401
+
402
+ <div class="config-info">
403
+ <h3>🔧 Remote Analytics Configuration</h3>
404
+ <p>This dashboard connects directly to your analytics server for real-time data:</p>
405
+ <ul>
406
+ <li><strong>Primary Server:</strong> <code>192.168.0.138:${ENV.MAIN_PORT}</code> (your remote server)</li>
407
+ <li><strong>Fallback Server:</strong> <code>localhost:3001</code> (local demo server)</li>
408
+ <li><strong>Protocol:</strong> <code>HTTP</code> with CORS enabled</li>
409
+ <li><strong>Update Interval:</strong> <code>30 seconds</code></li>
410
+ <li><strong>Current Status:</strong> <span id="currentServerStatus">Loading...</span></li>
411
+ </ul>
412
+ <p><strong>Demo Server API Endpoints:</strong></p>
413
+ <ul>
414
+ <li><code>/api/analytics/overview</code> - Overall metrics</li>
415
+ <li><code>/api/analytics/users</code> - User statistics</li>
416
+ <li><code>/api/analytics/performance</code> - Server performance</li>
417
+ <li><code>/api/analytics/realtime</code> - Live data updates</li>
418
+ </ul>
419
+ <p><strong>How it works:</strong></p>
420
+ <ol>
421
+ <li>First tries to connect to local demo server at <code>localhost:3001</code></li>
422
+ <li>If demo server fails, tries your remote server at <code>192.168.0.138</code></li>
423
+ <li>If both fail, falls back to mock data for demonstration</li>
424
+ <li>Demo server provides realistic, live-updating analytics data</li>
425
+ </ol>
426
+ </div>
427
+ </div>
428
+
429
+ <script>
430
+ // Configuration - Try demo server first since we know it's working
431
+ const ANALYTICS_SERVER_IP = '192.168.0.138';
432
+ const ANALYTICS_SERVER_PORT = getPortFromEnv() || '3000'; // Fallback port
433
+ const ANALYTICS_BASE_URL = `http://${ANALYTICS_SERVER_IP}:${ANALYTICS_SERVER_PORT}`;
434
+
435
+ // Demo server that we know is working
436
+ const DEMO_SERVER_URL = 'http://localhost:3001';
437
+
438
+ let connectionStatus = 'connecting';
439
+ let retryCount = 0;
440
+ const maxRetries = 2;
441
+ let currentServerUrl = DEMO_SERVER_URL; // Start with demo server first
442
+
443
+ // Function to get port from environment variable or URL params
444
+ function getPortFromEnv() {
445
+ // Try to get from URL parameters first
446
+ const urlParams = new URLSearchParams(window.location.search);
447
+ const port = urlParams.get('port');
448
+ if (port) return port;
449
+
450
+ // Try to get from a global variable if set by the server
451
+ if (typeof window.ENV_MAIN_PORT !== 'undefined') {
452
+ return window.ENV_MAIN_PORT;
453
+ }
454
+
455
+ // Default fallback
456
+ return null;
457
+ }
458
+
459
+ // Update connection status
460
+ function updateConnectionStatus(status, message = '') {
461
+ const statusElement = document.getElementById('connectionStatus');
462
+ const serverEndpoint = document.getElementById('serverEndpoint');
463
+
464
+ connectionStatus = status;
465
+ statusElement.className = `connection-status ${status}`;
466
+
467
+ switch (status) {
468
+ case 'connected':
469
+ statusElement.textContent = 'Connected';
470
+ serverEndpoint.textContent = `${currentServerUrl} - Active`;
471
+ // Update status in config section
472
+ const configStatus = document.getElementById('currentServerStatus');
473
+ if (configStatus) {
474
+ const serverType = currentServerUrl === DEMO_SERVER_URL ? 'Demo Server' : 'Remote Server';
475
+ configStatus.innerHTML = `<span style="color: #28a745;">✅ Connected to ${serverType}</span>`;
476
+ }
477
+ break;
478
+ case 'disconnected':
479
+ statusElement.textContent = 'Disconnected';
480
+ serverEndpoint.textContent = `${currentServerUrl} - Unavailable`;
481
+ const configStatusDisc = document.getElementById('currentServerStatus');
482
+ if (configStatusDisc) {
483
+ configStatusDisc.innerHTML = `<span style="color: #dc3545;">❌ Server Unavailable</span>`;
484
+ }
485
+ break;
486
+ case 'connecting':
487
+ statusElement.textContent = 'Connecting...';
488
+ serverEndpoint.textContent = `Connecting to ${currentServerUrl}...`;
489
+ const configStatusConn = document.getElementById('currentServerStatus');
490
+ if (configStatusConn) {
491
+ configStatusConn.innerHTML = `<span style="color: #ffc107;">🔄 Connecting...</span>`;
492
+ }
493
+ break;
494
+ }
495
+
496
+ if (message) {
497
+ showError(message);
498
+ }
499
+ }
500
+
501
+ // Show error message
502
+ function showError(message) {
503
+ const errorContainer = document.getElementById('errorContainer');
504
+ errorContainer.innerHTML = `
505
+ <div class="error-message">
506
+ <strong>Connection Error:</strong> ${message}
507
+ <br><small>Falling back to mock data for demonstration</small>
508
+ </div>
509
+ `;
510
+
511
+ // Auto-hide after 5 seconds
512
+ setTimeout(() => {
513
+ errorContainer.innerHTML = '';
514
+ }, 5000);
515
+ }
516
+
517
+ // Fetch data from analytics server
518
+ async function fetchAnalyticsData() {
519
+ try {
520
+ updateConnectionStatus('connecting');
521
+ console.log(`🔄 Attempting connection to: ${currentServerUrl}`);
522
+
523
+ // Create a timeout promise
524
+ const timeoutPromise = new Promise((_, reject) =>
525
+ setTimeout(() => reject(new Error('Request timeout')), 3000)
526
+ );
527
+
528
+ // Try to fetch data from analytics server with timeout
529
+ const fetchPromise = fetch(`${currentServerUrl}/api/analytics/overview`, {
530
+ method: 'GET',
531
+ headers: {
532
+ 'Content-Type': 'application/json'
533
+ },
534
+ mode: 'cors'
535
+ });
536
+
537
+ const response = await Promise.race([fetchPromise, timeoutPromise]);
538
+
539
+ if (!response.ok) {
540
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
541
+ }
542
+
543
+ const data = await response.json();
544
+ console.log(`✅ Successfully connected to: ${currentServerUrl}`);
545
+ updateConnectionStatus('connected');
546
+ retryCount = 0;
547
+ return data;
548
+
549
+ } catch (error) {
550
+ console.warn(`❌ Connection failed to ${currentServerUrl}:`, error.message);
551
+
552
+ // If we're using demo server and it fails, try mock data
553
+ if (currentServerUrl === DEMO_SERVER_URL) {
554
+ if (retryCount < maxRetries) {
555
+ retryCount++;
556
+ console.log(`🔄 Retry ${retryCount}/${maxRetries} for demo server...`);
557
+ updateConnectionStatus('connecting', `Demo server retry ${retryCount}/${maxRetries}: ${error.message}`);
558
+
559
+ // Wait before retry
560
+ await new Promise(resolve => setTimeout(resolve, 1000));
561
+ return fetchAnalyticsData();
562
+ } else {
563
+ console.log('🔄 Demo server failed, trying remote server...');
564
+ currentServerUrl = ANALYTICS_BASE_URL;
565
+ retryCount = 0;
566
+ updateConnectionStatus('connecting', `Demo server unavailable, trying remote server...`);
567
+ return fetchAnalyticsData();
568
+ }
569
+ } else {
570
+ // Remote server failed, use mock data
571
+ console.log('❌ All servers failed, using mock data');
572
+ updateConnectionStatus('disconnected', `Cannot reach any analytics server: ${error.message}`);
573
+ return generateMockData();
574
+ }
575
+ }
576
+ }
577
+
578
+ // Generate mock data when server is unavailable
579
+ function generateMockData() {
580
+ const now = new Date();
581
+ return {
582
+ timestamp: now.toISOString(),
583
+ metrics: {
584
+ totalUsers: Math.floor(Math.random() * 5000) + 1000,
585
+ totalObjects: Math.floor(Math.random() * 20000) + 5000,
586
+ apiCalls: Math.floor(Math.random() * 1000) + 200,
587
+ activeSessions: Math.floor(Math.random() * 100) + 20
588
+ },
589
+ changes: {
590
+ users: '+' + (Math.random() * 20 + 5).toFixed(1) + '% this month',
591
+ objects: '+' + (Math.random() * 15 + 3).toFixed(1) + '% this week',
592
+ api: (Math.random() > 0.5 ? '+' : '-') + (Math.random() * 10 + 1).toFixed(1) + '% today',
593
+ sessions: '+' + (Math.random() * 25 + 5).toFixed(1) + '% this hour'
594
+ },
595
+ source: 'mock' // Indicates this is fallback data
596
+ };
597
+ }
598
+
599
+ // Update the dashboard with new data
600
+ function updateDashboard(data) {
601
+ const timestamp = new Date(data.timestamp).toLocaleTimeString();
602
+
603
+ // Update metrics
604
+ document.getElementById('totalUsers').textContent = data.metrics.totalUsers.toLocaleString();
605
+ document.getElementById('totalObjects').textContent = data.metrics.totalObjects.toLocaleString();
606
+ document.getElementById('apiCalls').textContent = data.metrics.apiCalls.toLocaleString();
607
+ document.getElementById('activeSessions').textContent = data.metrics.activeSessions.toLocaleString();
608
+
609
+ // Update changes
610
+ document.getElementById('usersChange').textContent = data.changes.users;
611
+ document.getElementById('objectsChange').textContent = data.changes.objects;
612
+ document.getElementById('apiChange').textContent = data.changes.api;
613
+ document.getElementById('sessionsChange').textContent = data.changes.sessions;
614
+
615
+ // Update timestamps
616
+ document.getElementById('usersTimestamp').textContent = `Updated: ${timestamp}`;
617
+ document.getElementById('objectsTimestamp').textContent = `Updated: ${timestamp}`;
618
+ document.getElementById('apiTimestamp').textContent = `Updated: ${timestamp}`;
619
+ document.getElementById('sessionsTimestamp').textContent = `Updated: ${timestamp}`;
620
+
621
+ // Update chart placeholders with data indicators
622
+ const chartStatus = data.source === 'mock' ? 'Mock Data (Server Unavailable)' : 'Live Data from Analytics Server';
623
+
624
+ document.getElementById('userGrowthChart').innerHTML = `
625
+ <div style="text-align: center; line-height: 1.4;">
626
+ 📈 User Growth Chart<br>
627
+ <small style="opacity: 0.7;">${chartStatus}</small><br>
628
+ <strong style="font-size: 24px; margin-top: 10px; display: block;">${data.metrics.totalUsers.toLocaleString()}</strong>
629
+ <small>Total Users</small>
630
+ </div>
631
+ `;
632
+
633
+ document.getElementById('apiUsageChart').innerHTML = `
634
+ <div style="text-align: center; line-height: 1.4;">
635
+ 🔄 API Usage Chart<br>
636
+ <small style="opacity: 0.7;">${chartStatus}</small><br>
637
+ <strong style="font-size: 24px; margin-top: 10px; display: block;">${data.metrics.apiCalls.toLocaleString()}</strong>
638
+ <small>API Calls (24h)</small>
639
+ </div>
640
+ `;
641
+
642
+ document.getElementById('performanceChart').innerHTML = `
643
+ <div style="text-align: center; line-height: 1.4;">
644
+ ⚡ Performance Chart<br>
645
+ <small style="opacity: 0.7;">${chartStatus}</small><br>
646
+ <strong style="font-size: 24px; margin-top: 10px; display: block;">${data.metrics.activeSessions}</strong>
647
+ <small>Active Sessions</small>
648
+ </div>
649
+ `;
650
+
651
+ document.getElementById('storageChart').innerHTML = `
652
+ <div style="text-align: center; line-height: 1.4;">
653
+ 💾 Storage Chart<br>
654
+ <small style="opacity: 0.7;">${chartStatus}</small><br>
655
+ <strong style="font-size: 24px; margin-top: 10px; display: block;">${data.metrics.totalObjects.toLocaleString()}</strong>
656
+ <small>Total Objects</small>
657
+ </div>
658
+ `;
659
+ }
660
+
661
+ // Refresh data manually
662
+ async function refreshData() {
663
+ const data = await fetchAnalyticsData();
664
+ updateDashboard(data);
665
+ }
666
+
667
+ // Initialize dashboard
668
+ async function initDashboard() {
669
+ console.log('🚀 Initializing Remote Analytics Dashboard');
670
+ console.log(`📡 Demo server: ${DEMO_SERVER_URL}`);
671
+ console.log(`🔄 Remote server: ${ANALYTICS_BASE_URL}`);
672
+
673
+ // Initial data load
674
+ const data = await fetchAnalyticsData();
675
+ updateDashboard(data);
676
+
677
+ // Set up auto-refresh every 30 seconds
678
+ setInterval(async () => {
679
+ if (connectionStatus !== 'connecting') {
680
+ const data = await fetchAnalyticsData();
681
+ updateDashboard(data);
682
+ }
683
+ }, 30000);
684
+
685
+ // Add some visual feedback
686
+ const cards = document.querySelectorAll('.metric-card, .chart-card');
687
+ cards.forEach((card, index) => {
688
+ card.style.opacity = '0';
689
+ card.style.transform = 'translateY(30px)';
690
+ setTimeout(() => {
691
+ card.style.transition = 'all 0.6s ease';
692
+ card.style.opacity = '1';
693
+ card.style.transform = 'translateY(0)';
694
+ }, index * 150);
695
+ });
696
+ }
697
+
698
+ // Start when page loads
699
+ document.addEventListener('DOMContentLoaded', initDashboard);
700
+
701
+ // Handle page visibility changes to pause/resume updates
702
+ document.addEventListener('visibilitychange', () => {
703
+ if (!document.hidden && connectionStatus === 'disconnected') {
704
+ console.log('🔄 Page visible again, attempting to reconnect...');
705
+ refreshData();
706
+ }
707
+ });
708
+ </script>
709
+ </body>
710
+ </html>