+762
-806
src/dashboard.html
+762
-806
src/dashboard.html
···
1
1
<!doctype html>
2
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>Cachet Analytics Dashboard</title>
7
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
-
<style>
9
-
* {
10
-
margin: 0;
11
-
padding: 0;
12
-
box-sizing: border-box;
13
-
}
3
+
<head>
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>
8
+
<style>
9
+
* {
10
+
margin: 0;
11
+
padding: 0;
12
+
box-sizing: border-box;
13
+
}
14
14
15
-
body {
16
-
font-family:
17
-
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
18
-
sans-serif;
19
-
background: #f5f5f5;
20
-
color: #333;
21
-
}
15
+
body {
16
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
17
+
sans-serif;
18
+
background: #f5f5f5;
19
+
color: #333;
20
+
}
22
21
23
-
.header {
24
-
background: #fff;
25
-
padding: 1rem 2rem;
26
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
27
-
margin-bottom: 2rem;
28
-
display: flex;
29
-
justify-content: space-between;
30
-
align-items: center;
31
-
}
22
+
.header {
23
+
background: #fff;
24
+
padding: 1rem 2rem;
25
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
26
+
margin-bottom: 2rem;
27
+
display: flex;
28
+
justify-content: space-between;
29
+
align-items: center;
30
+
}
32
31
33
-
.header h1 {
34
-
color: #2c3e50;
35
-
}
32
+
.header h1 {
33
+
color: #2c3e50;
34
+
}
36
35
37
-
.header-links a {
38
-
margin-left: 1rem;
39
-
color: #3498db;
40
-
text-decoration: none;
41
-
}
36
+
.header-links a {
37
+
margin-left: 1rem;
38
+
color: #3498db;
39
+
text-decoration: none;
40
+
}
42
41
43
-
.header-links a:hover {
44
-
text-decoration: underline;
45
-
}
42
+
.header-links a:hover {
43
+
text-decoration: underline;
44
+
}
46
45
47
-
.controls {
48
-
margin-bottom: 2rem;
49
-
text-align: center;
50
-
}
46
+
.controls {
47
+
margin-bottom: 2rem;
48
+
text-align: center;
49
+
}
51
50
52
-
.controls select,
53
-
.controls button {
54
-
padding: 0.5rem 1rem;
55
-
margin: 0 0.5rem;
56
-
border: 1px solid #ddd;
57
-
border-radius: 4px;
58
-
background: white;
59
-
cursor: pointer;
60
-
}
51
+
.controls select,
52
+
.controls button {
53
+
padding: 0.5rem 1rem;
54
+
margin: 0 0.5rem;
55
+
border: 1px solid #ddd;
56
+
border-radius: 4px;
57
+
background: white;
58
+
cursor: pointer;
59
+
}
61
60
62
-
.controls button {
63
-
background: #3498db;
64
-
color: white;
65
-
border: none;
66
-
}
61
+
.controls button {
62
+
background: #3498db;
63
+
color: white;
64
+
border: none;
65
+
}
67
66
68
-
.controls button:hover {
69
-
background: #2980b9;
70
-
}
67
+
.controls button:hover {
68
+
background: #2980b9;
69
+
}
71
70
72
-
.dashboard {
73
-
max-width: 1200px;
74
-
margin: 0 auto;
75
-
padding: 0 2rem;
76
-
}
71
+
.dashboard {
72
+
max-width: 1200px;
73
+
margin: 0 auto;
74
+
padding: 0 2rem;
75
+
}
77
76
78
-
.stats-grid {
79
-
display: grid;
80
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
81
-
gap: 1rem;
82
-
margin-bottom: 2rem;
83
-
}
77
+
.stats-grid {
78
+
display: grid;
79
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
80
+
gap: 1rem;
81
+
margin-bottom: 2rem;
82
+
}
84
83
85
-
.stat-card {
86
-
background: white;
87
-
padding: 1.5rem;
88
-
border-radius: 8px;
89
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
90
-
text-align: center;
91
-
}
84
+
.stat-card {
85
+
background: white;
86
+
padding: 1.5rem;
87
+
border-radius: 8px;
88
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
89
+
text-align: center;
90
+
}
92
91
93
-
.stat-number {
94
-
font-size: 2rem;
95
-
font-weight: bold;
96
-
color: #3498db;
97
-
}
92
+
.stat-number {
93
+
font-size: 2rem;
94
+
font-weight: bold;
95
+
color: #3498db;
96
+
}
98
97
99
-
.stat-label {
100
-
color: #666;
101
-
margin-top: 0.5rem;
102
-
}
98
+
.stat-label {
99
+
color: #666;
100
+
margin-top: 0.5rem;
101
+
}
103
102
104
-
.charts-grid {
105
-
display: grid;
106
-
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
107
-
gap: 2rem;
108
-
margin-bottom: 2rem;
109
-
}
103
+
.charts-grid {
104
+
display: grid;
105
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
106
+
gap: 2rem;
107
+
margin-bottom: 2rem;
108
+
}
110
109
111
-
.chart-container {
112
-
background: white;
113
-
padding: 1.5rem;
114
-
border-radius: 8px;
115
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
116
-
}
110
+
.chart-container {
111
+
background: white;
112
+
padding: 1.5rem;
113
+
border-radius: 8px;
114
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
115
+
}
117
116
118
-
.chart-title {
119
-
font-size: 1.2rem;
120
-
margin-bottom: 1rem;
121
-
color: #2c3e50;
122
-
}
117
+
.chart-title {
118
+
font-size: 1.2rem;
119
+
margin-bottom: 1rem;
120
+
color: #2c3e50;
121
+
}
123
122
124
-
.loading {
125
-
text-align: center;
126
-
padding: 2rem;
127
-
color: #666;
128
-
}
123
+
.loading {
124
+
text-align: center;
125
+
padding: 2rem;
126
+
color: #666;
127
+
}
129
128
130
-
.error {
131
-
background: #e74c3c;
132
-
color: white;
133
-
padding: 1rem;
134
-
border-radius: 4px;
135
-
margin: 1rem 0;
136
-
}
129
+
.error {
130
+
background: #e74c3c;
131
+
color: white;
132
+
padding: 1rem;
133
+
border-radius: 4px;
134
+
margin: 1rem 0;
135
+
}
137
136
138
-
.auto-refresh {
139
-
display: flex;
140
-
align-items: center;
141
-
gap: 0.5rem;
142
-
justify-content: center;
143
-
margin-top: 1rem;
144
-
}
137
+
.auto-refresh {
138
+
display: flex;
139
+
align-items: center;
140
+
gap: 0.5rem;
141
+
justify-content: center;
142
+
margin-top: 1rem;
143
+
}
145
144
146
-
.auto-refresh input[type="checkbox"] {
147
-
transform: scale(1.2);
148
-
}
145
+
.auto-refresh input[type="checkbox"] {
146
+
transform: scale(1.2);
147
+
}
149
148
150
-
@media (max-width: 768px) {
151
-
.charts-grid {
152
-
grid-template-columns: 1fr;
153
-
}
149
+
@media (max-width: 768px) {
150
+
.charts-grid {
151
+
grid-template-columns: 1fr;
152
+
}
154
153
155
-
.dashboard {
156
-
padding: 0 1rem;
157
-
}
154
+
.dashboard {
155
+
padding: 0 1rem;
156
+
}
158
157
159
-
.header {
160
-
flex-direction: column;
161
-
gap: 1rem;
162
-
text-align: center;
163
-
}
164
-
}
165
-
</style>
166
-
</head>
167
-
<body>
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>
173
-
</div>
174
-
</div>
158
+
.header {
159
+
flex-direction: column;
160
+
gap: 1rem;
161
+
text-align: center;
162
+
}
163
+
}
164
+
</style>
165
+
</head>
166
+
<body>
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>
173
+
</div>
174
+
</div>
175
175
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>
182
-
</select>
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>
187
-
</div>
188
-
</div>
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>
182
+
</select>
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>
187
+
</div>
188
+
</div>
189
189
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>
192
192
193
-
<div id="content" style="display: none">
194
-
<div
195
-
class="chart-container"
196
-
style="margin-bottom: 2rem; height: 450px"
197
-
>
198
-
<div class="chart-title">
199
-
Traffic Overview - All Routes Over Time
200
-
</div>
201
-
<canvas
202
-
id="trafficOverviewChart"
203
-
style="padding-bottom: 2rem"
204
-
></canvas>
205
-
</div>
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>
196
+
<canvas
197
+
id="trafficOverviewChart"
198
+
style="padding-bottom: 2rem"
199
+
></canvas>
200
+
</div>
206
201
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>
211
-
</div>
212
-
<div class="stat-card">
213
-
<div class="stat-number" id="avgResponseTime">-</div>
214
-
<div class="stat-label">Avg Response Time (ms)</div>
215
-
</div>
216
-
<div class="stat-card">
217
-
<div class="stat-number" id="p95ResponseTime">-</div>
218
-
<div class="stat-label">P95 Response Time (ms)</div>
219
-
</div>
220
-
<div class="stat-card">
221
-
<div class="stat-number" id="uniqueEndpoints">-</div>
222
-
<div class="stat-label">Unique Endpoints</div>
223
-
</div>
224
-
<div class="stat-card">
225
-
<div class="stat-number" id="errorRate">-</div>
226
-
<div class="stat-label">Error Rate (%)</div>
227
-
</div>
228
-
<div class="stat-card">
229
-
<div class="stat-number" id="fastRequests">-</div>
230
-
<div class="stat-label">Fast Requests (<100ms)</div>
231
-
</div>
232
-
<div class="stat-card">
233
-
<div class="stat-number" id="uptime">-</div>
234
-
<div class="stat-label">Uptime (%)</div>
235
-
</div>
236
-
<div class="stat-card">
237
-
<div class="stat-number" id="throughput">-</div>
238
-
<div class="stat-label">Throughput (req/hr)</div>
239
-
</div>
240
-
<div class="stat-card">
241
-
<div class="stat-number" id="apdex">-</div>
242
-
<div class="stat-label">APDEX Score</div>
243
-
</div>
244
-
<div class="stat-card">
245
-
<div class="stat-number" id="cacheHitRate">-</div>
246
-
<div class="stat-label">Cache Hit Rate (%)</div>
247
-
</div>
248
-
</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>
206
+
</div>
207
+
<div class="stat-card">
208
+
<div class="stat-number" id="avgResponseTime">-</div>
209
+
<div class="stat-label">Avg Response Time (ms)</div>
210
+
</div>
211
+
<div class="stat-card">
212
+
<div class="stat-number" id="p95ResponseTime">-</div>
213
+
<div class="stat-label">P95 Response Time (ms)</div>
214
+
</div>
215
+
<div class="stat-card">
216
+
<div class="stat-number" id="uniqueEndpoints">-</div>
217
+
<div class="stat-label">Unique Endpoints</div>
218
+
</div>
219
+
<div class="stat-card">
220
+
<div class="stat-number" id="errorRate">-</div>
221
+
<div class="stat-label">Error Rate (%)</div>
222
+
</div>
223
+
<div class="stat-card">
224
+
<div class="stat-number" id="fastRequests">-</div>
225
+
<div class="stat-label">Fast Requests (<100ms)</div>
226
+
</div>
227
+
<div class="stat-card">
228
+
<div class="stat-number" id="uptime">-</div>
229
+
<div class="stat-label">Uptime (%)</div>
230
+
</div>
231
+
<div class="stat-card">
232
+
<div class="stat-number" id="throughput">-</div>
233
+
<div class="stat-label">Throughput (req/hr)</div>
234
+
</div>
235
+
<div class="stat-card">
236
+
<div class="stat-number" id="apdex">-</div>
237
+
<div class="stat-label">APDEX Score</div>
238
+
</div>
239
+
<div class="stat-card">
240
+
<div class="stat-number" id="cacheHitRate">-</div>
241
+
<div class="stat-label">Cache Hit Rate (%)</div>
242
+
</div>
243
+
</div>
249
244
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>
254
-
</div>
255
-
<div class="stat-card">
256
-
<div class="stat-number" id="peakHourRequests">-</div>
257
-
<div class="stat-label">Peak Hour Requests</div>
258
-
</div>
259
-
<div class="stat-card">
260
-
<div class="stat-number" id="peakDay">-</div>
261
-
<div class="stat-label">Peak Day</div>
262
-
</div>
263
-
<div class="stat-card">
264
-
<div class="stat-number" id="peakDayRequests">-</div>
265
-
<div class="stat-label">Peak Day Requests</div>
266
-
</div>
267
-
<div class="stat-card">
268
-
<div class="stat-number" id="dashboardRequests">-</div>
269
-
<div class="stat-label">Dashboard Requests</div>
270
-
</div>
271
-
</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>
249
+
</div>
250
+
<div class="stat-card">
251
+
<div class="stat-number" id="peakHourRequests">-</div>
252
+
<div class="stat-label">Peak Hour Requests</div>
253
+
</div>
254
+
<div class="stat-card">
255
+
<div class="stat-number" id="peakDay">-</div>
256
+
<div class="stat-label">Peak Day</div>
257
+
</div>
258
+
<div class="stat-card">
259
+
<div class="stat-number" id="peakDayRequests">-</div>
260
+
<div class="stat-label">Peak Day Requests</div>
261
+
</div>
262
+
<div class="stat-card">
263
+
<div class="stat-number" id="dashboardRequests">-</div>
264
+
<div class="stat-label">Dashboard Requests</div>
265
+
</div>
266
+
</div>
272
267
273
-
<div class="charts-grid">
274
-
<div class="chart-container">
275
-
<div class="chart-title">Requests Over Time</div>
276
-
<canvas id="timeChart"></canvas>
277
-
</div>
268
+
<div class="charts-grid">
269
+
<div class="chart-container">
270
+
<div class="chart-title">Requests Over Time</div>
271
+
<canvas id="timeChart"></canvas>
272
+
</div>
278
273
279
-
<div class="chart-container">
280
-
<div class="chart-title">
281
-
Latency Over Time (Hourly)
282
-
</div>
283
-
<canvas id="latencyTimeChart"></canvas>
284
-
</div>
274
+
<div class="chart-container">
275
+
<div class="chart-title">Latency Over Time (Hourly)</div>
276
+
<canvas id="latencyTimeChart"></canvas>
277
+
</div>
285
278
286
-
<div class="chart-container">
287
-
<div class="chart-title">
288
-
Response Time Distribution
289
-
</div>
290
-
<canvas id="latencyDistributionChart"></canvas>
291
-
</div>
279
+
<div class="chart-container">
280
+
<div class="chart-title">Response Time Distribution</div>
281
+
<canvas id="latencyDistributionChart"></canvas>
282
+
</div>
292
283
293
-
<div class="chart-container">
294
-
<div class="chart-title">Latency Percentiles</div>
295
-
<canvas id="percentilesChart"></canvas>
296
-
</div>
284
+
<div class="chart-container">
285
+
<div class="chart-title">Latency Percentiles</div>
286
+
<canvas id="percentilesChart"></canvas>
287
+
</div>
297
288
298
-
<div class="chart-container">
299
-
<div class="chart-title">Top Endpoints</div>
300
-
<canvas id="endpointChart"></canvas>
301
-
</div>
289
+
<div class="chart-container">
290
+
<div class="chart-title">Top Endpoints</div>
291
+
<canvas id="endpointChart"></canvas>
292
+
</div>
302
293
303
-
<div class="chart-container">
304
-
<div class="chart-title">Slowest Endpoints</div>
305
-
<canvas id="slowestEndpointsChart"></canvas>
306
-
</div>
294
+
<div class="chart-container">
295
+
<div class="chart-title">Slowest Endpoints</div>
296
+
<canvas id="slowestEndpointsChart"></canvas>
297
+
</div>
307
298
308
-
<div class="chart-container">
309
-
<div class="chart-title">Status Codes</div>
310
-
<canvas id="statusChart"></canvas>
311
-
</div>
299
+
<div class="chart-container">
300
+
<div class="chart-title">Status Codes</div>
301
+
<canvas id="statusChart"></canvas>
302
+
</div>
312
303
313
-
<div class="chart-container">
314
-
<div class="chart-title">Top User Agents</div>
315
-
<canvas id="userAgentChart"></canvas>
316
-
</div>
317
-
</div>
318
-
</div>
304
+
<div class="chart-container">
305
+
<div class="chart-title">Top User Agents</div>
306
+
<canvas id="userAgentChart"></canvas>
307
+
</div>
319
308
</div>
309
+
</div>
310
+
</div>
320
311
321
-
<script>
322
-
let charts = {};
323
-
let autoRefreshInterval;
312
+
<script>
313
+
let charts = {};
314
+
let autoRefreshInterval;
324
315
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");
330
321
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";
334
325
335
-
try {
336
-
const response = await fetch(`/stats?days=${days}`);
337
-
if (!response.ok)
338
-
throw new Error(`HTTP ${response.status}`);
326
+
try {
327
+
const response = await fetch(`/stats?days=${days}`);
328
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
339
329
340
-
const data = await response.json();
341
-
updateDashboard(data);
330
+
const data = await response.json();
331
+
updateDashboard(data);
342
332
343
-
loading.style.display = "none";
344
-
content.style.display = "block";
345
-
} catch (err) {
346
-
loading.style.display = "none";
347
-
error.style.display = "block";
348
-
error.textContent = `Failed to load data: ${err.message}`;
349
-
}
350
-
}
333
+
loading.style.display = "none";
334
+
content.style.display = "block";
335
+
} catch (err) {
336
+
loading.style.display = "none";
337
+
error.style.display = "block";
338
+
error.textContent = `Failed to load data: ${err.message}`;
339
+
}
340
+
}
351
341
352
-
function updateDashboard(data) {
353
-
// Main metrics
354
-
document.getElementById("totalRequests").textContent =
355
-
data.totalRequests.toLocaleString();
356
-
document.getElementById("avgResponseTime").textContent =
357
-
data.averageResponseTime
358
-
? Math.round(data.averageResponseTime)
359
-
: "N/A";
360
-
document.getElementById("p95ResponseTime").textContent = data
361
-
.latencyAnalytics.percentiles.p95
362
-
? Math.round(data.latencyAnalytics.percentiles.p95)
363
-
: "N/A";
364
-
document.getElementById("uniqueEndpoints").textContent =
365
-
data.requestsByEndpoint.length;
342
+
function updateDashboard(data) {
343
+
// Main metrics
344
+
document.getElementById("totalRequests").textContent =
345
+
data.totalRequests.toLocaleString();
346
+
document.getElementById("avgResponseTime").textContent =
347
+
data.averageResponseTime
348
+
? Math.round(data.averageResponseTime)
349
+
: "N/A";
350
+
document.getElementById("p95ResponseTime").textContent = data
351
+
.latencyAnalytics.percentiles.p95
352
+
? Math.round(data.latencyAnalytics.percentiles.p95)
353
+
: "N/A";
354
+
document.getElementById("uniqueEndpoints").textContent =
355
+
data.requestsByEndpoint.length;
366
356
367
-
const errorRequests = data.requestsByStatus
368
-
.filter((s) => s.status >= 400)
369
-
.reduce((sum, s) => sum + s.count, 0);
370
-
const errorRate =
371
-
data.totalRequests > 0
372
-
? ((errorRequests / data.totalRequests) * 100).toFixed(
373
-
1,
374
-
)
375
-
: "0.0";
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);
360
+
const errorRate =
361
+
data.totalRequests > 0
362
+
? ((errorRequests / data.totalRequests) * 100).toFixed(1)
363
+
: "0.0";
364
+
document.getElementById("errorRate").textContent = errorRate;
377
365
378
-
// Calculate fast requests percentage
379
-
const fastRequestsData = data.latencyAnalytics.distribution
380
-
.filter(
381
-
(d) => d.range === "0-50ms" || d.range === "50-100ms",
382
-
)
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) + "%";
386
372
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,
392
-
);
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,
378
+
);
379
+
document.getElementById("apdex").textContent =
380
+
data.performanceMetrics.apdex.toFixed(2);
381
+
document.getElementById("cacheHitRate").textContent =
382
+
data.performanceMetrics.cachehitRate.toFixed(1);
397
383
398
-
// Peak traffic
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();
384
+
// Peak traffic
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();
407
393
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();
411
397
412
-
// Determine if we're showing hourly or daily data
413
-
const days = parseInt(
414
-
document.getElementById("daysSelect").value,
415
-
);
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;
417
401
418
-
updateTrafficOverviewChart(data.trafficOverview, days);
419
-
updateTimeChart(data.requestsByDay, isHourly);
420
-
updateLatencyTimeChart(
421
-
data.latencyAnalytics.latencyOverTime,
422
-
isHourly,
423
-
);
424
-
updateLatencyDistributionChart(
425
-
data.latencyAnalytics.distribution,
426
-
);
427
-
updatePercentilesChart(data.latencyAnalytics.percentiles);
428
-
updateEndpointChart(data.requestsByEndpoint.slice(0, 10));
429
-
updateSlowestEndpointsChart(
430
-
data.latencyAnalytics.slowestEndpoints,
431
-
);
432
-
updateStatusChart(data.requestsByStatus);
433
-
updateUserAgentChart(data.topUserAgents.slice(0, 5));
434
-
}
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));
411
+
}
435
412
436
-
function updateTrafficOverviewChart(data, days) {
437
-
const ctx = document
438
-
.getElementById("trafficOverviewChart")
439
-
.getContext("2d");
413
+
function updateTrafficOverviewChart(data, days) {
414
+
const ctx = document
415
+
.getElementById("trafficOverviewChart")
416
+
.getContext("2d");
440
417
441
-
if (charts.trafficOverview) charts.trafficOverview.destroy();
418
+
if (charts.trafficOverview) charts.trafficOverview.destroy();
442
419
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";
448
-
if (days === 1) {
449
-
titleText += " (Hourly)";
450
-
} else if (days <= 7) {
451
-
titleText += " (4-Hour Intervals)";
452
-
} else {
453
-
titleText += " (Daily)";
454
-
}
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";
425
+
if (days === 1) {
426
+
titleText += " (Hourly)";
427
+
} else if (days <= 7) {
428
+
titleText += " (4-Hour Intervals)";
429
+
} else {
430
+
titleText += " (Daily)";
431
+
}
432
+
chartTitle.textContent = titleText;
456
433
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),
462
-
);
463
-
});
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),
439
+
);
440
+
});
464
441
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",
476
-
};
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",
453
+
};
477
454
478
-
// Create datasets for each route
479
-
const datasets = Array.from(allRoutes).map((route) => {
480
-
const color = routeColors[route] || "#95a5a6";
481
-
return {
482
-
label: route,
483
-
data: data.map(
484
-
(timePoint) => timePoint.routes[route] || 0,
485
-
),
486
-
borderColor: color,
487
-
backgroundColor: color + "20", // Add transparency
488
-
tension: 0.4,
489
-
fill: false,
490
-
pointRadius: 2,
491
-
pointHoverRadius: 4,
492
-
};
493
-
});
455
+
// Create datasets for each route
456
+
const datasets = Array.from(allRoutes).map((route) => {
457
+
const color = routeColors[route] || "#95a5a6";
458
+
return {
459
+
label: route,
460
+
data: data.map((timePoint) => timePoint.routes[route] || 0),
461
+
borderColor: color,
462
+
backgroundColor: color + "20", // Add transparency
463
+
tension: 0.4,
464
+
fill: false,
465
+
pointRadius: 2,
466
+
pointHoverRadius: 4,
467
+
};
468
+
});
494
469
495
-
// Format labels based on time granularity
496
-
const labels = data.map((timePoint) => {
497
-
if (days === 1) {
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}`;
506
-
} else {
507
-
// Show full date for longer periods
508
-
return timePoint.time;
509
-
}
510
-
});
470
+
// Format labels based on time granularity
471
+
const labels = data.map((timePoint) => {
472
+
if (days === 1) {
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}`;
481
+
} else {
482
+
// Show full date for longer periods
483
+
return timePoint.time;
484
+
}
485
+
});
511
486
512
-
charts.trafficOverview = new Chart(ctx, {
513
-
type: "line",
514
-
data: {
515
-
labels: labels,
516
-
datasets: datasets,
517
-
},
518
-
options: {
519
-
responsive: true,
520
-
maintainAspectRatio: false,
521
-
interaction: {
522
-
mode: "index",
523
-
intersect: false,
524
-
},
525
-
plugins: {
526
-
legend: {
527
-
position: "top",
528
-
labels: {
529
-
usePointStyle: true,
530
-
padding: 15,
531
-
font: {
532
-
size: 11,
533
-
},
534
-
},
535
-
},
536
-
tooltip: {
537
-
mode: "index",
538
-
intersect: false,
539
-
callbacks: {
540
-
afterLabel: function (context) {
541
-
const timePoint =
542
-
data[context.dataIndex];
543
-
return `Total: ${timePoint.total} requests`;
544
-
},
545
-
},
546
-
},
547
-
},
548
-
scales: {
549
-
x: {
550
-
display: true,
551
-
title: {
552
-
display: true,
553
-
text:
554
-
days === 1
555
-
? "Hour"
556
-
: days <= 7
557
-
? "Day & Hour"
558
-
: "Date",
559
-
},
560
-
ticks: {
561
-
maxTicksLimit: 20,
562
-
},
563
-
},
564
-
y: {
565
-
display: true,
566
-
title: {
567
-
display: true,
568
-
text: "Requests",
569
-
},
570
-
beginAtZero: true,
571
-
},
572
-
},
573
-
elements: {
574
-
line: {
575
-
tension: 0.4,
576
-
},
577
-
},
578
-
},
579
-
});
580
-
}
487
+
charts.trafficOverview = new Chart(ctx, {
488
+
type: "line",
489
+
data: {
490
+
labels: labels,
491
+
datasets: datasets,
492
+
},
493
+
options: {
494
+
responsive: true,
495
+
maintainAspectRatio: false,
496
+
interaction: {
497
+
mode: "index",
498
+
intersect: false,
499
+
},
500
+
plugins: {
501
+
legend: {
502
+
position: "top",
503
+
labels: {
504
+
usePointStyle: true,
505
+
padding: 15,
506
+
font: {
507
+
size: 11,
508
+
},
509
+
},
510
+
},
511
+
tooltip: {
512
+
mode: "index",
513
+
intersect: false,
514
+
callbacks: {
515
+
afterLabel: function (context) {
516
+
const timePoint = data[context.dataIndex];
517
+
return `Total: ${timePoint.total} requests`;
518
+
},
519
+
},
520
+
},
521
+
},
522
+
scales: {
523
+
x: {
524
+
display: true,
525
+
title: {
526
+
display: true,
527
+
text: days === 1 ? "Hour" : days <= 7 ? "Day & Hour" : "Date",
528
+
},
529
+
ticks: {
530
+
maxTicksLimit: 20,
531
+
},
532
+
},
533
+
y: {
534
+
display: true,
535
+
title: {
536
+
display: true,
537
+
text: "Requests",
538
+
},
539
+
beginAtZero: true,
540
+
},
541
+
},
542
+
elements: {
543
+
line: {
544
+
tension: 0.4,
545
+
},
546
+
},
547
+
},
548
+
});
549
+
}
581
550
582
-
function updateTimeChart(data, isHourly) {
583
-
const ctx = document
584
-
.getElementById("timeChart")
585
-
.getContext("2d");
551
+
function updateTimeChart(data, isHourly) {
552
+
const ctx = document.getElementById("timeChart").getContext("2d");
586
553
587
-
if (charts.time) charts.time.destroy();
554
+
if (charts.time) charts.time.destroy();
588
555
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)";
596
563
597
-
charts.time = new Chart(ctx, {
598
-
type: "line",
599
-
data: {
600
-
labels: data.map((d) =>
601
-
isHourly ? d.date.split(" ")[1] : d.date,
602
-
),
603
-
datasets: [
604
-
{
605
-
label: "Requests",
606
-
data: data.map((d) => d.count),
607
-
borderColor: "#3498db",
608
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
609
-
tension: 0.4,
610
-
fill: true,
611
-
},
612
-
],
613
-
},
614
-
options: {
615
-
responsive: true,
616
-
scales: {
617
-
y: {
618
-
beginAtZero: true,
619
-
},
620
-
},
621
-
},
622
-
});
623
-
}
564
+
charts.time = new Chart(ctx, {
565
+
type: "line",
566
+
data: {
567
+
labels: data.map((d) => (isHourly ? d.date.split(" ")[1] : d.date)),
568
+
datasets: [
569
+
{
570
+
label: "Requests",
571
+
data: data.map((d) => d.count),
572
+
borderColor: "#3498db",
573
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
574
+
tension: 0.4,
575
+
fill: true,
576
+
},
577
+
],
578
+
},
579
+
options: {
580
+
responsive: true,
581
+
scales: {
582
+
y: {
583
+
beginAtZero: true,
584
+
},
585
+
},
586
+
},
587
+
});
588
+
}
624
589
625
-
function updateEndpointChart(data) {
626
-
const ctx = document
627
-
.getElementById("endpointChart")
628
-
.getContext("2d");
590
+
function updateEndpointChart(data) {
591
+
const ctx = document.getElementById("endpointChart").getContext("2d");
629
592
630
-
if (charts.endpoint) charts.endpoint.destroy();
593
+
if (charts.endpoint) charts.endpoint.destroy();
631
594
632
-
charts.endpoint = new Chart(ctx, {
633
-
type: "bar",
634
-
data: {
635
-
labels: data.map((d) => d.endpoint),
636
-
datasets: [
637
-
{
638
-
label: "Requests",
639
-
data: data.map((d) => d.count),
640
-
backgroundColor: "#2ecc71",
641
-
},
642
-
],
643
-
},
644
-
options: {
645
-
responsive: true,
646
-
indexAxis: "y",
647
-
scales: {
648
-
x: {
649
-
beginAtZero: true,
650
-
},
651
-
},
652
-
},
653
-
});
654
-
}
595
+
charts.endpoint = new Chart(ctx, {
596
+
type: "bar",
597
+
data: {
598
+
labels: data.map((d) => d.endpoint),
599
+
datasets: [
600
+
{
601
+
label: "Requests",
602
+
data: data.map((d) => d.count),
603
+
backgroundColor: "#2ecc71",
604
+
},
605
+
],
606
+
},
607
+
options: {
608
+
responsive: true,
609
+
indexAxis: "y",
610
+
scales: {
611
+
x: {
612
+
beginAtZero: true,
613
+
},
614
+
},
615
+
},
616
+
});
617
+
}
655
618
656
-
function updateStatusChart(data) {
657
-
const ctx = document
658
-
.getElementById("statusChart")
659
-
.getContext("2d");
619
+
function updateStatusChart(data) {
620
+
const ctx = document.getElementById("statusChart").getContext("2d");
660
621
661
-
if (charts.status) charts.status.destroy();
622
+
if (charts.status) charts.status.destroy();
662
623
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";
667
-
return "#9b59b6";
668
-
});
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";
628
+
return "#9b59b6";
629
+
});
669
630
670
-
charts.status = new Chart(ctx, {
671
-
type: "doughnut",
672
-
data: {
673
-
labels: data.map((d) => `${d.status}`),
674
-
datasets: [
675
-
{
676
-
data: data.map((d) => d.count),
677
-
backgroundColor: colors,
678
-
},
679
-
],
680
-
},
681
-
options: {
682
-
responsive: true,
683
-
},
684
-
});
685
-
}
631
+
charts.status = new Chart(ctx, {
632
+
type: "doughnut",
633
+
data: {
634
+
labels: data.map((d) => `${d.status}`),
635
+
datasets: [
636
+
{
637
+
data: data.map((d) => d.count),
638
+
backgroundColor: colors,
639
+
},
640
+
],
641
+
},
642
+
options: {
643
+
responsive: true,
644
+
},
645
+
});
646
+
}
686
647
687
-
function updateUserAgentChart(data) {
688
-
const ctx = document
689
-
.getElementById("userAgentChart")
690
-
.getContext("2d");
648
+
function updateUserAgentChart(data) {
649
+
const ctx = document.getElementById("userAgentChart").getContext("2d");
691
650
692
-
if (charts.userAgent) charts.userAgent.destroy();
651
+
if (charts.userAgent) charts.userAgent.destroy();
693
652
694
-
charts.userAgent = new Chart(ctx, {
695
-
type: "pie",
696
-
data: {
697
-
labels: data.map((d) => d.userAgent),
698
-
datasets: [
699
-
{
700
-
data: data.map((d) => d.count),
701
-
backgroundColor: [
702
-
"#3498db",
703
-
"#e74c3c",
704
-
"#2ecc71",
705
-
"#f39c12",
706
-
"#9b59b6",
707
-
"#34495e",
708
-
"#16a085",
709
-
"#8e44ad",
710
-
"#d35400",
711
-
"#7f8c8d",
712
-
],
713
-
},
714
-
],
715
-
},
716
-
options: {
717
-
responsive: true,
718
-
},
719
-
});
720
-
}
653
+
charts.userAgent = new Chart(ctx, {
654
+
type: "pie",
655
+
data: {
656
+
labels: data.map((d) => d.userAgent),
657
+
datasets: [
658
+
{
659
+
data: data.map((d) => d.count),
660
+
backgroundColor: [
661
+
"#3498db",
662
+
"#e74c3c",
663
+
"#2ecc71",
664
+
"#f39c12",
665
+
"#9b59b6",
666
+
"#34495e",
667
+
"#16a085",
668
+
"#8e44ad",
669
+
"#d35400",
670
+
"#7f8c8d",
671
+
],
672
+
},
673
+
],
674
+
},
675
+
options: {
676
+
responsive: true,
677
+
},
678
+
});
679
+
}
721
680
722
-
function updateLatencyTimeChart(data, isHourly) {
723
-
const ctx = document
724
-
.getElementById("latencyTimeChart")
725
-
.getContext("2d");
681
+
function updateLatencyTimeChart(data, isHourly) {
682
+
const ctx = document
683
+
.getElementById("latencyTimeChart")
684
+
.getContext("2d");
726
685
727
-
if (charts.latencyTime) charts.latencyTime.destroy();
686
+
if (charts.latencyTime) charts.latencyTime.destroy();
728
687
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)";
736
695
737
-
charts.latencyTime = new Chart(ctx, {
738
-
type: "line",
739
-
data: {
740
-
labels: data.map((d) =>
741
-
isHourly ? d.time.split(" ")[1] : d.time,
742
-
),
743
-
datasets: [
744
-
{
745
-
label: "Average Response Time",
746
-
data: data.map((d) => d.averageResponseTime),
747
-
borderColor: "#3498db",
748
-
backgroundColor: "rgba(52, 152, 219, 0.1)",
749
-
tension: 0.4,
750
-
yAxisID: "y",
751
-
},
752
-
{
753
-
label: "P95 Response Time",
754
-
data: data.map((d) => d.p95),
755
-
borderColor: "#e74c3c",
756
-
backgroundColor: "rgba(231, 76, 60, 0.1)",
757
-
tension: 0.4,
758
-
yAxisID: "y",
759
-
},
760
-
],
761
-
},
762
-
options: {
763
-
responsive: true,
764
-
scales: {
765
-
y: {
766
-
beginAtZero: true,
767
-
title: {
768
-
display: true,
769
-
text: "Response Time (ms)",
770
-
},
771
-
},
772
-
},
773
-
},
774
-
});
775
-
}
696
+
charts.latencyTime = new Chart(ctx, {
697
+
type: "line",
698
+
data: {
699
+
labels: data.map((d) => (isHourly ? d.time.split(" ")[1] : d.time)),
700
+
datasets: [
701
+
{
702
+
label: "Average Response Time",
703
+
data: data.map((d) => d.averageResponseTime),
704
+
borderColor: "#3498db",
705
+
backgroundColor: "rgba(52, 152, 219, 0.1)",
706
+
tension: 0.4,
707
+
yAxisID: "y",
708
+
},
709
+
{
710
+
label: "P95 Response Time",
711
+
data: data.map((d) => d.p95),
712
+
borderColor: "#e74c3c",
713
+
backgroundColor: "rgba(231, 76, 60, 0.1)",
714
+
tension: 0.4,
715
+
yAxisID: "y",
716
+
},
717
+
],
718
+
},
719
+
options: {
720
+
responsive: true,
721
+
scales: {
722
+
y: {
723
+
beginAtZero: true,
724
+
title: {
725
+
display: true,
726
+
text: "Response Time (ms)",
727
+
},
728
+
},
729
+
},
730
+
},
731
+
});
732
+
}
776
733
777
-
function updateLatencyDistributionChart(data) {
778
-
const ctx = document
779
-
.getElementById("latencyDistributionChart")
780
-
.getContext("2d");
734
+
function updateLatencyDistributionChart(data) {
735
+
const ctx = document
736
+
.getElementById("latencyDistributionChart")
737
+
.getContext("2d");
781
738
782
-
if (charts.latencyDistribution)
783
-
charts.latencyDistribution.destroy();
739
+
if (charts.latencyDistribution) charts.latencyDistribution.destroy();
784
740
785
-
charts.latencyDistribution = new Chart(ctx, {
786
-
type: "bar",
787
-
data: {
788
-
labels: data.map((d) => d.range),
789
-
datasets: [
790
-
{
791
-
label: "Requests",
792
-
data: data.map((d) => d.count),
793
-
backgroundColor: "#2ecc71",
794
-
},
795
-
],
796
-
},
797
-
options: {
798
-
responsive: true,
799
-
scales: {
800
-
y: {
801
-
beginAtZero: true,
802
-
},
803
-
},
804
-
},
805
-
});
806
-
}
741
+
charts.latencyDistribution = new Chart(ctx, {
742
+
type: "bar",
743
+
data: {
744
+
labels: data.map((d) => d.range),
745
+
datasets: [
746
+
{
747
+
label: "Requests",
748
+
data: data.map((d) => d.count),
749
+
backgroundColor: "#2ecc71",
750
+
},
751
+
],
752
+
},
753
+
options: {
754
+
responsive: true,
755
+
scales: {
756
+
y: {
757
+
beginAtZero: true,
758
+
},
759
+
},
760
+
},
761
+
});
762
+
}
807
763
808
-
function updatePercentilesChart(percentiles) {
809
-
const ctx = document
810
-
.getElementById("percentilesChart")
811
-
.getContext("2d");
764
+
function updatePercentilesChart(percentiles) {
765
+
const ctx = document
766
+
.getElementById("percentilesChart")
767
+
.getContext("2d");
812
768
813
-
if (charts.percentiles) charts.percentiles.destroy();
769
+
if (charts.percentiles) charts.percentiles.destroy();
814
770
815
-
const data = [
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);
771
+
const data = [
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);
822
778
823
-
charts.percentiles = new Chart(ctx, {
824
-
type: "bar",
825
-
data: {
826
-
labels: data.map((d) => d.label),
827
-
datasets: [
828
-
{
829
-
label: "Response Time (ms)",
830
-
data: data.map((d) => d.value),
831
-
backgroundColor: [
832
-
"#3498db",
833
-
"#2ecc71",
834
-
"#f39c12",
835
-
"#e74c3c",
836
-
"#9b59b6",
837
-
],
838
-
},
839
-
],
840
-
},
841
-
options: {
842
-
responsive: true,
843
-
scales: {
844
-
y: {
845
-
beginAtZero: true,
846
-
},
847
-
},
848
-
},
849
-
});
850
-
}
779
+
charts.percentiles = new Chart(ctx, {
780
+
type: "bar",
781
+
data: {
782
+
labels: data.map((d) => d.label),
783
+
datasets: [
784
+
{
785
+
label: "Response Time (ms)",
786
+
data: data.map((d) => d.value),
787
+
backgroundColor: [
788
+
"#3498db",
789
+
"#2ecc71",
790
+
"#f39c12",
791
+
"#e74c3c",
792
+
"#9b59b6",
793
+
],
794
+
},
795
+
],
796
+
},
797
+
options: {
798
+
responsive: true,
799
+
scales: {
800
+
y: {
801
+
beginAtZero: true,
802
+
},
803
+
},
804
+
},
805
+
});
806
+
}
851
807
852
-
function updateSlowestEndpointsChart(data) {
853
-
const ctx = document
854
-
.getElementById("slowestEndpointsChart")
855
-
.getContext("2d");
808
+
function updateSlowestEndpointsChart(data) {
809
+
const ctx = document
810
+
.getElementById("slowestEndpointsChart")
811
+
.getContext("2d");
856
812
857
-
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
813
+
if (charts.slowestEndpoints) charts.slowestEndpoints.destroy();
858
814
859
-
charts.slowestEndpoints = new Chart(ctx, {
860
-
type: "bar",
861
-
data: {
862
-
labels: data.map((d) => d.endpoint),
863
-
datasets: [
864
-
{
865
-
label: "Avg Response Time (ms)",
866
-
data: data.map((d) => d.averageResponseTime),
867
-
backgroundColor: "#e74c3c",
868
-
},
869
-
],
870
-
},
871
-
options: {
872
-
responsive: true,
873
-
indexAxis: "y",
874
-
scales: {
875
-
x: {
876
-
beginAtZero: true,
877
-
},
878
-
},
879
-
},
880
-
});
881
-
}
815
+
charts.slowestEndpoints = new Chart(ctx, {
816
+
type: "bar",
817
+
data: {
818
+
labels: data.map((d) => d.endpoint),
819
+
datasets: [
820
+
{
821
+
label: "Avg Response Time (ms)",
822
+
data: data.map((d) => d.averageResponseTime),
823
+
backgroundColor: "#e74c3c",
824
+
},
825
+
],
826
+
},
827
+
options: {
828
+
responsive: true,
829
+
indexAxis: "y",
830
+
scales: {
831
+
x: {
832
+
beginAtZero: true,
833
+
},
834
+
},
835
+
},
836
+
});
837
+
}
882
838
883
-
document
884
-
.getElementById("autoRefresh")
885
-
.addEventListener("change", function () {
886
-
if (this.checked) {
887
-
autoRefreshInterval = setInterval(loadData, 30000);
888
-
} else {
889
-
clearInterval(autoRefreshInterval);
890
-
}
891
-
});
839
+
document
840
+
.getElementById("autoRefresh")
841
+
.addEventListener("change", function () {
842
+
if (this.checked) {
843
+
autoRefreshInterval = setInterval(loadData, 30000);
844
+
} else {
845
+
clearInterval(autoRefreshInterval);
846
+
}
847
+
});
892
848
893
-
loadData();
894
-
document
895
-
.getElementById("daysSelect")
896
-
.addEventListener("change", loadData);
897
-
</script>
898
-
</body>
849
+
loadData();
850
+
document
851
+
.getElementById("daysSelect")
852
+
.addEventListener("change", loadData);
853
+
</script>
854
+
</body>
899
855
</html>
+1
-2
src/swagger.ts
+1
-2
src/swagger.ts
···
1
-
import { serve } from "bun";
2
1
import { version } from "../package.json";
3
2
4
3
// Define the Swagger specification
···
15
14
},
16
15
license: {
17
16
name: "AGPL 3.0",
18
-
url: "https://github.com/taciturnaxoltol/cachet/blob/master/LICENSE.md",
17
+
url: "https://github.com/taciturnaxolotl/cachet/blob/main/LICENSE.md",
19
18
},
20
19
},
21
20
tags: [