Skip to content

Commit 5a68b7b

Browse files
committed
Add ReverseProxyMonitor
1 parent caa4099 commit 5a68b7b

File tree

3 files changed

+561
-14
lines changed

3 files changed

+561
-14
lines changed

src/main/resources/dashboard.html

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>ReverseProxy Monitor Dashboard</title>
5+
<meta charset="UTF-8">
6+
<style>
7+
body {
8+
font-family: Arial, sans-serif;
9+
margin: 20px;
10+
background-color: #f5f5f5;
11+
}
12+
13+
.container {
14+
max-width: 1200px;
15+
margin: 0 auto;
16+
}
17+
18+
.header {
19+
background: #2c3e50;
20+
color: white;
21+
padding: 20px;
22+
border-radius: 8px;
23+
margin-bottom: 20px;
24+
}
25+
26+
.stats-grid {
27+
display: grid;
28+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
29+
gap: 15px;
30+
margin-bottom: 20px;
31+
}
32+
33+
.stat-card {
34+
background: white;
35+
padding: 15px;
36+
border-radius: 8px;
37+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
38+
}
39+
40+
.stat-value {
41+
font-size: 24px;
42+
font-weight: bold;
43+
color: #3498db;
44+
}
45+
46+
.stat-label {
47+
color: #7f8c8d;
48+
font-size: 14px;
49+
}
50+
51+
.traffic-table {
52+
background: white;
53+
border-radius: 8px;
54+
overflow: hidden;
55+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
56+
}
57+
58+
.traffic-table table {
59+
width: 100%;
60+
border-collapse: collapse;
61+
}
62+
63+
.traffic-table th, .traffic-table td {
64+
padding: 12px;
65+
text-align: left;
66+
border-bottom: 1px solid #ecf0f1;
67+
}
68+
69+
.traffic-table th {
70+
background-color: #34495e;
71+
color: white;
72+
font-weight: 500;
73+
}
74+
75+
.status-ok {
76+
color: #27ae60;
77+
font-weight: bold;
78+
}
79+
80+
.status-error {
81+
color: #e74c3c;
82+
font-weight: bold;
83+
}
84+
85+
.connection-status {
86+
padding: 10px;
87+
margin: 10px 0;
88+
border-radius: 4px;
89+
}
90+
91+
.connected {
92+
background-color: #d4edda;
93+
color: #155724;
94+
border: 1px solid #c3e6cb;
95+
}
96+
97+
.disconnected {
98+
background-color: #f8d7da;
99+
color: #721c24;
100+
border: 1px solid #f5c6cb;
101+
}
102+
103+
.auto-refresh {
104+
margin: 10px 0;
105+
}
106+
</style>
107+
</head>
108+
<body>
109+
<div class="container">
110+
<div class="header">
111+
<h1>🔄 ReverseProxy Monitor</h1>
112+
<p>Real-time traffic monitoring and statistics</p>
113+
</div>
114+
115+
<div id="connection-status" class="connection-status disconnected">
116+
🔴 Disconnected from ReverseProxy
117+
</div>
118+
119+
<div class="auto-refresh">
120+
<label><input type="checkbox" id="auto-refresh" checked> Auto-refresh every 2 seconds</label>
121+
</div>
122+
123+
<div class="stats-grid">
124+
<div class="stat-card">
125+
<div class="stat-value" id="total-requests">0</div>
126+
<div class="stat-label">Total Requests</div>
127+
</div>
128+
<div class="stat-card">
129+
<div class="stat-value" id="total-responses">0</div>
130+
<div class="stat-label">Total Responses</div>
131+
</div>
132+
<div class="stat-card">
133+
<div class="stat-value" id="error-rate">0.0%</div>
134+
<div class="stat-label">Error Rate</div>
135+
</div>
136+
<div class="stat-card">
137+
<div class="stat-value" id="avg-response-time">0ms</div>
138+
<div class="stat-label">Avg Response Time</div>
139+
</div>
140+
</div>
141+
142+
<div class="traffic-table">
143+
<table>
144+
<thead>
145+
<tr>
146+
<th>Time</th>
147+
<th>Method</th>
148+
<th>Path</th>
149+
<th>Target</th>
150+
<th>Status</th>
151+
<th>Duration</th>
152+
<th>Correlation ID</th>
153+
</tr>
154+
</thead>
155+
<tbody id="traffic-tbody">
156+
<tr>
157+
<td colspan="7">Loading traffic data...</td>
158+
</tr>
159+
</tbody>
160+
</table>
161+
</div>
162+
</div>
163+
164+
<script>
165+
let ws = null;
166+
let autoRefreshEnabled = true;
167+
168+
function formatTimestamp(timestamp) {
169+
return new Date(timestamp).toLocaleTimeString();
170+
}
171+
172+
function formatDuration(ms) {
173+
return ms ? ms + 'ms' : '-';
174+
}
175+
176+
function formatStatus(status) {
177+
if (!status) return '-';
178+
const className = status >= 400 ? 'status-error' : 'status-ok';
179+
return `<span class="${className}">${status}</span>`;
180+
}
181+
182+
function updateStats(stats) {
183+
document.getElementById('total-requests').textContent = stats.totalRequests;
184+
document.getElementById('total-responses').textContent = stats.totalResponses;
185+
document.getElementById('error-rate').textContent = (stats.errorRate * 100).toFixed(1) + '%';
186+
document.getElementById('avg-response-time').textContent = Math.round(stats.avgResponseTime) + 'ms';
187+
}
188+
189+
function updateTrafficTable(traffic) {
190+
const tbody = document.getElementById('traffic-tbody');
191+
if (traffic.length === 0) {
192+
tbody.innerHTML = '<tr><td colspan="7">No traffic data available</td></tr>';
193+
return;
194+
}
195+
196+
const rows = traffic.slice(-100).reverse().map(entry => {
197+
const req = entry.request;
198+
const resp = entry.response;
199+
return `
200+
<tr>
201+
<td>${formatTimestamp(req.timestamp)}</td>
202+
<td>${req.method}</td>
203+
<td>${req.uri}</td>
204+
<td>${req.targetHost}</td>
205+
<td>${formatStatus(resp ? resp.status : null)}</td>
206+
<td>${formatDuration(entry.duration)}</td>
207+
<td>${req.correlationId}</td>
208+
</tr>
209+
`;
210+
}).join('');
211+
tbody.innerHTML = rows;
212+
}
213+
214+
function connectWebSocket() {
215+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
216+
const wsUrl = `${protocol}//${window.location.host}/api/ws`;
217+
218+
ws = new WebSocket(wsUrl);
219+
220+
ws.onopen = function () {
221+
document.getElementById('connection-status').textContent = '🟢 Connected to ReverseProxy';
222+
document.getElementById('connection-status').className = 'connection-status connected';
223+
};
224+
225+
ws.onmessage = function (event) {
226+
try {
227+
const stats = JSON.parse(event.data);
228+
updateStats(stats);
229+
} catch (e) {
230+
console.error('Error parsing WebSocket message:', e);
231+
}
232+
};
233+
234+
ws.onclose = function () {
235+
document.getElementById('connection-status').textContent = '🔴 Disconnected from ReverseProxy';
236+
document.getElementById('connection-status').className = 'connection-status disconnected';
237+
// Attempt to reconnect after 3 seconds
238+
setTimeout(connectWebSocket, 3000);
239+
};
240+
241+
ws.onerror = function (error) {
242+
console.error('WebSocket error:', error);
243+
};
244+
}
245+
246+
function loadTrafficData() {
247+
fetch('/api/traffic')
248+
.then(response => response.json())
249+
.then(updateTrafficTable)
250+
.catch(error => {
251+
console.error('Error loading traffic data:', error);
252+
document.getElementById('traffic-tbody').innerHTML =
253+
'<tr><td colspan="7">Error loading traffic data</td></tr>';
254+
});
255+
}
256+
257+
function loadStats() {
258+
fetch('/api/stats')
259+
.then(response => response.json())
260+
.then(updateStats)
261+
.catch(error => console.error('Error loading stats:', error));
262+
}
263+
264+
// Auto-refresh toggle
265+
document.getElementById('auto-refresh').addEventListener('change', function () {
266+
autoRefreshEnabled = this.checked;
267+
});
268+
269+
// Auto-refresh interval
270+
setInterval(() => {
271+
if (autoRefreshEnabled) {
272+
loadTrafficData();
273+
if (!ws || ws.readyState !== WebSocket.OPEN) {
274+
loadStats();
275+
}
276+
}
277+
}, 2000);
278+
279+
// Initial load
280+
connectWebSocket();
281+
loadTrafficData();
282+
loadStats();
283+
</script>
284+
</body>
285+
</html>

0 commit comments

Comments
 (0)