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