setu-trafficmonitor 2.0.0__py3-none-any.whl
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.
- setu_trafficmonitor-2.0.0.dist-info/LICENSE +21 -0
- setu_trafficmonitor-2.0.0.dist-info/METADATA +401 -0
- setu_trafficmonitor-2.0.0.dist-info/RECORD +42 -0
- setu_trafficmonitor-2.0.0.dist-info/WHEEL +5 -0
- setu_trafficmonitor-2.0.0.dist-info/top_level.txt +1 -0
- trafficmonitor/__init__.py +11 -0
- trafficmonitor/admin.py +217 -0
- trafficmonitor/analytics/__init__.py +0 -0
- trafficmonitor/analytics/enhanced_queries.py +286 -0
- trafficmonitor/analytics/serializers.py +238 -0
- trafficmonitor/analytics/tests.py +757 -0
- trafficmonitor/analytics/urls.py +18 -0
- trafficmonitor/analytics/views.py +694 -0
- trafficmonitor/apps.py +7 -0
- trafficmonitor/circuit_breaker.py +63 -0
- trafficmonitor/conf.py +154 -0
- trafficmonitor/dashboard_security.py +111 -0
- trafficmonitor/db_utils.py +37 -0
- trafficmonitor/exceptions.py +93 -0
- trafficmonitor/health.py +66 -0
- trafficmonitor/load_test.py +423 -0
- trafficmonitor/load_test_api.py +307 -0
- trafficmonitor/management/__init__.py +1 -0
- trafficmonitor/management/commands/__init__.py +1 -0
- trafficmonitor/management/commands/cleanup_request_logs.py +77 -0
- trafficmonitor/middleware.py +383 -0
- trafficmonitor/migrations/0001_initial.py +93 -0
- trafficmonitor/migrations/__init__.py +0 -0
- trafficmonitor/models.py +206 -0
- trafficmonitor/monitoring.py +104 -0
- trafficmonitor/permissions.py +64 -0
- trafficmonitor/security.py +180 -0
- trafficmonitor/settings_production.py +105 -0
- trafficmonitor/static/analytics/css/dashboard.css +99 -0
- trafficmonitor/static/analytics/js/dashboard-production.js +339 -0
- trafficmonitor/static/analytics/js/dashboard-v2.js +697 -0
- trafficmonitor/static/analytics/js/dashboard.js +693 -0
- trafficmonitor/tasks.py +137 -0
- trafficmonitor/templates/analytics/dashboard.html +500 -0
- trafficmonitor/tests.py +246 -0
- trafficmonitor/views.py +3 -0
- trafficmonitor/websocket_consumers.py +128 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Dashboard JavaScript
|
|
3
|
+
* Handles data fetching, chart rendering, and interactions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get CSRF token from cookie
|
|
8
|
+
*/
|
|
9
|
+
function getCookie(name) {
|
|
10
|
+
let cookieValue = null;
|
|
11
|
+
if (document.cookie && document.cookie !== '') {
|
|
12
|
+
const cookies = document.cookie.split(';');
|
|
13
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
14
|
+
const cookie = cookies[i].trim();
|
|
15
|
+
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
16
|
+
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return cookieValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Chart instances (global)
|
|
25
|
+
let requestsOverTimeChart = null;
|
|
26
|
+
let statusCodesChart = null;
|
|
27
|
+
let methodsChart = null;
|
|
28
|
+
let errorTrendChart = null;
|
|
29
|
+
let slowestEndpointsChart = null;
|
|
30
|
+
let hourlyHeatmapChart = null;
|
|
31
|
+
|
|
32
|
+
// Chart color schemes
|
|
33
|
+
const COLORS = {
|
|
34
|
+
primary: 'rgb(59, 130, 246)', // Blue
|
|
35
|
+
success: 'rgb(34, 197, 94)', // Green
|
|
36
|
+
warning: 'rgb(251, 191, 36)', // Yellow
|
|
37
|
+
danger: 'rgb(239, 68, 68)', // Red
|
|
38
|
+
info: 'rgb(139, 92, 246)', // Purple
|
|
39
|
+
secondary: 'rgb(107, 114, 128)', // Gray
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const METHOD_COLORS = {
|
|
43
|
+
'GET': COLORS.success,
|
|
44
|
+
'POST': COLORS.primary,
|
|
45
|
+
'PUT': COLORS.warning,
|
|
46
|
+
'PATCH': 'rgb(249, 115, 22)', // Orange
|
|
47
|
+
'DELETE': COLORS.danger,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const STATUS_COLORS = {
|
|
51
|
+
'2xx': COLORS.success,
|
|
52
|
+
'3xx': COLORS.info,
|
|
53
|
+
'4xx': COLORS.warning,
|
|
54
|
+
'5xx': COLORS.danger,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the dashboard
|
|
59
|
+
*/
|
|
60
|
+
function initializeDashboard() {
|
|
61
|
+
loadAnalyticsData();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Refresh dashboard with current filters
|
|
66
|
+
*/
|
|
67
|
+
function refreshDashboard() {
|
|
68
|
+
loadAnalyticsData();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Apply filters and reload dashboard
|
|
73
|
+
*/
|
|
74
|
+
function applyFilters() {
|
|
75
|
+
loadAnalyticsData();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get current filter parameters
|
|
80
|
+
*/
|
|
81
|
+
function getFilterParams() {
|
|
82
|
+
const params = new URLSearchParams();
|
|
83
|
+
|
|
84
|
+
const range = document.getElementById('rangeSelect').value;
|
|
85
|
+
params.append('range', range);
|
|
86
|
+
|
|
87
|
+
if (range === 'custom') {
|
|
88
|
+
const startDate = document.getElementById('startDate').value;
|
|
89
|
+
const endDate = document.getElementById('endDate').value;
|
|
90
|
+
if (startDate) params.append('start_date', startDate);
|
|
91
|
+
if (endDate) params.append('end_date', endDate);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const method = document.getElementById('methodFilter').value;
|
|
95
|
+
if (method) params.append('method', method);
|
|
96
|
+
|
|
97
|
+
const status = document.getElementById('statusFilter').value;
|
|
98
|
+
if (status) params.append('status', status);
|
|
99
|
+
|
|
100
|
+
const path = document.getElementById('pathFilter').value;
|
|
101
|
+
if (path) params.append('path', path);
|
|
102
|
+
|
|
103
|
+
return params;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Load analytics data from API
|
|
108
|
+
*/
|
|
109
|
+
async function loadAnalyticsData() {
|
|
110
|
+
try {
|
|
111
|
+
// Show loading indicator
|
|
112
|
+
document.getElementById('loadingIndicator').classList.remove('hidden');
|
|
113
|
+
document.getElementById('dashboardContent').classList.add('hidden');
|
|
114
|
+
|
|
115
|
+
// Fetch analytics data
|
|
116
|
+
const params = getFilterParams();
|
|
117
|
+
const csrftoken = getCookie('csrftoken');
|
|
118
|
+
const response = await fetch(`/api/analytics/overview/?${params.toString()}`, {
|
|
119
|
+
credentials: 'same-origin', // Include cookies for session auth
|
|
120
|
+
headers: {
|
|
121
|
+
'Accept': 'application/json',
|
|
122
|
+
'X-CSRFToken': csrftoken, // Include CSRF token for Django
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const data = await response.json();
|
|
131
|
+
|
|
132
|
+
// Update dashboard with data
|
|
133
|
+
updateStats(data);
|
|
134
|
+
updateCharts(data);
|
|
135
|
+
updateTables(data);
|
|
136
|
+
|
|
137
|
+
// Update last updated time
|
|
138
|
+
document.getElementById('lastUpdated').textContent = new Date().toLocaleString();
|
|
139
|
+
|
|
140
|
+
// Hide loading, show content
|
|
141
|
+
document.getElementById('loadingIndicator').classList.add('hidden');
|
|
142
|
+
document.getElementById('dashboardContent').classList.remove('hidden');
|
|
143
|
+
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Error loading analytics data:', error);
|
|
146
|
+
alert('Failed to load analytics data. Please try again.');
|
|
147
|
+
document.getElementById('loadingIndicator').classList.add('hidden');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Update stats cards
|
|
153
|
+
*/
|
|
154
|
+
function updateStats(data) {
|
|
155
|
+
// Total requests
|
|
156
|
+
document.getElementById('totalToday').textContent =
|
|
157
|
+
formatNumber(data.totals.today);
|
|
158
|
+
document.getElementById('totalLast7Days').textContent =
|
|
159
|
+
formatNumber(data.totals.last_7_days);
|
|
160
|
+
document.getElementById('totalLast30Days').textContent =
|
|
161
|
+
formatNumber(data.totals.last_30_days);
|
|
162
|
+
|
|
163
|
+
// Average response time
|
|
164
|
+
const avgTime = data.performance.avg_response_time;
|
|
165
|
+
document.getElementById('avgResponseTime').textContent =
|
|
166
|
+
avgTime ? `${avgTime.toFixed(1)} ms` : 'N/A';
|
|
167
|
+
|
|
168
|
+
// Status code summary
|
|
169
|
+
document.getElementById('statusSuccess').textContent =
|
|
170
|
+
formatNumber(data.status_summary.success);
|
|
171
|
+
document.getElementById('statusRedirect').textContent =
|
|
172
|
+
formatNumber(data.status_summary.redirect);
|
|
173
|
+
document.getElementById('statusClientError').textContent =
|
|
174
|
+
formatNumber(data.status_summary.client_error);
|
|
175
|
+
document.getElementById('statusServerError').textContent =
|
|
176
|
+
formatNumber(data.status_summary.server_error);
|
|
177
|
+
document.getElementById('statusTotal').textContent =
|
|
178
|
+
formatNumber(data.status_summary.total);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update all charts
|
|
183
|
+
*/
|
|
184
|
+
function updateCharts(data) {
|
|
185
|
+
renderRequestsOverTimeChart(data.requests_over_time);
|
|
186
|
+
renderStatusCodesChart(data.status_codes);
|
|
187
|
+
renderMethodsChart(data.methods);
|
|
188
|
+
renderErrorTrendChart(data.error_trend);
|
|
189
|
+
renderSlowestEndpointsChart(data.slowest_endpoints);
|
|
190
|
+
renderHourlyHeatmapChart(data.hourly_heatmap);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Update tables
|
|
195
|
+
*/
|
|
196
|
+
function updateTables(data) {
|
|
197
|
+
updateTopEndpointsTable(data.top_endpoints);
|
|
198
|
+
updateTopIPsTable(data.top_ips);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Render requests over time chart (Line chart)
|
|
203
|
+
*/
|
|
204
|
+
function renderRequestsOverTimeChart(data) {
|
|
205
|
+
const ctx = document.getElementById('requestsOverTimeChart');
|
|
206
|
+
|
|
207
|
+
// Destroy existing chart
|
|
208
|
+
if (requestsOverTimeChart) {
|
|
209
|
+
requestsOverTimeChart.destroy();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
requestsOverTimeChart = new Chart(ctx, {
|
|
213
|
+
type: 'line',
|
|
214
|
+
data: {
|
|
215
|
+
labels: data.map(item => new Date(item.period)),
|
|
216
|
+
datasets: [{
|
|
217
|
+
label: 'Requests',
|
|
218
|
+
data: data.map(item => item.count),
|
|
219
|
+
borderColor: COLORS.primary,
|
|
220
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
221
|
+
borderWidth: 2,
|
|
222
|
+
fill: true,
|
|
223
|
+
tension: 0.4,
|
|
224
|
+
}]
|
|
225
|
+
},
|
|
226
|
+
options: {
|
|
227
|
+
responsive: true,
|
|
228
|
+
maintainAspectRatio: false,
|
|
229
|
+
plugins: {
|
|
230
|
+
legend: {
|
|
231
|
+
display: false
|
|
232
|
+
},
|
|
233
|
+
tooltip: {
|
|
234
|
+
mode: 'index',
|
|
235
|
+
intersect: false,
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
scales: {
|
|
239
|
+
x: {
|
|
240
|
+
type: 'time',
|
|
241
|
+
time: {
|
|
242
|
+
unit: 'day',
|
|
243
|
+
displayFormats: {
|
|
244
|
+
day: 'MMM dd'
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
grid: {
|
|
248
|
+
display: false
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
y: {
|
|
252
|
+
beginAtZero: true,
|
|
253
|
+
ticks: {
|
|
254
|
+
precision: 0
|
|
255
|
+
},
|
|
256
|
+
grid: {
|
|
257
|
+
color: 'rgba(0, 0, 0, 0.05)'
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Render status codes chart (Bar chart)
|
|
267
|
+
*/
|
|
268
|
+
function renderStatusCodesChart(data) {
|
|
269
|
+
const ctx = document.getElementById('statusCodesChart');
|
|
270
|
+
|
|
271
|
+
if (statusCodesChart) {
|
|
272
|
+
statusCodesChart.destroy();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Sort by status code
|
|
276
|
+
data.sort((a, b) => a.status_code - b.status_code);
|
|
277
|
+
|
|
278
|
+
// Assign colors based on status code range
|
|
279
|
+
const colors = data.map(item => {
|
|
280
|
+
if (item.status_code < 300) return COLORS.success;
|
|
281
|
+
if (item.status_code < 400) return COLORS.info;
|
|
282
|
+
if (item.status_code < 500) return COLORS.warning;
|
|
283
|
+
return COLORS.danger;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
statusCodesChart = new Chart(ctx, {
|
|
287
|
+
type: 'bar',
|
|
288
|
+
data: {
|
|
289
|
+
labels: data.map(item => item.status_code.toString()),
|
|
290
|
+
datasets: [{
|
|
291
|
+
label: 'Requests',
|
|
292
|
+
data: data.map(item => item.count),
|
|
293
|
+
backgroundColor: colors,
|
|
294
|
+
borderWidth: 0,
|
|
295
|
+
}]
|
|
296
|
+
},
|
|
297
|
+
options: {
|
|
298
|
+
responsive: true,
|
|
299
|
+
maintainAspectRatio: false,
|
|
300
|
+
plugins: {
|
|
301
|
+
legend: {
|
|
302
|
+
display: false
|
|
303
|
+
},
|
|
304
|
+
tooltip: {
|
|
305
|
+
callbacks: {
|
|
306
|
+
label: function(context) {
|
|
307
|
+
return `Requests: ${formatNumber(context.parsed.y)}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
scales: {
|
|
313
|
+
y: {
|
|
314
|
+
beginAtZero: true,
|
|
315
|
+
ticks: {
|
|
316
|
+
precision: 0
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Render HTTP methods chart (Pie chart)
|
|
326
|
+
*/
|
|
327
|
+
function renderMethodsChart(data) {
|
|
328
|
+
const ctx = document.getElementById('methodsChart');
|
|
329
|
+
|
|
330
|
+
if (methodsChart) {
|
|
331
|
+
methodsChart.destroy();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const colors = data.map(item => METHOD_COLORS[item.method] || COLORS.secondary);
|
|
335
|
+
|
|
336
|
+
methodsChart = new Chart(ctx, {
|
|
337
|
+
type: 'doughnut',
|
|
338
|
+
data: {
|
|
339
|
+
labels: data.map(item => item.method),
|
|
340
|
+
datasets: [{
|
|
341
|
+
data: data.map(item => item.count),
|
|
342
|
+
backgroundColor: colors,
|
|
343
|
+
borderWidth: 2,
|
|
344
|
+
borderColor: '#fff',
|
|
345
|
+
}]
|
|
346
|
+
},
|
|
347
|
+
options: {
|
|
348
|
+
responsive: true,
|
|
349
|
+
maintainAspectRatio: false,
|
|
350
|
+
plugins: {
|
|
351
|
+
legend: {
|
|
352
|
+
position: 'bottom',
|
|
353
|
+
},
|
|
354
|
+
tooltip: {
|
|
355
|
+
callbacks: {
|
|
356
|
+
label: function(context) {
|
|
357
|
+
const label = context.label || '';
|
|
358
|
+
const value = formatNumber(context.parsed);
|
|
359
|
+
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
360
|
+
const percentage = ((context.parsed / total) * 100).toFixed(1);
|
|
361
|
+
return `${label}: ${value} (${percentage}%)`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Render error trend chart (Line chart)
|
|
372
|
+
*/
|
|
373
|
+
function renderErrorTrendChart(data) {
|
|
374
|
+
const ctx = document.getElementById('errorTrendChart');
|
|
375
|
+
|
|
376
|
+
if (errorTrendChart) {
|
|
377
|
+
errorTrendChart.destroy();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
errorTrendChart = new Chart(ctx, {
|
|
381
|
+
type: 'line',
|
|
382
|
+
data: {
|
|
383
|
+
labels: data.map(item => new Date(item.period)),
|
|
384
|
+
datasets: [
|
|
385
|
+
{
|
|
386
|
+
label: '4xx Client Errors',
|
|
387
|
+
data: data.map(item => item.client_errors),
|
|
388
|
+
borderColor: COLORS.warning,
|
|
389
|
+
backgroundColor: 'rgba(251, 191, 36, 0.1)',
|
|
390
|
+
borderWidth: 2,
|
|
391
|
+
tension: 0.4,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
label: '5xx Server Errors',
|
|
395
|
+
data: data.map(item => item.server_errors),
|
|
396
|
+
borderColor: COLORS.danger,
|
|
397
|
+
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
398
|
+
borderWidth: 2,
|
|
399
|
+
tension: 0.4,
|
|
400
|
+
}
|
|
401
|
+
]
|
|
402
|
+
},
|
|
403
|
+
options: {
|
|
404
|
+
responsive: true,
|
|
405
|
+
maintainAspectRatio: false,
|
|
406
|
+
plugins: {
|
|
407
|
+
legend: {
|
|
408
|
+
position: 'bottom',
|
|
409
|
+
},
|
|
410
|
+
tooltip: {
|
|
411
|
+
mode: 'index',
|
|
412
|
+
intersect: false,
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
scales: {
|
|
416
|
+
x: {
|
|
417
|
+
type: 'time',
|
|
418
|
+
time: {
|
|
419
|
+
unit: 'day',
|
|
420
|
+
displayFormats: {
|
|
421
|
+
day: 'MMM dd'
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
y: {
|
|
426
|
+
beginAtZero: true,
|
|
427
|
+
ticks: {
|
|
428
|
+
precision: 0
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Render slowest endpoints chart (Horizontal bar chart)
|
|
438
|
+
*/
|
|
439
|
+
function renderSlowestEndpointsChart(data) {
|
|
440
|
+
const ctx = document.getElementById('slowestEndpointsChart');
|
|
441
|
+
|
|
442
|
+
if (slowestEndpointsChart) {
|
|
443
|
+
slowestEndpointsChart.destroy();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Truncate long paths for display
|
|
447
|
+
const labels = data.map(item => {
|
|
448
|
+
const path = item.path;
|
|
449
|
+
return path.length > 50 ? path.substring(0, 47) + '...' : path;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
slowestEndpointsChart = new Chart(ctx, {
|
|
453
|
+
type: 'bar',
|
|
454
|
+
data: {
|
|
455
|
+
labels: labels,
|
|
456
|
+
datasets: [{
|
|
457
|
+
label: 'Avg Response Time (ms)',
|
|
458
|
+
data: data.map(item => item.avg_response_time),
|
|
459
|
+
backgroundColor: COLORS.orange,
|
|
460
|
+
borderWidth: 0,
|
|
461
|
+
}]
|
|
462
|
+
},
|
|
463
|
+
options: {
|
|
464
|
+
indexAxis: 'y',
|
|
465
|
+
responsive: true,
|
|
466
|
+
maintainAspectRatio: false,
|
|
467
|
+
plugins: {
|
|
468
|
+
legend: {
|
|
469
|
+
display: false
|
|
470
|
+
},
|
|
471
|
+
tooltip: {
|
|
472
|
+
callbacks: {
|
|
473
|
+
label: function(context) {
|
|
474
|
+
return `Avg: ${context.parsed.x.toFixed(2)} ms`;
|
|
475
|
+
},
|
|
476
|
+
afterLabel: function(context) {
|
|
477
|
+
const item = data[context.dataIndex];
|
|
478
|
+
return `Requests: ${formatNumber(item.count)}`;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
scales: {
|
|
484
|
+
x: {
|
|
485
|
+
beginAtZero: true,
|
|
486
|
+
title: {
|
|
487
|
+
display: true,
|
|
488
|
+
text: 'Response Time (ms)'
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Render hourly heatmap chart (Bar chart)
|
|
498
|
+
*/
|
|
499
|
+
function renderHourlyHeatmapChart(data) {
|
|
500
|
+
const ctx = document.getElementById('hourlyHeatmapChart');
|
|
501
|
+
|
|
502
|
+
if (hourlyHeatmapChart) {
|
|
503
|
+
hourlyHeatmapChart.destroy();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Create array for all 24 hours
|
|
507
|
+
const hourlyData = Array(24).fill(0);
|
|
508
|
+
data.forEach(item => {
|
|
509
|
+
hourlyData[item.hour] = item.count;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Generate gradient colors based on intensity
|
|
513
|
+
const maxCount = Math.max(...hourlyData);
|
|
514
|
+
const colors = hourlyData.map(count => {
|
|
515
|
+
const intensity = maxCount > 0 ? count / maxCount : 0;
|
|
516
|
+
return `rgba(59, 130, 246, ${0.3 + intensity * 0.7})`;
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
hourlyHeatmapChart = new Chart(ctx, {
|
|
520
|
+
type: 'bar',
|
|
521
|
+
data: {
|
|
522
|
+
labels: Array.from({length: 24}, (_, i) => `${i}:00`),
|
|
523
|
+
datasets: [{
|
|
524
|
+
label: 'Requests',
|
|
525
|
+
data: hourlyData,
|
|
526
|
+
backgroundColor: colors,
|
|
527
|
+
borderWidth: 0,
|
|
528
|
+
}]
|
|
529
|
+
},
|
|
530
|
+
options: {
|
|
531
|
+
responsive: true,
|
|
532
|
+
maintainAspectRatio: false,
|
|
533
|
+
plugins: {
|
|
534
|
+
legend: {
|
|
535
|
+
display: false
|
|
536
|
+
},
|
|
537
|
+
tooltip: {
|
|
538
|
+
callbacks: {
|
|
539
|
+
label: function(context) {
|
|
540
|
+
return `Requests: ${formatNumber(context.parsed.y)}`;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
scales: {
|
|
546
|
+
x: {
|
|
547
|
+
title: {
|
|
548
|
+
display: true,
|
|
549
|
+
text: 'Hour of Day'
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
y: {
|
|
553
|
+
beginAtZero: true,
|
|
554
|
+
ticks: {
|
|
555
|
+
precision: 0
|
|
556
|
+
},
|
|
557
|
+
title: {
|
|
558
|
+
display: true,
|
|
559
|
+
text: 'Request Count'
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Update top endpoints table
|
|
569
|
+
*/
|
|
570
|
+
function updateTopEndpointsTable(data) {
|
|
571
|
+
const tbody = document.getElementById('topEndpointsTable');
|
|
572
|
+
tbody.innerHTML = '';
|
|
573
|
+
|
|
574
|
+
if (data.length === 0) {
|
|
575
|
+
tbody.innerHTML = '<tr><td colspan="3" class="px-4 py-3 text-center text-gray-500">No data available</td></tr>';
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
data.forEach((item, index) => {
|
|
580
|
+
const row = document.createElement('tr');
|
|
581
|
+
row.className = index % 2 === 0 ? 'bg-white' : 'bg-gray-50';
|
|
582
|
+
|
|
583
|
+
const pathCell = document.createElement('td');
|
|
584
|
+
pathCell.className = 'px-4 py-3 text-sm text-gray-900';
|
|
585
|
+
pathCell.textContent = item.path.length > 40 ?
|
|
586
|
+
item.path.substring(0, 37) + '...' : item.path;
|
|
587
|
+
pathCell.title = item.path;
|
|
588
|
+
|
|
589
|
+
const countCell = document.createElement('td');
|
|
590
|
+
countCell.className = 'px-4 py-3 text-sm text-gray-900 text-right font-semibold';
|
|
591
|
+
countCell.textContent = formatNumber(item.count);
|
|
592
|
+
|
|
593
|
+
const timeCell = document.createElement('td');
|
|
594
|
+
timeCell.className = 'px-4 py-3 text-sm text-right';
|
|
595
|
+
if (item.avg_response_time) {
|
|
596
|
+
const time = item.avg_response_time;
|
|
597
|
+
timeCell.innerHTML = getColoredResponseTime(time);
|
|
598
|
+
} else {
|
|
599
|
+
timeCell.textContent = '-';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
row.appendChild(pathCell);
|
|
603
|
+
row.appendChild(countCell);
|
|
604
|
+
row.appendChild(timeCell);
|
|
605
|
+
tbody.appendChild(row);
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Update top IPs table
|
|
611
|
+
*/
|
|
612
|
+
function updateTopIPsTable(data) {
|
|
613
|
+
const tbody = document.getElementById('topIPsTable');
|
|
614
|
+
tbody.innerHTML = '';
|
|
615
|
+
|
|
616
|
+
if (data.length === 0) {
|
|
617
|
+
tbody.innerHTML = '<tr><td colspan="2" class="px-4 py-3 text-center text-gray-500">No data available</td></tr>';
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
data.forEach((item, index) => {
|
|
622
|
+
const row = document.createElement('tr');
|
|
623
|
+
row.className = index % 2 === 0 ? 'bg-white' : 'bg-gray-50';
|
|
624
|
+
|
|
625
|
+
const ipCell = document.createElement('td');
|
|
626
|
+
ipCell.className = 'px-4 py-3 text-sm text-gray-900 font-mono';
|
|
627
|
+
ipCell.textContent = item.ip_address;
|
|
628
|
+
|
|
629
|
+
const countCell = document.createElement('td');
|
|
630
|
+
countCell.className = 'px-4 py-3 text-sm text-gray-900 text-right font-semibold';
|
|
631
|
+
countCell.textContent = formatNumber(item.count);
|
|
632
|
+
|
|
633
|
+
row.appendChild(ipCell);
|
|
634
|
+
row.appendChild(countCell);
|
|
635
|
+
tbody.appendChild(row);
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Helper: Format numbers with commas
|
|
641
|
+
*/
|
|
642
|
+
function formatNumber(num) {
|
|
643
|
+
if (num === null || num === undefined) return '0';
|
|
644
|
+
return num.toLocaleString();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Helper: Get colored response time HTML
|
|
649
|
+
*/
|
|
650
|
+
function getColoredResponseTime(time) {
|
|
651
|
+
let color;
|
|
652
|
+
if (time < 100) {
|
|
653
|
+
color = 'text-green-600';
|
|
654
|
+
} else if (time < 500) {
|
|
655
|
+
color = 'text-yellow-600';
|
|
656
|
+
} else if (time < 1000) {
|
|
657
|
+
color = 'text-orange-600';
|
|
658
|
+
} else {
|
|
659
|
+
color = 'text-red-600';
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return `<span class="${color} font-semibold">${time.toFixed(1)} ms</span>`;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Helper: Get common chart options
|
|
667
|
+
*/
|
|
668
|
+
function getCommonChartOptions() {
|
|
669
|
+
return {
|
|
670
|
+
responsive: true,
|
|
671
|
+
maintainAspectRatio: false,
|
|
672
|
+
plugins: {
|
|
673
|
+
legend: {
|
|
674
|
+
position: 'bottom',
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Export analytics data as CSV
|
|
682
|
+
*/
|
|
683
|
+
function exportAsCSV() {
|
|
684
|
+
// This would require server-side implementation
|
|
685
|
+
alert('CSV export functionality - to be implemented');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Print dashboard
|
|
690
|
+
*/
|
|
691
|
+
function printDashboard() {
|
|
692
|
+
window.print();
|
|
693
|
+
}
|