···
4
-
<meta charset="UTF-8" />
5
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
-
<title>Cachet Analytics Dashboard</title>
7
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
-
box-sizing: border-box;
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Cachet Analytics Dashboard</title>
7
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
+
box-sizing: border-box;
17
-
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
19
-
background: #f5f5f5;
16
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
18
+
background: #f5f5f5;
26
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
27
-
margin-bottom: 2rem;
29
-
justify-content: space-between;
30
-
align-items: center;
25
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
26
+
margin-bottom: 2rem;
28
+
justify-content: space-between;
29
+
align-items: center;
40
-
text-decoration: none;
39
+
text-decoration: none;
43
-
.header-links a:hover {
44
-
text-decoration: underline;
42
+
.header-links a:hover {
43
+
text-decoration: underline;
48
-
margin-bottom: 2rem;
47
+
margin-bottom: 2rem;
54
-
padding: 0.5rem 1rem;
56
-
border: 1px solid #ddd;
53
+
padding: 0.5rem 1rem;
55
+
border: 1px solid #ddd;
63
-
background: #3498db;
62
+
background: #3498db;
68
-
.controls button:hover {
69
-
background: #2980b9;
67
+
.controls button:hover {
68
+
background: #2980b9;
80
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
82
-
margin-bottom: 2rem;
79
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
81
+
margin-bottom: 2rem;
89
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
88
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
101
-
margin-top: 0.5rem;
100
+
margin-top: 0.5rem;
106
-
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
108
-
margin-bottom: 2rem;
105
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
107
+
margin-bottom: 2rem;
114
-
border-radius: 8px;
115
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
113
+
border-radius: 8px;
114
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
120
-
margin-bottom: 1rem;
119
+
margin-bottom: 1rem;
125
-
text-align: center;
124
+
text-align: center;
131
-
background: #e74c3c;
134
-
border-radius: 4px;
130
+
background: #e74c3c;
133
+
border-radius: 4px;
140
-
align-items: center;
142
-
justify-content: center;
139
+
align-items: center;
141
+
justify-content: center;
146
-
.auto-refresh input[type="checkbox"] {
147
-
transform: scale(1.2);
145
+
.auto-refresh input[type="checkbox"] {
146
+
transform: scale(1.2);
150
-
@media (max-width: 768px) {
152
-
grid-template-columns: 1fr;
149
+
@media (max-width: 768px) {
151
+
grid-template-columns: 1fr;
160
-
flex-direction: column;
162
-
text-align: center;
168
-
<div class="header">
169
-
<h1>📊 Cachet Analytics Dashboard</h1>
170
-
<div class="header-links">
171
-
<a href="/swagger">API Docs</a>
172
-
<a href="/stats">Raw Stats</a>
159
+
flex-direction: column;
161
+
text-align: center;
167
+
<div class="header">
168
+
<h1>📊 Cachet Analytics Dashboard</h1>
169
+
<div class="header-links">
170
+
<a href="https://github.com/taciturnaxolotl/cachet">Github</a>
171
+
<a href="/swagger">API Docs</a>
172
+
<a href="/stats">Raw Stats</a>
176
-
<div class="dashboard">
177
-
<div class="controls">
178
-
<select id="daysSelect">
179
-
<option value="1">Last 24 hours</option>
180
-
<option value="7" selected>Last 7 days</option>
181
-
<option value="30">Last 30 days</option>
183
-
<button onclick="loadData()">Refresh</button>
184
-
<div class="auto-refresh">
185
-
<input type="checkbox" id="autoRefresh" />
186
-
<label for="autoRefresh">Auto-refresh (30s)</label>
176
+
<div class="dashboard">
177
+
<div class="controls">
178
+
<select id="daysSelect">
179
+
<option value="1">Last 24 hours</option>
180
+
<option value="7" selected>Last 7 days</option>
181
+
<option value="30">Last 30 days</option>
183
+
<button onclick="loadData()">Refresh</button>
184
+
<div class="auto-refresh">
185
+
<input type="checkbox" id="autoRefresh" />
186
+
<label for="autoRefresh">Auto-refresh (30s)</label>
190
-
<div id="loading" class="loading">Loading analytics data...</div>
191
-
<div id="error" class="error" style="display: none"></div>
190
+
<div id="loading" class="loading">Loading analytics data...</div>
191
+
<div id="error" class="error" style="display: none"></div>
193
-
<div id="content" style="display: none">
195
-
class="chart-container"
196
-
style="margin-bottom: 2rem; height: 450px"
198
-
<div class="chart-title">
199
-
Traffic Overview - All Routes Over Time
202
-
id="trafficOverviewChart"
203
-
style="padding-bottom: 2rem"
193
+
<div id="content" style="display: none">
194
+
<div class="chart-container" style="margin-bottom: 2rem; height: 450px">
195
+
<div class="chart-title">Traffic Overview - All Routes Over Time</div>
197
+
id="trafficOverviewChart"
198
+
style="padding-bottom: 2rem"
207
-
<div class="stats-grid">
208
-
<div class="stat-card">
209
-
<div class="stat-number" id="totalRequests">-</div>
210
-
<div class="stat-label">Total Requests</div>
212
-
<div class="stat-card">
213
-
<div class="stat-number" id="avgResponseTime">-</div>
214
-
<div class="stat-label">Avg Response Time (ms)</div>
216
-
<div class="stat-card">
217
-
<div class="stat-number" id="p95ResponseTime">-</div>
218
-
<div class="stat-label">P95 Response Time (ms)</div>
220
-
<div class="stat-card">
221
-
<div class="stat-number" id="uniqueEndpoints">-</div>
222
-
<div class="stat-label">Unique Endpoints</div>
224
-
<div class="stat-card">
225
-
<div class="stat-number" id="errorRate">-</div>
226
-
<div class="stat-label">Error Rate (%)</div>
228
-
<div class="stat-card">
229
-
<div class="stat-number" id="fastRequests">-</div>
230
-
<div class="stat-label">Fast Requests (<100ms)</div>
232
-
<div class="stat-card">
233
-
<div class="stat-number" id="uptime">-</div>
234
-
<div class="stat-label">Uptime (%)</div>
236
-
<div class="stat-card">
237
-
<div class="stat-number" id="throughput">-</div>
238
-
<div class="stat-label">Throughput (req/hr)</div>
240
-
<div class="stat-card">
241
-
<div class="stat-number" id="apdex">-</div>
242
-
<div class="stat-label">APDEX Score</div>
244
-
<div class="stat-card">
245
-
<div class="stat-number" id="cacheHitRate">-</div>
246
-
<div class="stat-label">Cache Hit Rate (%)</div>
202
+
<div class="stats-grid">
203
+
<div class="stat-card">
204
+
<div class="stat-number" id="totalRequests">-</div>
205
+
<div class="stat-label">Total Requests</div>
207
+
<div class="stat-card">
208
+
<div class="stat-number" id="avgResponseTime">-</div>
209
+
<div class="stat-label">Avg Response Time (ms)</div>
211
+
<div class="stat-card">
212
+
<div class="stat-number" id="p95ResponseTime">-</div>
213
+
<div class="stat-label">P95 Response Time (ms)</div>
215
+
<div class="stat-card">
216
+
<div class="stat-number" id="uniqueEndpoints">-</div>
217
+
<div class="stat-label">Unique Endpoints</div>
219
+
<div class="stat-card">
220
+
<div class="stat-number" id="errorRate">-</div>
221
+
<div class="stat-label">Error Rate (%)</div>
223
+
<div class="stat-card">
224
+
<div class="stat-number" id="fastRequests">-</div>
225
+
<div class="stat-label">Fast Requests (<100ms)</div>
227
+
<div class="stat-card">
228
+
<div class="stat-number" id="uptime">-</div>
229
+
<div class="stat-label">Uptime (%)</div>
231
+
<div class="stat-card">
232
+
<div class="stat-number" id="throughput">-</div>
233
+
<div class="stat-label">Throughput (req/hr)</div>
235
+
<div class="stat-card">
236
+
<div class="stat-number" id="apdex">-</div>
237
+
<div class="stat-label">APDEX Score</div>
239
+
<div class="stat-card">
240
+
<div class="stat-number" id="cacheHitRate">-</div>
241
+
<div class="stat-label">Cache Hit Rate (%)</div>
250
-
<div class="stats-grid">
251
-
<div class="stat-card">
252
-
<div class="stat-number" id="peakHour">-</div>
253
-
<div class="stat-label">Peak Hour</div>
255
-
<div class="stat-card">
256
-
<div class="stat-number" id="peakHourRequests">-</div>
257
-
<div class="stat-label">Peak Hour Requests</div>
259
-
<div class="stat-card">
260
-
<div class="stat-number" id="peakDay">-</div>
261
-
<div class="stat-label">Peak Day</div>
263
-
<div class="stat-card">
264
-
<div class="stat-number" id="peakDayRequests">-</div>
265
-
<div class="stat-label">Peak Day Requests</div>
267
-
<div class="stat-card">
268
-
<div class="stat-number" id="dashboardRequests">-</div>
269
-
<div class="stat-label">Dashboard Requests</div>
245
+
<div class="stats-grid">
246
+
<div class="stat-card">
247
+
<div class="stat-number" id="peakHour">-</div>
248
+
<div class="stat-label">Peak Hour</div>
250
+
<div class="stat-card">
251
+
<div class="stat-number" id="peakHourRequests">-</div>
252
+
<div class="stat-label">Peak Hour Requests</div>
254
+
<div class="stat-card">
255
+
<div class="stat-number" id="peakDay">-</div>
256
+
<div class="stat-label">Peak Day</div>
258
+
<div class="stat-card">
259
+
<div class="stat-number" id="peakDayRequests">-</div>
260
+
<div class="stat-label">Peak Day Requests</div>
262
+
<div class="stat-card">
263
+
<div class="stat-number" id="dashboardRequests">-</div>
264
+
<div class="stat-label">Dashboard Requests</div>
273
-
<div class="charts-grid">
274
-
<div class="chart-container">
275
-
<div class="chart-title">Requests Over Time</div>
276
-
<canvas id="timeChart"></canvas>
268
+
<div class="charts-grid">
269
+
<div class="chart-container">
270
+
<div class="chart-title">Requests Over Time</div>
271
+
<canvas id="timeChart"></canvas>
279
-
<div class="chart-container">
280
-
<div class="chart-title">
281
-
Latency Over Time (Hourly)
283
-
<canvas id="latencyTimeChart"></canvas>
274
+
<div class="chart-container">
275
+
<div class="chart-title">Latency Over Time (Hourly)</div>
276
+
<canvas id="latencyTimeChart"></canvas>
286
-
<div class="chart-container">
287
-
<div class="chart-title">
288
-
Response Time Distribution
290
-
<canvas id="latencyDistributionChart"></canvas>
279
+
<div class="chart-container">
280
+
<div class="chart-title">Response Time Distribution</div>
281
+
<canvas id="latencyDistributionChart"></canvas>
293
-
<div class="chart-container">
294
-
<div class="chart-title">Latency Percentiles</div>
295
-
<canvas id="percentilesChart"></canvas>
284
+
<div class="chart-container">
285
+
<div class="chart-title">Latency Percentiles</div>
286
+
<canvas id="percentilesChart"></canvas>
298
-
<div class="chart-container">
299
-
<div class="chart-title">Top Endpoints</div>
300
-
<canvas id="endpointChart"></canvas>
289
+
<div class="chart-container">
290
+
<div class="chart-title">Top Endpoints</div>
291
+
<canvas id="endpointChart"></canvas>
303
-
<div class="chart-container">
304
-
<div class="chart-title">Slowest Endpoints</div>
305
-
<canvas id="slowestEndpointsChart"></canvas>
294
+
<div class="chart-container">
295
+
<div class="chart-title">Slowest Endpoints</div>
296
+
<canvas id="slowestEndpointsChart"></canvas>
308
-
<div class="chart-container">
309
-
<div class="chart-title">Status Codes</div>
310
-
<canvas id="statusChart"></canvas>
299
+
<div class="chart-container">
300
+
<div class="chart-title">Status Codes</div>
301
+
<canvas id="statusChart"></canvas>
313
-
<div class="chart-container">
314
-
<div class="chart-title">Top User Agents</div>
315
-
<canvas id="userAgentChart"></canvas>
304
+
<div class="chart-container">
305
+
<div class="chart-title">Top User Agents</div>
306
+
<canvas id="userAgentChart"></canvas>
323
-
let autoRefreshInterval;
314
+
let autoRefreshInterval;
325
-
async function loadData() {
326
-
const days = document.getElementById("daysSelect").value;
327
-
const loading = document.getElementById("loading");
328
-
const error = document.getElementById("error");
329
-
const content = document.getElementById("content");
316
+
async function loadData() {
317
+
const days = document.getElementById("daysSelect").value;
318
+
const loading = document.getElementById("loading");
319
+
const error = document.getElementById("error");
320
+
const content = document.getElementById("content");
331
-
loading.style.display = "block";
332
-
error.style.display = "none";
333
-
content.style.display = "none";
322
+
loading.style.display = "block";
323
+
error.style.display = "none";
324
+
content.style.display = "none";
336
-
const response = await fetch(`/stats?days=${days}`);
338
-
throw new Error(`HTTP ${response.status}`);
327
+
const response = await fetch(`/stats?days=${days}`);
328
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
340
-
const data = await response.json();
341
-
updateDashboard(data);
330
+
const data = await response.json();
331
+
updateDashboard(data);
343
-
loading.style.display = "none";
344
-
content.style.display = "block";
346
-
loading.style.display = "none";
347
-
error.style.display = "block";
348
-
error.textContent = `Failed to load data: ${err.message}`;
333
+
loading.style.display = "none";
334
+
content.style.display = "block";
336
+
loading.style.display = "none";
337
+
error.style.display = "block";
338
+
error.textContent = `Failed to load data: ${err.message}`;
352
-
function updateDashboard(data) {
354
-
document.getElementById("totalRequests").textContent =
355
-
data.totalRequests.toLocaleString();
356
-
document.getElementById("avgResponseTime").textContent =
357
-
data.averageResponseTime
358
-
? Math.round(data.averageResponseTime)
360
-
document.getElementById("p95ResponseTime").textContent = data
361
-
.latencyAnalytics.percentiles.p95
362
-
? Math.round(data.latencyAnalytics.percentiles.p95)
364
-
document.getElementById("uniqueEndpoints").textContent =
365
-
data.requestsByEndpoint.length;
342
+
function updateDashboard(data) {
344
+
document.getElementById("totalRequests").textContent =
345
+
data.totalRequests.toLocaleString();
346
+
document.getElementById("avgResponseTime").textContent =
347
+
data.averageResponseTime
348
+
? Math.round(data.averageResponseTime)
350
+
document.getElementById("p95ResponseTime").textContent = data
351
+
.latencyAnalytics.percentiles.p95
352
+
? Math.round(data.latencyAnalytics.percentiles.p95)
354
+
document.getElementById("uniqueEndpoints").textContent =
355
+
data.requestsByEndpoint.length;
367
-
const errorRequests = data.requestsByStatus
368
-
.filter((s) => s.status >= 400)
369
-
.reduce((sum, s) => sum + s.count, 0);
371
-
data.totalRequests > 0
372
-
? ((errorRequests / data.totalRequests) * 100).toFixed(
376
-
document.getElementById("errorRate").textContent = errorRate;
357
+
const errorRequests = data.requestsByStatus
358
+
.filter((s) => s.status >= 400)
359
+
.reduce((sum, s) => sum + s.count, 0);
361
+
data.totalRequests > 0
362
+
? ((errorRequests / data.totalRequests) * 100).toFixed(1)
364
+
document.getElementById("errorRate").textContent = errorRate;
378
-
// Calculate fast requests percentage
379
-
const fastRequestsData = data.latencyAnalytics.distribution
381
-
(d) => d.range === "0-50ms" || d.range === "50-100ms",
383
-
.reduce((sum, d) => sum + d.percentage, 0);
384
-
document.getElementById("fastRequests").textContent =
385
-
fastRequestsData.toFixed(1) + "%";
366
+
// Calculate fast requests percentage
367
+
const fastRequestsData = data.latencyAnalytics.distribution
368
+
.filter((d) => d.range === "0-50ms" || d.range === "50-100ms")
369
+
.reduce((sum, d) => sum + d.percentage, 0);
370
+
document.getElementById("fastRequests").textContent =
371
+
fastRequestsData.toFixed(1) + "%";
387
-
// Performance metrics
388
-
document.getElementById("uptime").textContent =
389
-
data.performanceMetrics.uptime.toFixed(1);
390
-
document.getElementById("throughput").textContent = Math.round(
391
-
data.performanceMetrics.throughput,
393
-
document.getElementById("apdex").textContent =
394
-
data.performanceMetrics.apdex.toFixed(2);
395
-
document.getElementById("cacheHitRate").textContent =
396
-
data.performanceMetrics.cachehitRate.toFixed(1);
373
+
// Performance metrics
374
+
document.getElementById("uptime").textContent =
375
+
data.performanceMetrics.uptime.toFixed(1);
376
+
document.getElementById("throughput").textContent = Math.round(
377
+
data.performanceMetrics.throughput,
379
+
document.getElementById("apdex").textContent =
380
+
data.performanceMetrics.apdex.toFixed(2);
381
+
document.getElementById("cacheHitRate").textContent =
382
+
data.performanceMetrics.cachehitRate.toFixed(1);
399
-
document.getElementById("peakHour").textContent =
400
-
data.peakTraffic.peakHour;
401
-
document.getElementById("peakHourRequests").textContent =
402
-
data.peakTraffic.peakRequests.toLocaleString();
403
-
document.getElementById("peakDay").textContent =
404
-
data.peakTraffic.peakDay;
405
-
document.getElementById("peakDayRequests").textContent =
406
-
data.peakTraffic.peakDayRequests.toLocaleString();
385
+
document.getElementById("peakHour").textContent =
386
+
data.peakTraffic.peakHour;
387
+
document.getElementById("peakHourRequests").textContent =
388
+
data.peakTraffic.peakRequests.toLocaleString();
389
+
document.getElementById("peakDay").textContent =
390
+
data.peakTraffic.peakDay;
391
+
document.getElementById("peakDayRequests").textContent =
392
+
data.peakTraffic.peakDayRequests.toLocaleString();
408
-
// Dashboard metrics
409
-
document.getElementById("dashboardRequests").textContent =
410
-
data.dashboardMetrics.statsRequests.toLocaleString();
394
+
// Dashboard metrics
395
+
document.getElementById("dashboardRequests").textContent =
396
+
data.dashboardMetrics.statsRequests.toLocaleString();
412
-
// Determine if we're showing hourly or daily data
413
-
const days = parseInt(
414
-
document.getElementById("daysSelect").value,
416
-
const isHourly = days === 1;
398
+
// Determine if we're showing hourly or daily data
399
+
const days = parseInt(document.getElementById("daysSelect").value);
400
+
const isHourly = days === 1;
418
-
updateTrafficOverviewChart(data.trafficOverview, days);
419
-
updateTimeChart(data.requestsByDay, isHourly);
420
-
updateLatencyTimeChart(
421
-
data.latencyAnalytics.latencyOverTime,
424
-
updateLatencyDistributionChart(
425
-
data.latencyAnalytics.distribution,
427
-
updatePercentilesChart(data.latencyAnalytics.percentiles);
428
-
updateEndpointChart(data.requestsByEndpoint.slice(0, 10));
429
-
updateSlowestEndpointsChart(
430
-
data.latencyAnalytics.slowestEndpoints,
432
-
updateStatusChart(data.requestsByStatus);
433
-
updateUserAgentChart(data.topUserAgents.slice(0, 5));
402
+
updateTrafficOverviewChart(data.trafficOverview, days);
403
+
updateTimeChart(data.requestsByDay, isHourly);
404
+
updateLatencyTimeChart(data.latencyAnalytics.latencyOverTime, isHourly);
405
+
updateLatencyDistributionChart(data.latencyAnalytics.distribution);
406
+
updatePercentilesChart(data.latencyAnalytics.percentiles);
407
+
updateEndpointChart(data.requestsByEndpoint.slice(0, 10));
408
+
updateSlowestEndpointsChart(data.latencyAnalytics.slowestEndpoints);
409
+
updateStatusChart(data.requestsByStatus);
410
+
updateUserAgentChart(data.topUserAgents.slice(0, 5));
436
-
function updateTrafficOverviewChart(data, days) {
437
-
const ctx = document
438
-
.getElementById("trafficOverviewChart")
413
+
function updateTrafficOverviewChart(data, days) {
414
+
const ctx = document
415
+
.getElementById("trafficOverviewChart")
441
-
if (charts.trafficOverview) charts.trafficOverview.destroy();
418
+
if (charts.trafficOverview) charts.trafficOverview.destroy();
443
-
// Update chart title based on granularity
444
-
const chartTitle = document
445
-
.querySelector("#trafficOverviewChart")
446
-
.parentElement.querySelector(".chart-title");
447
-
let titleText = "Traffic Overview - All Routes Over Time";
449
-
titleText += " (Hourly)";
450
-
} else if (days <= 7) {
451
-
titleText += " (4-Hour Intervals)";
453
-
titleText += " (Daily)";
455
-
chartTitle.textContent = titleText;
420
+
// Update chart title based on granularity
421
+
const chartTitle = document
422
+
.querySelector("#trafficOverviewChart")
423
+
.parentElement.querySelector(".chart-title");
424
+
let titleText = "Traffic Overview - All Routes Over Time";
426
+
titleText += " (Hourly)";
427
+
} else if (days <= 7) {
428
+
titleText += " (4-Hour Intervals)";
430
+
titleText += " (Daily)";
432
+
chartTitle.textContent = titleText;
457
-
// Get all unique routes across all time periods
458
-
const allRoutes = new Set();
459
-
data.forEach((timePoint) => {
460
-
Object.keys(timePoint.routes).forEach((route) =>
461
-
allRoutes.add(route),
434
+
// Get all unique routes across all time periods
435
+
const allRoutes = new Set();
436
+
data.forEach((timePoint) => {
437
+
Object.keys(timePoint.routes).forEach((route) =>
438
+
allRoutes.add(route),
465
-
// Define colors for different route types
466
-
const routeColors = {
467
-
Dashboard: "#3498db",
468
-
"User Data": "#2ecc71",
469
-
"User Redirects": "#27ae60",
470
-
"Emoji Data": "#e74c3c",
471
-
"Emoji Redirects": "#c0392b",
472
-
"Emoji List": "#e67e22",
473
-
"Health Check": "#f39c12",
474
-
"API Documentation": "#9b59b6",
475
-
"Cache Management": "#34495e",
442
+
// Define colors for different route types
443
+
const routeColors = {
444
+
Dashboard: "#3498db",
445
+
"User Data": "#2ecc71",
446
+
"User Redirects": "#27ae60",
447
+
"Emoji Data": "#e74c3c",
448
+
"Emoji Redirects": "#c0392b",
449
+
"Emoji List": "#e67e22",
450
+
"Health Check": "#f39c12",
451
+
"API Documentation": "#9b59b6",
452
+
"Cache Management": "#34495e",
478
-
// Create datasets for each route
479
-
const datasets = Array.from(allRoutes).map((route) => {
480
-
const color = routeColors[route] || "#95a5a6";
484
-
(timePoint) => timePoint.routes[route] || 0,
486
-
borderColor: color,
487
-
backgroundColor: color + "20", // Add transparency
491
-
pointHoverRadius: 4,
455
+
// Create datasets for each route
456
+
const datasets = Array.from(allRoutes).map((route) => {
457
+
const color = routeColors[route] || "#95a5a6";
460
+
data: data.map((timePoint) => timePoint.routes[route] || 0),
461
+
borderColor: color,
462
+
backgroundColor: color + "20", // Add transparency
466
+
pointHoverRadius: 4,
495
-
// Format labels based on time granularity
496
-
const labels = data.map((timePoint) => {
498
-
// Show just hour for 24h view
499
-
return timePoint.time.split(" ")[1] || timePoint.time;
500
-
} else if (days <= 7) {
501
-
// Show day and hour for 7-day view
502
-
const parts = timePoint.time.split(" ");
503
-
const date = parts[0].split("-")[2]; // Get day
504
-
const hour = parts[1] || "00:00";
505
-
return `${date} ${hour}`;
507
-
// Show full date for longer periods
508
-
return timePoint.time;
470
+
// Format labels based on time granularity
471
+
const labels = data.map((timePoint) => {
473
+
// Show just hour for 24h view
474
+
return timePoint.time.split(" ")[1] || timePoint.time;
475
+
} else if (days <= 7) {
476
+
// Show day and hour for 7-day view
477
+
const parts = timePoint.time.split(" ");
478
+
const date = parts[0].split("-")[2]; // Get day
479
+
const hour = parts[1] || "00:00";
480
+
return `${date} ${hour}`;
482
+
// Show full date for longer periods
483
+
return timePoint.time;
512
-
charts.trafficOverview = new Chart(ctx, {
516
-
datasets: datasets,
520
-
maintainAspectRatio: false,
529
-
usePointStyle: true,
540
-
afterLabel: function (context) {
542
-
data[context.dataIndex];
543
-
return `Total: ${timePoint.total} requests`;
487
+
charts.trafficOverview = new Chart(ctx, {
491
+
datasets: datasets,
495
+
maintainAspectRatio: false,
504
+
usePointStyle: true,
515
+
afterLabel: function (context) {
516
+
const timePoint = data[context.dataIndex];
517
+
return `Total: ${timePoint.total} requests`;
527
+
text: days === 1 ? "Hour" : days <= 7 ? "Day & Hour" : "Date",
582
-
function updateTimeChart(data, isHourly) {
583
-
const ctx = document
584
-
.getElementById("timeChart")
551
+
function updateTimeChart(data, isHourly) {
552
+
const ctx = document.getElementById("timeChart").getContext("2d");
587
-
if (charts.time) charts.time.destroy();
554
+
if (charts.time) charts.time.destroy();
589
-
// Update chart title
590
-
const chartTitle = document
591
-
.querySelector("#timeChart")
592
-
.parentElement.querySelector(".chart-title");
593
-
chartTitle.textContent = isHourly
594
-
? "Requests Over Time (Hourly)"
595
-
: "Requests Over Time (Daily)";
556
+
// Update chart title
557
+
const chartTitle = document
558
+
.querySelector("#timeChart")
559
+
.parentElement.querySelector(".chart-title");
560
+
chartTitle.textContent = isHourly
561
+
? "Requests Over Time (Hourly)"
562
+
: "Requests Over Time (Daily)";
597
-
charts.time = new Chart(ctx, {
600
-
labels: data.map((d) =>
601
-
isHourly ? d.date.split(" ")[1] : d.date,
606
-
data: data.map((d) => d.count),
607
-
borderColor: "#3498db",
608
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
564
+
charts.time = new Chart(ctx, {
567
+
labels: data.map((d) => (isHourly ? d.date.split(" ")[1] : d.date)),
571
+
data: data.map((d) => d.count),
572
+
borderColor: "#3498db",
573
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
625
-
function updateEndpointChart(data) {
626
-
const ctx = document
627
-
.getElementById("endpointChart")
590
+
function updateEndpointChart(data) {
591
+
const ctx = document.getElementById("endpointChart").getContext("2d");
630
-
if (charts.endpoint) charts.endpoint.destroy();
593
+
if (charts.endpoint) charts.endpoint.destroy();
632
-
charts.endpoint = new Chart(ctx, {
635
-
labels: data.map((d) => d.endpoint),
639
-
data: data.map((d) => d.count),
640
-
backgroundColor: "#2ecc71",
595
+
charts.endpoint = new Chart(ctx, {
598
+
labels: data.map((d) => d.endpoint),
602
+
data: data.map((d) => d.count),
603
+
backgroundColor: "#2ecc71",
656
-
function updateStatusChart(data) {
657
-
const ctx = document
658
-
.getElementById("statusChart")
619
+
function updateStatusChart(data) {
620
+
const ctx = document.getElementById("statusChart").getContext("2d");
661
-
if (charts.status) charts.status.destroy();
622
+
if (charts.status) charts.status.destroy();
663
-
const colors = data.map((d) => {
664
-
if (d.status >= 200 && d.status < 300) return "#2ecc71";
665
-
if (d.status >= 300 && d.status < 400) return "#f39c12";
666
-
if (d.status >= 400 && d.status < 500) return "#e74c3c";
624
+
const colors = data.map((d) => {
625
+
if (d.status >= 200 && d.status < 300) return "#2ecc71";
626
+
if (d.status >= 300 && d.status < 400) return "#f39c12";
627
+
if (d.status >= 400 && d.status < 500) return "#e74c3c";
670
-
charts.status = new Chart(ctx, {
673
-
labels: data.map((d) => `${d.status}`),
676
-
data: data.map((d) => d.count),
677
-
backgroundColor: colors,
631
+
charts.status = new Chart(ctx, {
634
+
labels: data.map((d) => `${d.status}`),
637
+
data: data.map((d) => d.count),
638
+
backgroundColor: colors,
687
-
function updateUserAgentChart(data) {
688
-
const ctx = document
689
-
.getElementById("userAgentChart")
648
+
function updateUserAgentChart(data) {
649
+
const ctx = document.getElementById("userAgentChart").getContext("2d");
692
-
if (charts.userAgent) charts.userAgent.destroy();
651
+
if (charts.userAgent) charts.userAgent.destroy();
694
-
charts.userAgent = new Chart(ctx, {
697
-
labels: data.map((d) => d.userAgent),
700
-
data: data.map((d) => d.count),
653
+
charts.userAgent = new Chart(ctx, {
656
+
labels: data.map((d) => d.userAgent),
659
+
data: data.map((d) => d.count),
722
-
function updateLatencyTimeChart(data, isHourly) {
723
-
const ctx = document
724
-
.getElementById("latencyTimeChart")
681
+
function updateLatencyTimeChart(data, isHourly) {
682
+
const ctx = document
683
+
.getElementById("latencyTimeChart")
727
-
if (charts.latencyTime) charts.latencyTime.destroy();
686
+
if (charts.latencyTime) charts.latencyTime.destroy();
729
-
// Update chart title
730
-
const chartTitle = document
731
-
.querySelector("#latencyTimeChart")
732
-
.parentElement.querySelector(".chart-title");
733
-
chartTitle.textContent = isHourly
734
-
? "Latency Over Time (Hourly)"
735
-
: "Latency Over Time (Daily)";
688
+
// Update chart title
689
+
const chartTitle = document
690
+
.querySelector("#latencyTimeChart")
691
+
.parentElement.querySelector(".chart-title");
692
+
chartTitle.textContent = isHourly
693
+
? "Latency Over Time (Hourly)"
694
+
: "Latency Over Time (Daily)";
737
-
charts.latencyTime = new Chart(ctx, {
740
-
labels: data.map((d) =>
741
-
isHourly ? d.time.split(" ")[1] : d.time,
745
-
label: "Average Response Time",
746
-
data: data.map((d) => d.averageResponseTime),
747
-
borderColor: "#3498db",
748
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
753
-
label: "P95 Response Time",
754
-
data: data.map((d) => d.p95),
755
-
borderColor: "#e74c3c",
756
-
backgroundColor: "rgba(231, 76, 60, 0.1)",
769
-
text: "Response Time (ms)",
696
+
charts.latencyTime = new Chart(ctx, {
699
+
labels: data.map((d) => (isHourly ? d.time.split(" ")[1] : d.time)),
702
+
label: "Average Response Time",
703
+
data: data.map((d) => d.averageResponseTime),
704
+
borderColor: "#3498db",
705
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
710
+
label: "P95 Response Time",
711
+
data: data.map((d) => d.p95),
712
+
borderColor: "#e74c3c",
713
+
backgroundColor: "rgba(231, 76, 60, 0.1)",
726
+
text: "Response Time (ms)",
777
-
function updateLatencyDistributionChart(data) {
778
-
const ctx = document
779
-
.getElementById("latencyDistributionChart")
734
+
function updateLatencyDistributionChart(data) {
735
+
const ctx = document
736
+
.getElementById("latencyDistributionChart")
782
-
if (charts.latencyDistribution)
783
-
charts.latencyDistribution.destroy();
739
+
if (charts.latencyDistribution) charts.latencyDistribution.destroy();
785
-
charts.latencyDistribution = new Chart(ctx, {
788
-
labels: data.map((d) => d.range),
792
-
data: data.map((d) => d.count),
793
-
backgroundColor: "#2ecc71",
741
+
charts.latencyDistribution = new Chart(ctx, {
744
+
labels: data.map((d) => d.range),
748
+
data: data.map((d) => d.count),
749
+
backgroundColor: "#2ecc71",
808
-
function updatePercentilesChart(percentiles) {
809
-
const ctx = document
810
-
.getElementById("percentilesChart")
764
+
function updatePercentilesChart(percentiles) {
765
+
const ctx = document
766
+
.getElementById("percentilesChart")
813
-
if (charts.percentiles) charts.percentiles.destroy();
769
+
if (charts.percentiles) charts.percentiles.destroy();
816
-
{ label: "P50", value: percentiles.p50 },
817
-
{ label: "P75", value: percentiles.p75 },
818
-
{ label: "P90", value: percentiles.p90 },
819
-
{ label: "P95", value: percentiles.p95 },
820
-
{ label: "P99", value: percentiles.p99 },
821
-
].filter((d) => d.value !== null);
772
+
{ label: "P50", value: percentiles.p50 },
773
+
{ label: "P75", value: percentiles.p75 },
774
+
{ label: "P90", value: percentiles.p90 },
775
+
{ label: "P95", value: percentiles.p95 },
776
+
{ label: "P99", value: percentiles.p99 },
777
+
].filter((d) => d.value !== null);
823
-
charts.percentiles = new Chart(ctx, {
826
-
labels: data.map((d) => d.label),
829
-
label: "Response Time (ms)",
830
-
data: data.map((d) => d.value),
779
+
charts.percentiles = new Chart(ctx, {
782
+
labels: data.map((d) => d.label),
785
+
label: "Response Time (ms)",
786
+
data: data.map((d) => d.value),
852
-
function updateSlowestEndpointsChart(data) {
853
-
const ctx = document
854
-
.getElementById("slowestEndpointsChart")
808
+
function updateSlowestEndpointsChart(data) {
809
+
const ctx = document
810
+
.getElementById("slowestEndpointsChart")
857
-
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
813
+
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
859
-
charts.slowestEndpoints = new Chart(ctx, {
862
-
labels: data.map((d) => d.endpoint),
865
-
label: "Avg Response Time (ms)",
866
-
data: data.map((d) => d.averageResponseTime),
867
-
backgroundColor: "#e74c3c",
815
+
charts.slowestEndpoints = new Chart(ctx, {
818
+
labels: data.map((d) => d.endpoint),
821
+
label: "Avg Response Time (ms)",
822
+
data: data.map((d) => d.averageResponseTime),
823
+
backgroundColor: "#e74c3c",
884
-
.getElementById("autoRefresh")
885
-
.addEventListener("change", function () {
886
-
if (this.checked) {
887
-
autoRefreshInterval = setInterval(loadData, 30000);
889
-
clearInterval(autoRefreshInterval);
840
+
.getElementById("autoRefresh")
841
+
.addEventListener("change", function () {
842
+
if (this.checked) {
843
+
autoRefreshInterval = setInterval(loadData, 30000);
845
+
clearInterval(autoRefreshInterval);
895
-
.getElementById("daysSelect")
896
-
.addEventListener("change", loadData);
851
+
.getElementById("daysSelect")
852
+
.addEventListener("change", loadData);