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.
Files changed (42) hide show
  1. setu_trafficmonitor-2.0.0.dist-info/LICENSE +21 -0
  2. setu_trafficmonitor-2.0.0.dist-info/METADATA +401 -0
  3. setu_trafficmonitor-2.0.0.dist-info/RECORD +42 -0
  4. setu_trafficmonitor-2.0.0.dist-info/WHEEL +5 -0
  5. setu_trafficmonitor-2.0.0.dist-info/top_level.txt +1 -0
  6. trafficmonitor/__init__.py +11 -0
  7. trafficmonitor/admin.py +217 -0
  8. trafficmonitor/analytics/__init__.py +0 -0
  9. trafficmonitor/analytics/enhanced_queries.py +286 -0
  10. trafficmonitor/analytics/serializers.py +238 -0
  11. trafficmonitor/analytics/tests.py +757 -0
  12. trafficmonitor/analytics/urls.py +18 -0
  13. trafficmonitor/analytics/views.py +694 -0
  14. trafficmonitor/apps.py +7 -0
  15. trafficmonitor/circuit_breaker.py +63 -0
  16. trafficmonitor/conf.py +154 -0
  17. trafficmonitor/dashboard_security.py +111 -0
  18. trafficmonitor/db_utils.py +37 -0
  19. trafficmonitor/exceptions.py +93 -0
  20. trafficmonitor/health.py +66 -0
  21. trafficmonitor/load_test.py +423 -0
  22. trafficmonitor/load_test_api.py +307 -0
  23. trafficmonitor/management/__init__.py +1 -0
  24. trafficmonitor/management/commands/__init__.py +1 -0
  25. trafficmonitor/management/commands/cleanup_request_logs.py +77 -0
  26. trafficmonitor/middleware.py +383 -0
  27. trafficmonitor/migrations/0001_initial.py +93 -0
  28. trafficmonitor/migrations/__init__.py +0 -0
  29. trafficmonitor/models.py +206 -0
  30. trafficmonitor/monitoring.py +104 -0
  31. trafficmonitor/permissions.py +64 -0
  32. trafficmonitor/security.py +180 -0
  33. trafficmonitor/settings_production.py +105 -0
  34. trafficmonitor/static/analytics/css/dashboard.css +99 -0
  35. trafficmonitor/static/analytics/js/dashboard-production.js +339 -0
  36. trafficmonitor/static/analytics/js/dashboard-v2.js +697 -0
  37. trafficmonitor/static/analytics/js/dashboard.js +693 -0
  38. trafficmonitor/tasks.py +137 -0
  39. trafficmonitor/templates/analytics/dashboard.html +500 -0
  40. trafficmonitor/tests.py +246 -0
  41. trafficmonitor/views.py +3 -0
  42. trafficmonitor/websocket_consumers.py +128 -0
