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
trafficmonitor/tasks.py
ADDED
|
@@ -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>
|