api-mocker 0.1.1__py3-none-any.whl → 0.1.3__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.
- api_mocker/__init__.py +1 -1
- api_mocker/advanced.py +391 -0
- api_mocker/analytics.py +368 -0
- api_mocker/cli.py +167 -0
- api_mocker/core.py +3 -3
- api_mocker/dashboard.py +386 -0
- api_mocker-0.1.3.dist-info/METADATA +441 -0
- api_mocker-0.1.3.dist-info/RECORD +17 -0
- api_mocker-0.1.1.dist-info/METADATA +0 -657
- api_mocker-0.1.1.dist-info/RECORD +0 -14
- {api_mocker-0.1.1.dist-info → api_mocker-0.1.3.dist-info}/WHEEL +0 -0
- {api_mocker-0.1.1.dist-info → api_mocker-0.1.3.dist-info}/entry_points.txt +0 -0
- {api_mocker-0.1.1.dist-info → api_mocker-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {api_mocker-0.1.1.dist-info → api_mocker-0.1.3.dist-info}/top_level.txt +0 -0
api_mocker/dashboard.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Web dashboard for api-mocker analytics and monitoring.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from typing import Dict, List, Any, Optional
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import aiofiles
|
|
12
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
13
|
+
from fastapi.responses import HTMLResponse
|
|
14
|
+
from fastapi.staticfiles import StaticFiles
|
|
15
|
+
import uvicorn
|
|
16
|
+
from .analytics import AnalyticsManager
|
|
17
|
+
|
|
18
|
+
class DashboardManager:
|
|
19
|
+
"""Manages the web dashboard for analytics visualization."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, analytics_manager: AnalyticsManager, port: int = 8080):
|
|
22
|
+
self.analytics = analytics_manager
|
|
23
|
+
self.port = port
|
|
24
|
+
self.app = FastAPI(title="API-Mocker Dashboard", version="1.0.0")
|
|
25
|
+
self.active_connections: List[WebSocket] = []
|
|
26
|
+
self._setup_routes()
|
|
27
|
+
|
|
28
|
+
def _setup_routes(self):
|
|
29
|
+
"""Setup dashboard routes."""
|
|
30
|
+
|
|
31
|
+
@self.app.get("/", response_class=HTMLResponse)
|
|
32
|
+
async def dashboard_home():
|
|
33
|
+
"""Main dashboard page."""
|
|
34
|
+
return self._get_dashboard_html()
|
|
35
|
+
|
|
36
|
+
@self.app.get("/api/metrics")
|
|
37
|
+
async def get_metrics():
|
|
38
|
+
"""Get current server metrics."""
|
|
39
|
+
return self.analytics.get_server_metrics()
|
|
40
|
+
|
|
41
|
+
@self.app.get("/api/analytics")
|
|
42
|
+
async def get_analytics(hours: int = 24):
|
|
43
|
+
"""Get analytics summary."""
|
|
44
|
+
return self.analytics.get_analytics_summary(hours)
|
|
45
|
+
|
|
46
|
+
@self.app.get("/api/endpoints")
|
|
47
|
+
async def get_endpoints():
|
|
48
|
+
"""Get endpoint statistics."""
|
|
49
|
+
summary = self.analytics.get_analytics_summary(24)
|
|
50
|
+
return {
|
|
51
|
+
"popular_endpoints": summary["popular_endpoints"],
|
|
52
|
+
"slowest_endpoints": summary["slowest_endpoints"]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@self.app.get("/api/requests")
|
|
56
|
+
async def get_recent_requests(limit: int = 50):
|
|
57
|
+
"""Get recent requests."""
|
|
58
|
+
# TODO: Implement recent requests endpoint
|
|
59
|
+
return {"requests": []}
|
|
60
|
+
|
|
61
|
+
@self.app.websocket("/ws")
|
|
62
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
63
|
+
"""WebSocket endpoint for real-time updates."""
|
|
64
|
+
await websocket.accept()
|
|
65
|
+
self.active_connections.append(websocket)
|
|
66
|
+
try:
|
|
67
|
+
while True:
|
|
68
|
+
# Send real-time metrics every 5 seconds
|
|
69
|
+
metrics = self.analytics.get_server_metrics()
|
|
70
|
+
await websocket.send_text(json.dumps({
|
|
71
|
+
"type": "metrics",
|
|
72
|
+
"data": {
|
|
73
|
+
"uptime": metrics.uptime_seconds,
|
|
74
|
+
"total_requests": metrics.total_requests,
|
|
75
|
+
"requests_per_minute": metrics.requests_per_minute,
|
|
76
|
+
"average_response_time": metrics.average_response_time_ms,
|
|
77
|
+
"error_rate": metrics.error_rate,
|
|
78
|
+
"memory_usage": metrics.memory_usage_mb,
|
|
79
|
+
"cpu_usage": metrics.cpu_usage_percent
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
await asyncio.sleep(5)
|
|
83
|
+
except WebSocketDisconnect:
|
|
84
|
+
self.active_connections.remove(websocket)
|
|
85
|
+
|
|
86
|
+
def _get_dashboard_html(self) -> str:
|
|
87
|
+
"""Generate the dashboard HTML."""
|
|
88
|
+
return """
|
|
89
|
+
<!DOCTYPE html>
|
|
90
|
+
<html lang="en">
|
|
91
|
+
<head>
|
|
92
|
+
<meta charset="UTF-8">
|
|
93
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
94
|
+
<title>API-Mocker Dashboard</title>
|
|
95
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
96
|
+
<style>
|
|
97
|
+
body {
|
|
98
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
99
|
+
margin: 0;
|
|
100
|
+
padding: 20px;
|
|
101
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
102
|
+
min-height: 100vh;
|
|
103
|
+
}
|
|
104
|
+
.container {
|
|
105
|
+
max-width: 1200px;
|
|
106
|
+
margin: 0 auto;
|
|
107
|
+
background: white;
|
|
108
|
+
border-radius: 10px;
|
|
109
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
}
|
|
112
|
+
.header {
|
|
113
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
114
|
+
color: white;
|
|
115
|
+
padding: 20px;
|
|
116
|
+
text-align: center;
|
|
117
|
+
}
|
|
118
|
+
.header h1 {
|
|
119
|
+
margin: 0;
|
|
120
|
+
font-size: 2.5em;
|
|
121
|
+
}
|
|
122
|
+
.metrics-grid {
|
|
123
|
+
display: grid;
|
|
124
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
125
|
+
gap: 20px;
|
|
126
|
+
padding: 20px;
|
|
127
|
+
}
|
|
128
|
+
.metric-card {
|
|
129
|
+
background: #f8f9fa;
|
|
130
|
+
border-radius: 8px;
|
|
131
|
+
padding: 20px;
|
|
132
|
+
text-align: center;
|
|
133
|
+
border-left: 4px solid #667eea;
|
|
134
|
+
}
|
|
135
|
+
.metric-value {
|
|
136
|
+
font-size: 2em;
|
|
137
|
+
font-weight: bold;
|
|
138
|
+
color: #667eea;
|
|
139
|
+
}
|
|
140
|
+
.metric-label {
|
|
141
|
+
color: #6c757d;
|
|
142
|
+
margin-top: 5px;
|
|
143
|
+
}
|
|
144
|
+
.charts-section {
|
|
145
|
+
padding: 20px;
|
|
146
|
+
}
|
|
147
|
+
.chart-container {
|
|
148
|
+
background: white;
|
|
149
|
+
border-radius: 8px;
|
|
150
|
+
padding: 20px;
|
|
151
|
+
margin-bottom: 20px;
|
|
152
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
|
153
|
+
}
|
|
154
|
+
.chart-title {
|
|
155
|
+
font-size: 1.5em;
|
|
156
|
+
margin-bottom: 15px;
|
|
157
|
+
color: #333;
|
|
158
|
+
}
|
|
159
|
+
.endpoints-list {
|
|
160
|
+
background: #f8f9fa;
|
|
161
|
+
border-radius: 8px;
|
|
162
|
+
padding: 15px;
|
|
163
|
+
margin-top: 10px;
|
|
164
|
+
}
|
|
165
|
+
.endpoint-item {
|
|
166
|
+
display: flex;
|
|
167
|
+
justify-content: space-between;
|
|
168
|
+
padding: 8px 0;
|
|
169
|
+
border-bottom: 1px solid #dee2e6;
|
|
170
|
+
}
|
|
171
|
+
.endpoint-item:last-child {
|
|
172
|
+
border-bottom: none;
|
|
173
|
+
}
|
|
174
|
+
.status-indicator {
|
|
175
|
+
display: inline-block;
|
|
176
|
+
width: 10px;
|
|
177
|
+
height: 10px;
|
|
178
|
+
border-radius: 50%;
|
|
179
|
+
margin-right: 10px;
|
|
180
|
+
}
|
|
181
|
+
.status-healthy { background: #28a745; }
|
|
182
|
+
.status-warning { background: #ffc107; }
|
|
183
|
+
.status-error { background: #dc3545; }
|
|
184
|
+
</style>
|
|
185
|
+
</head>
|
|
186
|
+
<body>
|
|
187
|
+
<div class="container">
|
|
188
|
+
<div class="header">
|
|
189
|
+
<h1>🚀 API-Mocker Dashboard</h1>
|
|
190
|
+
<p>Real-time analytics and monitoring</p>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="metrics-grid" id="metrics-grid">
|
|
194
|
+
<div class="metric-card">
|
|
195
|
+
<div class="metric-value" id="uptime">--</div>
|
|
196
|
+
<div class="metric-label">Uptime</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="metric-card">
|
|
199
|
+
<div class="metric-value" id="total-requests">--</div>
|
|
200
|
+
<div class="metric-label">Total Requests</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="metric-card">
|
|
203
|
+
<div class="metric-value" id="requests-per-minute">--</div>
|
|
204
|
+
<div class="metric-label">Requests/Min</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="metric-card">
|
|
207
|
+
<div class="metric-value" id="avg-response-time">--</div>
|
|
208
|
+
<div class="metric-label">Avg Response Time (ms)</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="metric-card">
|
|
211
|
+
<div class="metric-value" id="error-rate">--</div>
|
|
212
|
+
<div class="metric-label">Error Rate (%)</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="metric-card">
|
|
215
|
+
<div class="metric-value" id="memory-usage">--</div>
|
|
216
|
+
<div class="metric-label">Memory Usage (%)</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div class="charts-section">
|
|
221
|
+
<div class="chart-container">
|
|
222
|
+
<div class="chart-title">Request Methods Distribution</div>
|
|
223
|
+
<canvas id="methods-chart" width="400" height="200"></canvas>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div class="chart-container">
|
|
227
|
+
<div class="chart-title">Status Codes Distribution</div>
|
|
228
|
+
<canvas id="status-chart" width="400" height="200"></canvas>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div class="chart-container">
|
|
232
|
+
<div class="chart-title">Most Popular Endpoints</div>
|
|
233
|
+
<div id="popular-endpoints" class="endpoints-list"></div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="chart-container">
|
|
237
|
+
<div class="chart-title">Slowest Endpoints</div>
|
|
238
|
+
<div id="slowest-endpoints" class="endpoints-list"></div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<script>
|
|
244
|
+
// Initialize charts
|
|
245
|
+
const methodsChart = new Chart(document.getElementById('methods-chart'), {
|
|
246
|
+
type: 'doughnut',
|
|
247
|
+
data: {
|
|
248
|
+
labels: [],
|
|
249
|
+
datasets: [{
|
|
250
|
+
data: [],
|
|
251
|
+
backgroundColor: [
|
|
252
|
+
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF'
|
|
253
|
+
]
|
|
254
|
+
}]
|
|
255
|
+
},
|
|
256
|
+
options: {
|
|
257
|
+
responsive: true,
|
|
258
|
+
plugins: {
|
|
259
|
+
legend: {
|
|
260
|
+
position: 'bottom'
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const statusChart = new Chart(document.getElementById('status-chart'), {
|
|
267
|
+
type: 'bar',
|
|
268
|
+
data: {
|
|
269
|
+
labels: [],
|
|
270
|
+
datasets: [{
|
|
271
|
+
label: 'Requests',
|
|
272
|
+
data: [],
|
|
273
|
+
backgroundColor: '#36A2EB'
|
|
274
|
+
}]
|
|
275
|
+
},
|
|
276
|
+
options: {
|
|
277
|
+
responsive: true,
|
|
278
|
+
scales: {
|
|
279
|
+
y: {
|
|
280
|
+
beginAtZero: true
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// WebSocket connection for real-time updates
|
|
287
|
+
const ws = new WebSocket(`ws://${window.location.host}/ws`);
|
|
288
|
+
|
|
289
|
+
ws.onmessage = function(event) {
|
|
290
|
+
const data = JSON.parse(event.data);
|
|
291
|
+
if (data.type === 'metrics') {
|
|
292
|
+
updateMetrics(data.data);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
function updateMetrics(metrics) {
|
|
297
|
+
document.getElementById('uptime').textContent = formatUptime(metrics.uptime);
|
|
298
|
+
document.getElementById('total-requests').textContent = metrics.total_requests;
|
|
299
|
+
document.getElementById('requests-per-minute').textContent = metrics.requests_per_minute.toFixed(1);
|
|
300
|
+
document.getElementById('avg-response-time').textContent = metrics.average_response_time.toFixed(1);
|
|
301
|
+
document.getElementById('error-rate').textContent = metrics.error_rate.toFixed(1);
|
|
302
|
+
document.getElementById('memory-usage').textContent = metrics.memory_usage.toFixed(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function formatUptime(seconds) {
|
|
306
|
+
const hours = Math.floor(seconds / 3600);
|
|
307
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
308
|
+
return `${hours}h ${minutes}m`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Load initial data
|
|
312
|
+
async function loadInitialData() {
|
|
313
|
+
try {
|
|
314
|
+
const [analytics, endpoints] = await Promise.all([
|
|
315
|
+
fetch('/api/analytics').then(r => r.json()),
|
|
316
|
+
fetch('/api/endpoints').then(r => r.json())
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
// Update methods chart
|
|
320
|
+
methodsChart.data.labels = Object.keys(analytics.methods);
|
|
321
|
+
methodsChart.data.datasets[0].data = Object.values(analytics.methods);
|
|
322
|
+
methodsChart.update();
|
|
323
|
+
|
|
324
|
+
// Update status chart
|
|
325
|
+
statusChart.data.labels = Object.keys(analytics.status_codes);
|
|
326
|
+
statusChart.data.datasets[0].data = Object.values(analytics.status_codes);
|
|
327
|
+
statusChart.update();
|
|
328
|
+
|
|
329
|
+
// Update endpoints lists
|
|
330
|
+
updateEndpointsList('popular-endpoints', analytics.popular_endpoints);
|
|
331
|
+
updateEndpointsList('slowest-endpoints', analytics.slowest_endpoints);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('Error loading data:', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function updateEndpointsList(elementId, endpoints) {
|
|
338
|
+
const container = document.getElementById(elementId);
|
|
339
|
+
container.innerHTML = '';
|
|
340
|
+
|
|
341
|
+
Object.entries(endpoints).forEach(([endpoint, value]) => {
|
|
342
|
+
const item = document.createElement('div');
|
|
343
|
+
item.className = 'endpoint-item';
|
|
344
|
+
item.innerHTML = `
|
|
345
|
+
<span>${endpoint}</span>
|
|
346
|
+
<span>${typeof value === 'number' ? value.toFixed(2) : value}</span>
|
|
347
|
+
`;
|
|
348
|
+
container.appendChild(item);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Load data on page load
|
|
353
|
+
loadInitialData();
|
|
354
|
+
|
|
355
|
+
// Refresh data every 30 seconds
|
|
356
|
+
setInterval(loadInitialData, 30000);
|
|
357
|
+
</script>
|
|
358
|
+
</body>
|
|
359
|
+
</html>
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def start(self, host: str = "127.0.0.1"):
|
|
363
|
+
"""Start the dashboard server."""
|
|
364
|
+
uvicorn.run(self.app, host=host, port=self.port)
|
|
365
|
+
|
|
366
|
+
async def broadcast_metrics(self):
|
|
367
|
+
"""Broadcast metrics to all connected WebSocket clients."""
|
|
368
|
+
metrics = self.analytics.get_server_metrics()
|
|
369
|
+
message = {
|
|
370
|
+
"type": "metrics",
|
|
371
|
+
"data": {
|
|
372
|
+
"uptime": metrics.uptime_seconds,
|
|
373
|
+
"total_requests": metrics.total_requests,
|
|
374
|
+
"requests_per_minute": metrics.requests_per_minute,
|
|
375
|
+
"average_response_time": metrics.average_response_time_ms,
|
|
376
|
+
"error_rate": metrics.error_rate,
|
|
377
|
+
"memory_usage": metrics.memory_usage_mb,
|
|
378
|
+
"cpu_usage": metrics.cpu_usage_percent
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for connection in self.active_connections:
|
|
383
|
+
try:
|
|
384
|
+
await connection.send_text(json.dumps(message))
|
|
385
|
+
except:
|
|
386
|
+
self.active_connections.remove(connection)
|