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.
- package/Parse-Dashboard/app.js +84 -10
- package/Parse-Dashboard/parse-dashboard-config.json +7 -5
- package/Parse-Dashboard/public/analytics.html +9 -8
- package/Parse-Dashboard/public/bundles/dashboard.bundle.js +1 -1
- package/Parse-Dashboard/public/custom-remote-analytics.html +710 -0
- package/Parse-Dashboard/public/simple-analytics.html +294 -0
- package/package.json +1 -1
|
@@ -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>
|