@@ -0,0 +1,137 @@
1
+ """
2
+ Celery tasks for async request logging.
3
+ Enterprise-grade async processing to avoid request latency.
4
+ """
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ try:
10
+ from celery import shared_task
11
+ CELERY_AVAILABLE = True
12
+ except ImportError:
13
+ CELERY_AVAILABLE = False
14
+ # Provide a no-op decorator if Celery is not available
15
+ def shared_task(*args, **kwargs):
16
+ def decorator(func):
17
+ return func
18
+ return decorator
19
+
20
+
21
+ @shared_task(bind=True, max_retries=3, default_retry_delay=60)
22
+ def log_request_async(self, log_data):
23
+ """
24
+ Async task to log request to database.
25
+ Reduces response time by offloading DB writes to background worker.
26
+
27
+ Args:
28
+ log_data (dict): Dictionary containing all request log fields
29
+
30
+ Returns:
31
+ str: Log entry ID
32
+ """
33
+ try:
34
+ from trafficmonitor.models import RequestLog
35
+
36
+ log_entry = RequestLog.objects.create(**log_data)
37
+ return str(log_entry.id)
38
+
39
+ except Exception as exc:
40
+ logger.error(f"Failed to log request asynchronously: {exc}")
41
+ # Retry the task
42
+ if self.request.retries < self.max_retries:
43
+ raise self.retry(exc=exc)
44
+ return None
45
+
46
+
47
+ @shared_task
48
+ def cleanup_old_logs(retention_days=90):
49
+ """
50
+ Celery task to clean up old request logs.
51
+ Should be scheduled to run daily (e.g., via Celery Beat).
52
+
53
+ Args:
54
+ retention_days (int): Number of days to retain logs
55
+
56
+ Returns:
57
+ dict: Cleanup statistics
58
+ """
59
+ from datetime import timedelta
60
+ from django.utils import timezone
61
+ from trafficmonitor.models import RequestLog
62
+
63
+ cutoff_date = timezone.now() - timedelta(days=retention_days)
64
+
65
+ # Delete in batches to avoid locking
66
+ batch_size = 1000
67
+ total_deleted = 0
68
+
69
+ while True:
70
+ # Get IDs of logs to delete
71
+ log_ids = list(
72
+ RequestLog.objects.filter(
73
+ timestamp__lt=cutoff_date
74
+ ).values_list('id', flat=True)[:batch_size]
75
+ )
76
+
77
+ if not log_ids:
78
+ break
79
+
80
+ # Delete batch
81
+ deleted_count, _ = RequestLog.objects.filter(id__in=log_ids).delete()
82
+ total_deleted += deleted_count
83
+
84
+ logger.info(f"Deleted {deleted_count} old request logs (batch)")
85
+
86
+ logger.info(f"Cleanup complete. Total deleted: {total_deleted} logs older than {retention_days} days")
87
+
88
+ return {
89
+ 'deleted_count': total_deleted,
90
+ 'cutoff_date': cutoff_date.isoformat(),
91
+ 'retention_days': retention_days,
92
+ }
93
+
94
+
95
+ @shared_task
96
+ def aggregate_daily_stats(date=None):
97
+ """
98
+ Pre-compute daily statistics for faster dashboard loading.
99
+ Should be scheduled to run daily after midnight.
100
+
101
+ Args:
102
+ date: Date to aggregate (defaults to yesterday)
103
+
104
+ Returns:
105
+ dict: Aggregated statistics
106
+ """
107
+ from datetime import timedelta
108
+ from django.utils import timezone
109
+ from trafficmonitor.models import RequestLog
110
+ from trafficmonitor.analytics.enhanced_queries import EnhancedAnalyticsQueries
111
+
112
+ if date is None:
113
+ # Default to yesterday
114
+ date = (timezone.now() - timedelta(days=1)).date()
115
+
116
+ start_date = timezone.make_aware(timezone.datetime.combine(date, timezone.datetime.min.time()))
117
+ end_date = timezone.make_aware(timezone.datetime.combine(date, timezone.datetime.max.time()))
118
+
119
+ # Compute statistics
120
+ stats = {
121
+ 'date': date.isoformat(),
122
+ 'total_requests': RequestLog.objects.filter(
123
+ timestamp__gte=start_date,
124
+ timestamp__lte=end_date
125
+ ).count(),
126
+ 'read_write_summary': EnhancedAnalyticsQueries.get_read_write_summary(
127
+ start_date, end_date
128
+ ),
129
+ 'api_health': EnhancedAnalyticsQueries.get_api_health_metrics(
130
+ start_date, end_date
131
+ ),
132
+ }
133
+
134
+ # Store in cache or separate stats table (implement as needed)
135
+ logger.info(f"Daily stats aggregated for {date}: {stats['total_requests']} requests")
136
+
137
+ return stats
@@ -0,0 +1,500 @@
1
+ {% load static %}
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <meta http-equiv="X-Content-Type-Options" content="nosniff">
9
+ <meta http-equiv="X-Frame-Options" content="DENY">
10
+ <meta http-equiv="X-XSS-Protection" content="1; mode=block">
11
+ <meta name="csrf-token" content="{{ csrf_token }}">
12
+ <title>Enterprise Analytics Dashboard - TrafficMonitor v2.0</title>
13
+
14
+ <!-- CDN assets -->
15
+ <script src="https://cdn.tailwindcss.com"></script>
16
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
17
+ <link rel="stylesheet" href="{% static 'analytics/css/dashboard.css' %}">
18
+
19
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
20
+ <script
21
+ src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
22
+
23
+ <style>
24
+ .card {
25
+ transition: transform 0.2s, box-shadow 0.2s;
26
+ }
27
+
28
+ .card:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
31
+ }
32
+
33
+ .chart-container {
34
+ position: relative;
35
+ height: 300px;
36
+ margin-bottom: 1rem;
37
+ }
38
+
39
+ .chart-container-large {
40
+ position: relative;
41
+ height: 400px;
42
+ margin-bottom: 1rem;
43
+ }
44
+
45
+ .stat-card {
46
+ background: linear-gradient(135deg, var(--tw-gradient-from), var(--tw-gradient-to));
47
+ }
48
+
49
+ .metric-badge {
50
+ display: inline-block;
51
+ padding: 0.25rem 0.75rem;
52
+ border-radius: 9999px;
53
+ font-size: 0.75rem;
54
+ font-weight: 600;
55
+ }
56
+
57
+ .badge-success {
58
+ background: #10b981;
59
+ color: white;
60
+ }
61
+
62
+ .badge-warning {
63
+ background: #f59e0b;
64
+ color: white;
65
+ }
66
+
67
+ .badge-danger {
68
+ background: #ef4444;
69
+ color: white;
70
+ }
71
+
72
+ .badge-info {
73
+ background: #3b82f6;
74
+ color: white;
75
+ }
76
+ </style>
77
+ </head>
78
+
79
+ <body class="bg-gray-50">
80
+ <!-- Header -->
81
+ <header class="bg-gradient-to-r from-blue-600 to-blue-700 shadow-lg">
82
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
83
+ <div class="flex items-center justify-between">
84
+ <div>
85
+ <h1 class="text-3xl font-bold text-white">
86
+ <i class="fas fa-chart-line mr-2"></i>
87
+ Enterprise Analytics Dashboard
88
+ </h1>
89
+ <p class="text-sm text-blue-100 mt-1">
90
+ <i class="fas fa-rocket mr-1"></i>
91
+ TrafficMonitor v2.0 - Real-time API monitoring with Read/Write insights
92
+ </p>
93
+ </div>
94
+ <div class="flex items-center space-x-4">
95
+ <button onclick="manualRefresh()"
96
+ class="text-white hover:text-blue-100 px-3 py-2 rounded-md hover:bg-blue-500 transition focus:outline-none"
97
+ title="Refresh Now">
98
+ <i class="fas fa-sync-alt"></i>
99
+ </button>
100
+ <div class="text-right border-l border-blue-400 pl-4">
101
+ <p class="text-xs text-blue-100">Auto-refresh</p>
102
+ <button id="toggleRefresh" onclick="toggleAutoRefresh()"
103
+ class="text-sm font-semibold text-white hover:text-blue-100 focus:outline-none">
104
+ <i id="refreshIcon" class="fas fa-sync-alt fa-spin"></i>
105
+ <span id="refreshStatus">ON (30s)</span>
106
+ </button>
107
+ </div>
108
+ <a href="/admin/"
109
+ class="text-sm text-white hover:text-blue-100 px-3 py-2 rounded-md hover:bg-blue-500 transition">
110
+ <i class="fas fa-cog mr-1"></i> Admin
111
+ </a>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </header>
116
+
117
+ <!-- Filters Bar -->
118
+ <div class="bg-white border-b border-gray-200">
119
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
120
+ <form id="filterForm" class="grid grid-cols-1 md:grid-cols-5 gap-4">
121
+ <!-- Date Range -->
122
+ <div>
123
+ <label class="block text-sm font-medium text-gray-700 mb-1">Date Range</label>
124
+ <select name="range" id="rangeSelect"
125
+ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
126
+ <option value="today" {% if selected_range=='today' %}selected{% endif %}>Today</option>
127
+ <option value="yesterday" {% if selected_range=='yesterday' %}selected{% endif %}>Yesterday
128
+ </option>
129
+ <option value="last_7_days" {% if selected_range=='last_7_days' %}selected{% endif %}>Last 7
130
+ Days</option>
131
+ <option value="last_30_days" {% if selected_range=='last_30_days' %}selected{% endif %}>Last 30
132
+ Days</option>
133
+ <option value="custom" {% if selected_range=='custom' %}selected{% endif %}>Custom Range
134
+ </option>
135
+ </select>
136
+ </div>
137
+
138
+ <!-- Method Filter -->
139
+ <div>
140
+ <label class="block text-sm font-medium text-gray-700 mb-1">Method</label>
141
+ <select name="method" id="methodFilter"
142
+ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
143
+ <option value="">All Methods</option>
144
+ <option value="GET">GET</option>
145
+ <option value="POST">POST</option>
146
+ <option value="PUT">PUT</option>
147
+ <option value="PATCH">PATCH</option>
148
+ <option value="DELETE">DELETE</option>
149
+ </select>
150
+ </div>
151
+
152
+ <!-- Status Filter -->
153
+ <div>
154
+ <label class="block text-sm font-medium text-gray-700 mb-1">Status Code</label>
155
+ <select name="status" id="statusFilter"
156
+ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
157
+ <option value="">All Status</option>
158
+ <option value="200">200 OK</option>
159
+ <option value="201">201 Created</option>
160
+ <option value="400">400 Bad Request</option>
161
+ <option value="401">401 Unauthorized</option>
162
+ <option value="403">403 Forbidden</option>
163
+ <option value="404">404 Not Found</option>
164
+ <option value="500">500 Server Error</option>
165
+ </select>
166
+ </div>
167
+
168
+ <!-- Path Filter -->
169
+ <div>
170
+ <label class="block text-sm font-medium text-gray-700 mb-1">Path Contains</label>
171
+ <input type="text" name="path" id="pathFilter" placeholder="e.g., /api/monitoring"
172
+ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
173
+ </div>
174
+
175
+ <!-- Apply Button -->
176
+ <div class="flex items-end">
177
+ <button type="button" onclick="applyFilters()"
178
+ class="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
179
+ <i class="fas fa-filter mr-2"></i>Apply Filters
180
+ </button>
181
+ </div>
182
+ </form>
183
+
184
+ <!-- Custom Date Range (hidden by default) -->
185
+ <div id="customRangeContainer" class="mt-4 hidden">
186
+ <div class="grid grid-cols-2 gap-4">
187
+ <div>
188
+ <label class="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
189
+ <input type="date" id="startDate" class="w-full px-3 py-2 border border-gray-300 rounded-md">
190
+ </div>
191
+ <div>
192
+ <label class="block text-sm font-medium text-gray-700 mb-1">End Date</label>
193
+ <input type="date" id="endDate" class="w-full px-3 py-2 border border-gray-300 rounded-md">
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <!-- Main Content -->
201
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
202
+ <!-- Loading Indicator -->
203
+ <div id="loadingIndicator" class="text-center py-12">
204
+ <i class="fas fa-spinner fa-spin text-4xl text-blue-600"></i>
205
+ <p class="text-gray-600 mt-4">Loading analytics data...</p>
206
+ </div>
207
+
208
+ <!-- Dashboard Content (hidden until loaded) -->
209
+ <div id="dashboardContent" class="hidden">
210
+ <!-- Stats Overview Cards -->
211
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">
212
+ <!-- Total Requests -->
213
+ <div
214
+ class="card stat-card bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg shadow-lg p-6 text-white">
215
+ <div class="flex items-center justify-between">
216
+ <div>
217
+ <p class="text-blue-100 text-sm font-medium">Total Requests</p>
218
+ <p id="totalRequests" class="text-3xl font-bold mt-2">-</p>
219
+ <p class="text-blue-100 text-xs mt-1">Selected Range</p>
220
+ </div>
221
+ <i class="fas fa-server text-4xl text-blue-200"></i>
222
+ </div>
223
+ </div>
224
+
225
+ <!-- Read Operations -->
226
+ <div
227
+ class="card stat-card bg-gradient-to-br from-green-500 to-green-600 rounded-lg shadow-lg p-6 text-white">
228
+ <div class="flex items-center justify-between">
229
+ <div>
230
+ <p class="text-green-100 text-sm font-medium">📖 READ Ops</p>
231
+ <p id="readOps" class="text-3xl font-bold mt-2">-</p>
232
+ <p id="readPercent" class="text-green-100 text-xs mt-1">-% of total</p>
233
+ </div>
234
+ <i class="fas fa-book-reader text-4xl text-green-200"></i>
235
+ </div>
236
+ </div>
237
+
238
+ <!-- Write Operations -->
239
+ <div
240
+ class="card stat-card bg-gradient-to-br from-purple-500 to-purple-600 rounded-lg shadow-lg p-6 text-white">
241
+ <div class="flex items-center justify-between">
242
+ <div>
243
+ <p class="text-purple-100 text-sm font-medium">✏️ WRITE Ops</p>
244
+ <p id="writeOps" class="text-3xl font-bold mt-2">-</p>
245
+ <p id="writePercent" class="text-purple-100 text-xs mt-1">-% of total</p>
246
+ </div>
247
+ <i class="fas fa-edit text-4xl text-purple-200"></i>
248
+ </div>
249
+ </div>
250
+
251
+ <!-- API Success Rate -->
252
+ <div
253
+ class="card stat-card bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-lg shadow-lg p-6 text-white">
254
+ <div class="flex items-center justify-between">
255
+ <div>
256
+ <p class="text-emerald-100 text-sm font-medium">Success Rate</p>
257
+ <p id="successRate" class="text-3xl font-bold mt-2">-</p>
258
+ <p class="text-emerald-100 text-xs mt-1">2xx Responses</p>
259
+ </div>
260
+ <i class="fas fa-check-circle text-4xl text-emerald-200"></i>
261
+ </div>
262
+ </div>
263
+
264
+ <!-- P95 Response Time -->
265
+ <div
266
+ class="card stat-card bg-gradient-to-br from-orange-500 to-orange-600 rounded-lg shadow-lg p-6 text-white">
267
+ <div class="flex items-center justify-between">
268
+ <div>
269
+ <p class="text-orange-100 text-sm font-medium">P95 Response</p>
270
+ <p id="p95ResponseTime" class="text-3xl font-bold mt-2">-</p>
271
+ <p class="text-orange-100 text-xs mt-1">Milliseconds</p>
272
+ </div>
273
+ <i class="fas fa-tachometer-alt text-4xl text-orange-200"></i>
274
+ </div>
275
+ </div>
276
+ </div>
277
+
278
+ <!-- API Health Metrics Cards -->
279
+ <div class="grid grid-cols-2 md:grid-cols-6 gap-4 mb-8">
280
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-green-500">
281
+ <p class="text-gray-600 text-xs font-medium">2xx Success</p>
282
+ <p id="statusSuccess" class="text-2xl font-bold text-gray-900 mt-1">-</p>
283
+ </div>
284
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-red-500">
285
+ <p class="text-gray-600 text-xs font-medium">4xx/5xx Errors</p>
286
+ <p id="statusErrors" class="text-2xl font-bold text-gray-900 mt-1">-</p>
287
+ </div>
288
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-blue-500">
289
+ <p class="text-gray-600 text-xs font-medium">P50 Response</p>
290
+ <p id="p50Response" class="text-2xl font-bold text-gray-900 mt-1">-</p>
291
+ </div>
292
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-yellow-500">
293
+ <p class="text-gray-600 text-xs font-medium">P99 Response</p>
294
+ <p id="p99Response" class="text-2xl font-bold text-gray-900 mt-1">-</p>
295
+ </div>
296
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-purple-500">
297
+ <p class="text-gray-600 text-xs font-medium">Throughput</p>
298
+ <p id="throughputRPS" class="text-2xl font-bold text-gray-900 mt-1">-</p>
299
+ </div>
300
+ <div class="bg-white rounded-lg shadow p-4 border-l-4 border-indigo-500">
301
+ <p class="text-gray-600 text-xs font-medium">Avg Queries</p>
302
+ <p id="avgQueries" class="text-2xl font-bold text-gray-900 mt-1">-</p>
303
+ </div>
304
+ </div>
305
+
306
+ <!-- Charts Grid -->
307
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
308
+ <!-- Requests Over Time -->
309
+ <div class="bg-white rounded-lg shadow-lg p-6">
310
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
311
+ <i class="fas fa-chart-line text-blue-600 mr-2"></i>
312
+ Requests Over Time
313
+ </h3>
314
+ <div class="chart-container-large">
315
+ <canvas id="requestsOverTimeChart"></canvas>
316
+ </div>
317
+ </div>
318
+
319
+ <!-- Read vs Write Operations -->
320
+ <div class="bg-white rounded-lg shadow-lg p-6">
321
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
322
+ <i class="fas fa-exchange-alt text-green-600 mr-2"></i>
323
+ READ vs WRITE Operations
324
+ </h3>
325
+ <div class="chart-container-large">
326
+ <canvas id="readWriteChart"></canvas>
327
+ </div>
328
+ </div>
329
+
330
+ <!-- HTTP Methods Distribution -->
331
+ <div class="bg-white rounded-lg shadow-lg p-6">
332
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
333
+ <i class="fas fa-chart-pie text-purple-600 mr-2"></i>
334
+ HTTP Methods Distribution
335
+ </h3>
336
+ <div class="chart-container">
337
+ <canvas id="methodsChart"></canvas>
338
+ </div>
339
+ </div>
340
+
341
+ <!-- Endpoint Categories -->
342
+ <div class="bg-white rounded-lg shadow-lg p-6">
343
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
344
+ <i class="fas fa-sitemap text-indigo-600 mr-2"></i>
345
+ Endpoint Categories
346
+ </h3>
347
+ <div class="chart-container">
348
+ <canvas id="endpointCategoriesChart"></canvas>
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ <!-- Full Width Charts -->
354
+ <div class="grid grid-cols-1 gap-6 mb-8">
355
+ <!-- Slowest Endpoints -->
356
+ <div class="bg-white rounded-lg shadow-lg p-6">
357
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
358
+ <i class="fas fa-hourglass-half text-orange-600 mr-2"></i>
359
+ Slowest Endpoints (Avg Response Time)
360
+ </h3>
361
+ <div class="chart-container-large">
362
+ <canvas id="slowestEndpointsChart"></canvas>
363
+ </div>
364
+ </div>
365
+
366
+ <!-- Hourly Heatmap -->
367
+ <div class="bg-white rounded-lg shadow-lg p-6">
368
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
369
+ <i class="fas fa-fire text-red-600 mr-2"></i>
370
+ Request Volume by Hour
371
+ </h3>
372
+ <div class="chart-container">
373
+ <canvas id="hourlyHeatmapChart"></canvas>
374
+ </div>
375
+ </div>
376
+ </div>
377
+
378
+ <!-- Top Endpoints Table -->
379
+ <div class="bg-white rounded-lg shadow-lg p-6 mb-8">
380
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
381
+ <i class="fas fa-trophy text-yellow-600 mr-2"></i>
382
+ Top 10 Endpoints by Request Volume
383
+ </h3>
384
+ <div class="overflow-x-auto">
385
+ <table class="min-w-full divide-y divide-gray-200">
386
+ <thead class="bg-gray-50">
387
+ <tr>
388
+ <th
389
+ class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
390
+ #</th>
391
+ <th
392
+ class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
393
+ Endpoint</th>
394
+ <th
395
+ class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
396
+ Category</th>
397
+ <th
398
+ class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
399
+ Requests</th>
400
+ <th
401
+ class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
402
+ Avg Time</th>
403
+ <th
404
+ class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
405
+ Max Time</th>
406
+ <th
407
+ class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
408
+ Health</th>
409
+ </tr>
410
+ </thead>
411
+ <tbody id="topEndpointsTable" class="bg-white divide-y divide-gray-200">
412
+ <!-- Populated by JavaScript -->
413
+ </tbody>
414
+ </table>
415
+ </div>
416
+ </div>
417
+
418
+ <!-- Footer -->
419
+ <div class="mt-8 p-6 bg-white rounded-lg shadow-lg">
420
+ <div class="flex items-center justify-between">
421
+ <div class="text-sm text-gray-600">
422
+ <p><i class="fas fa-clock mr-2"></i>Last updated: <span id="lastUpdated"
423
+ class="font-semibold">-</span></p>
424
+ <p class="mt-1"><i class="fas fa-rocket mr-2"></i>TrafficMonitor v2.0 - Enterprise Edition</p>
425
+ </div>
426
+ <div class="flex space-x-4">
427
+ <a href="/admin/trafficmonitor/requestlog/"
428
+ class="text-blue-600 hover:text-blue-800 font-medium">
429
+ <i class="fas fa-database mr-1"></i> View Raw Logs
430
+ </a>
431
+ <a href="/api/analytics/overview/" class="text-green-600 hover:text-green-800 font-medium">
432
+ <i class="fas fa-code mr-1"></i> API Docs
433
+ </a>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ </div>
438
+ </main>
439
+
440
+ <!-- JavaScript - Enterprise v2.0 -->
441
+ <script src="{% static 'analytics/js/dashboard-v2.js' %}"></script>
442
+ <script>
443
+ // Toggle auto-refresh on/off
444
+ function toggleAutoRefresh() {
445
+ if (window.autoRefreshEnabled) {
446
+ // Disable auto-refresh
447
+ if (window.refreshIntervalId) {
448
+ clearInterval(window.refreshIntervalId);
449
+ window.refreshIntervalId = null;
450
+ }
451
+ if (window.countdownIntervalId) {
452
+ clearInterval(window.countdownIntervalId);
453
+ window.countdownIntervalId = null;
454
+ }
455
+ window.autoRefreshEnabled = false;
456
+ document.getElementById('refreshStatus').textContent = 'OFF';
457
+ document.getElementById('refreshIcon').classList.remove('fa-spin');
458
+ console.log('Auto-refresh disabled');
459
+ } else {
460
+ // Enable auto-refresh
461
+ window.autoRefreshEnabled = true;
462
+
463
+ // Main refresh interval
464
+ window.refreshIntervalId = setInterval(function () {
465
+ console.log('Auto-refreshing dashboard data...');
466
+ refreshDashboard();
467
+ }, 30000);
468
+
469
+ // Countdown timer
470
+ let countdown = 30;
471
+ window.countdownIntervalId = setInterval(() => {
472
+ countdown--;
473
+ const refreshStatus = document.getElementById('refreshStatus');
474
+ if (refreshStatus && window.autoRefreshEnabled) {
475
+ refreshStatus.textContent = `ON (${countdown}s)`;
476
+ }
477
+ if (countdown <= 0) {
478
+ countdown = 30;
479
+ }
480
+ }, 1000);
481
+
482
+ document.getElementById('refreshStatus').textContent = 'ON (30s)';
483
+ document.getElementById('refreshIcon').classList.add('fa-spin');
484
+ console.log('Auto-refresh enabled');
485
+ }
486
+ }
487
+
488
+ // Manual refresh button
489
+ function manualRefresh() {
490
+ console.log('Manual refresh triggered');
491
+ const icon = document.getElementById('refreshIcon');
492
+ icon.classList.add('fa-spin');
493
+ refreshDashboard().then(() => {
494
+ setTimeout(() => icon.classList.remove('fa-spin'), 1000);
495
+ });
496
+ }
497
+ </script>
498
+ </body>
499
+
500
+ </html>