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,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production-ready Analytics Dashboard JavaScript
|
|
3
|
+
* Features: Error handling, retry logic, performance optimization, accessibility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class DashboardManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.charts = new Map();
|
|
9
|
+
this.retryCount = 0;
|
|
10
|
+
this.maxRetries = 3;
|
|
11
|
+
this.csrfToken = this.getCSRFToken();
|
|
12
|
+
this.abortController = null;
|
|
13
|
+
|
|
14
|
+
// Debounced filter function
|
|
15
|
+
this.applyFiltersDebounced = this.debounce(this.applyFilters.bind(this), 300);
|
|
16
|
+
|
|
17
|
+
this.init();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init() {
|
|
21
|
+
this.setupEventListeners();
|
|
22
|
+
this.loadDashboard();
|
|
23
|
+
this.setupPerformanceMonitoring();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setupEventListeners() {
|
|
27
|
+
// Filter change events
|
|
28
|
+
document.getElementById('rangeFilter')?.addEventListener('change', () => {
|
|
29
|
+
this.handleRangeChange();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Keyboard accessibility
|
|
33
|
+
document.addEventListener('keydown', (e) => {
|
|
34
|
+
if (e.key === 'r' && e.ctrlKey) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
this.refreshDashboard();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Window resize handling
|
|
41
|
+
window.addEventListener('resize', this.debounce(() => {
|
|
42
|
+
this.resizeCharts();
|
|
43
|
+
}, 250));
|
|
44
|
+
|
|
45
|
+
// Visibility change (pause updates when tab not visible)
|
|
46
|
+
document.addEventListener('visibilitychange', () => {
|
|
47
|
+
if (document.hidden) {
|
|
48
|
+
this.pauseUpdates();
|
|
49
|
+
} else {
|
|
50
|
+
this.resumeUpdates();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async loadDashboard() {
|
|
56
|
+
try {
|
|
57
|
+
this.showLoading();
|
|
58
|
+
const data = await this.fetchAnalyticsData();
|
|
59
|
+
this.renderDashboard(data);
|
|
60
|
+
this.hideLoading();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.handleError(error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async fetchAnalyticsData(filters = {}) {
|
|
67
|
+
// Cancel previous request
|
|
68
|
+
if (this.abortController) {
|
|
69
|
+
this.abortController.abort();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.abortController = new AbortController();
|
|
73
|
+
|
|
74
|
+
const params = new URLSearchParams(filters);
|
|
75
|
+
const url = `/api/analytics/overview/?${params}`;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(url, {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
headers: {
|
|
81
|
+
'X-CSRFToken': this.csrfToken,
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
},
|
|
84
|
+
signal: this.abortController.signal,
|
|
85
|
+
timeout: 30000, // 30 second timeout
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
this.retryCount = 0; // Reset on success
|
|
94
|
+
return data;
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error.name === 'AbortError') {
|
|
98
|
+
throw new Error('Request was cancelled');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Retry logic
|
|
102
|
+
if (this.retryCount < this.maxRetries) {
|
|
103
|
+
this.retryCount++;
|
|
104
|
+
console.warn(`Request failed, retrying (${this.retryCount}/${this.maxRetries}):`, error);
|
|
105
|
+
await this.delay(1000 * this.retryCount); // Exponential backoff
|
|
106
|
+
return this.fetchAnalyticsData(filters);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
renderDashboard(data) {
|
|
114
|
+
try {
|
|
115
|
+
this.updateMetrics(data.metrics);
|
|
116
|
+
this.renderCharts(data.charts);
|
|
117
|
+
this.updateLastRefresh();
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Error rendering dashboard:', error);
|
|
120
|
+
this.showError('Failed to render dashboard data');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
updateMetrics(metrics) {
|
|
125
|
+
const elements = {
|
|
126
|
+
'totalRequests': metrics.total_requests,
|
|
127
|
+
'readOps': metrics.read_operations,
|
|
128
|
+
'writeOps': metrics.write_operations,
|
|
129
|
+
'deleteOps': metrics.delete_operations,
|
|
130
|
+
'errorRate': `${metrics.error_rate}%`,
|
|
131
|
+
'avgResponseTime': `${metrics.avg_response_time}ms`,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
Object.entries(elements).forEach(([id, value]) => {
|
|
135
|
+
const element = document.getElementById(id);
|
|
136
|
+
if (element) {
|
|
137
|
+
this.animateValue(element, value);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
renderCharts(chartData) {
|
|
143
|
+
// Destroy existing charts to prevent memory leaks
|
|
144
|
+
this.destroyCharts();
|
|
145
|
+
|
|
146
|
+
// Render new charts with error handling
|
|
147
|
+
this.renderChart('requestsOverTime', chartData.requests_over_time, 'line');
|
|
148
|
+
this.renderChart('statusCodes', chartData.status_codes, 'doughnut');
|
|
149
|
+
this.renderChart('methods', chartData.methods, 'bar');
|
|
150
|
+
this.renderChart('errorTrend', chartData.error_trend, 'line');
|
|
151
|
+
this.renderChart('slowestEndpoints', chartData.slowest_endpoints, 'horizontalBar');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
renderChart(canvasId, data, type) {
|
|
155
|
+
try {
|
|
156
|
+
const canvas = document.getElementById(canvasId);
|
|
157
|
+
if (!canvas) {
|
|
158
|
+
console.warn(`Canvas element ${canvasId} not found`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const ctx = canvas.getContext('2d');
|
|
163
|
+
const config = this.getChartConfig(type, data);
|
|
164
|
+
|
|
165
|
+
const chart = new Chart(ctx, config);
|
|
166
|
+
this.charts.set(canvasId, chart);
|
|
167
|
+
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Error rendering chart ${canvasId}:`, error);
|
|
170
|
+
this.showChartError(canvasId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getChartConfig(type, data) {
|
|
175
|
+
const baseConfig = {
|
|
176
|
+
responsive: true,
|
|
177
|
+
maintainAspectRatio: false,
|
|
178
|
+
plugins: {
|
|
179
|
+
legend: {
|
|
180
|
+
display: true,
|
|
181
|
+
position: 'top',
|
|
182
|
+
},
|
|
183
|
+
tooltip: {
|
|
184
|
+
mode: 'index',
|
|
185
|
+
intersect: false,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
interaction: {
|
|
189
|
+
mode: 'nearest',
|
|
190
|
+
axis: 'x',
|
|
191
|
+
intersect: false,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Chart-specific configurations
|
|
196
|
+
switch (type) {
|
|
197
|
+
case 'line':
|
|
198
|
+
return {
|
|
199
|
+
type: 'line',
|
|
200
|
+
data: data,
|
|
201
|
+
options: {
|
|
202
|
+
...baseConfig,
|
|
203
|
+
scales: {
|
|
204
|
+
x: {
|
|
205
|
+
type: 'time',
|
|
206
|
+
time: {
|
|
207
|
+
displayFormats: {
|
|
208
|
+
hour: 'MMM dd, HH:mm',
|
|
209
|
+
day: 'MMM dd',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
y: {
|
|
214
|
+
beginAtZero: true,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
case 'doughnut':
|
|
221
|
+
return {
|
|
222
|
+
type: 'doughnut',
|
|
223
|
+
data: data,
|
|
224
|
+
options: {
|
|
225
|
+
...baseConfig,
|
|
226
|
+
cutout: '60%',
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
default:
|
|
231
|
+
return {
|
|
232
|
+
type: type,
|
|
233
|
+
data: data,
|
|
234
|
+
options: baseConfig,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Utility methods
|
|
240
|
+
getCSRFToken() {
|
|
241
|
+
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
242
|
+
if (!token) {
|
|
243
|
+
console.warn('CSRF token not found');
|
|
244
|
+
}
|
|
245
|
+
return token;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
debounce(func, wait) {
|
|
249
|
+
let timeout;
|
|
250
|
+
return function executedFunction(...args) {
|
|
251
|
+
const later = () => {
|
|
252
|
+
clearTimeout(timeout);
|
|
253
|
+
func(...args);
|
|
254
|
+
};
|
|
255
|
+
clearTimeout(timeout);
|
|
256
|
+
timeout = setTimeout(later, wait);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
delay(ms) {
|
|
261
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
animateValue(element, newValue) {
|
|
265
|
+
element.style.opacity = '0.5';
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
element.textContent = newValue;
|
|
268
|
+
element.style.opacity = '1';
|
|
269
|
+
}, 150);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
showLoading() {
|
|
273
|
+
document.getElementById('loadingIndicator')?.classList.remove('hidden');
|
|
274
|
+
document.getElementById('dashboardContent')?.classList.add('hidden');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
hideLoading() {
|
|
278
|
+
document.getElementById('loadingIndicator')?.classList.add('hidden');
|
|
279
|
+
document.getElementById('dashboardContent')?.classList.remove('hidden');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
showError(message) {
|
|
283
|
+
const errorDiv = document.createElement('div');
|
|
284
|
+
errorDiv.className = 'error-state';
|
|
285
|
+
errorDiv.innerHTML = `
|
|
286
|
+
<i class="fas fa-exclamation-triangle text-4xl mb-4"></i>
|
|
287
|
+
<h3 class="text-xl font-semibold mb-2">Error Loading Dashboard</h3>
|
|
288
|
+
<p>${message}</p>
|
|
289
|
+
<button onclick="dashboard.refreshDashboard()" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded">
|
|
290
|
+
Retry
|
|
291
|
+
</button>
|
|
292
|
+
`;
|
|
293
|
+
|
|
294
|
+
document.getElementById('dashboardContent').innerHTML = '';
|
|
295
|
+
document.getElementById('dashboardContent').appendChild(errorDiv);
|
|
296
|
+
this.hideLoading();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
destroyCharts() {
|
|
300
|
+
this.charts.forEach(chart => {
|
|
301
|
+
chart.destroy();
|
|
302
|
+
});
|
|
303
|
+
this.charts.clear();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resizeCharts() {
|
|
307
|
+
this.charts.forEach(chart => {
|
|
308
|
+
chart.resize();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
refreshDashboard() {
|
|
313
|
+
this.loadDashboard();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
setupPerformanceMonitoring() {
|
|
317
|
+
// Monitor performance
|
|
318
|
+
if ('performance' in window) {
|
|
319
|
+
window.addEventListener('load', () => {
|
|
320
|
+
const loadTime = performance.now();
|
|
321
|
+
console.log(`Dashboard loaded in ${loadTime.toFixed(2)}ms`);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Initialize dashboard when DOM is ready
|
|
328
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
329
|
+
window.dashboard = new DashboardManager();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Global functions for backward compatibility
|
|
333
|
+
function applyFilters() {
|
|
334
|
+
window.dashboard?.applyFiltersDebounced();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function refreshDashboard() {
|
|
338
|
+
window.dashboard?.refreshDashboard();
|
|
339
|
+
}
|