@@ -99,6 +99,21 @@ document.addEventListener('keydown', (e) => {
9999 }
100100} ) ;
101101
102+ const formatBandwidth = ( bytes , short = false ) => {
103+ const kb = bytes / 1024 ;
104+ const mb = kb / 1024 ;
105+ const gb = mb / 1024 ;
106+ const space = short ? '' : ' ' ;
107+
108+ if ( gb >= 1 ) {
109+ return gb . toFixed ( short ? 1 : 2 ) + space + 'GB' ;
110+ } else if ( mb >= 1 ) {
111+ return mb . toFixed ( short ? 1 : 2 ) + space + 'MB' ;
112+ } else {
113+ return kb . toFixed ( short ? 0 : 1 ) + space + 'KB' ;
114+ }
115+ } ;
116+
102117const evtSource = new EventSource ( "/events" ) ;
103118
104119evtSource . onmessage = ( event ) => {
@@ -117,21 +132,7 @@ evtSource.onmessage = (event) => {
117132
118133 if ( data . diagnostics ) {
119134 const d = data . diagnostics ;
120-
121- const formatBandwidth = ( bytes ) => {
122- const kb = bytes / 1024 ;
123- const mb = kb / 1024 ;
124- const gb = mb / 1024 ;
125-
126- if ( gb >= 1 ) {
127- return gb . toFixed ( 2 ) + ' GB' ;
128- } else if ( mb >= 1 ) {
129- return mb . toFixed ( 2 ) + ' MB' ;
130- } else {
131- return kb . toFixed ( 1 ) + ' KB' ;
132- }
133- } ;
134-
135+
135136 document . getElementById ( 'diag-heartbeats-rx' ) . innerText = d . heartbeatsReceived . toLocaleString ( ) ;
136137 document . getElementById ( 'diag-heartbeats-tx' ) . innerText = d . heartbeatsRelayed . toLocaleString ( ) ;
137138 document . getElementById ( 'diag-new-peers' ) . innerText = d . newPeersAdded . toLocaleString ( ) ;
@@ -141,6 +142,12 @@ evtSource.onmessage = (event) => {
141142 document . getElementById ( 'diag-bandwidth-in' ) . innerText = formatBandwidth ( d . bytesReceived ) ;
142143 document . getElementById ( 'diag-bandwidth-out' ) . innerText = formatBandwidth ( d . bytesRelayed ) ;
143144 document . getElementById ( 'diag-leave' ) . innerText = d . leaveMessages . toLocaleString ( ) ;
145+
146+ addBandwidthData ( d . bytesReceived , d . bytesRelayed ) ;
147+ drawBandwidthGraph ( ) ;
148+
149+ document . getElementById ( 'current-in' ) . innerText = formatBandwidth ( d . bytesReceived ) ;
150+ document . getElementById ( 'current-out' ) . innerText = formatBandwidth ( d . bytesRelayed ) ;
144151 }
145152} ;
146153
@@ -154,3 +161,111 @@ countEl.classList.add('loaded');
154161updateParticles ( initialCount ) ;
155162animate ( ) ;
156163
164+ const bandwidthHistory = { timestamps : [ ] , bytesIn : [ ] , bytesOut : [ ] } ;
165+ let selectedTimeRange = 300 ;
166+ const bandwidthCanvas = document . getElementById ( 'bandwidthGraph' ) ;
167+ const bandwidthCtx = bandwidthCanvas . getContext ( '2d' ) ;
168+ const bandwidthOverlay = document . getElementById ( 'bandwidthOverlay' ) ;
169+
170+ function resizeBandwidthCanvas ( ) {
171+ const rect = bandwidthCanvas . getBoundingClientRect ( ) ;
172+ bandwidthCanvas . width = rect . width ;
173+ bandwidthCanvas . height = rect . height ;
174+ drawBandwidthGraph ( ) ;
175+ }
176+
177+ window . addEventListener ( 'resize' , resizeBandwidthCanvas ) ;
178+ setTimeout ( resizeBandwidthCanvas , 100 ) ;
179+
180+ const toggleBandwidthGraph = ( ) => {
181+ bandwidthOverlay . classList . toggle ( 'collapsed' ) ;
182+ document . querySelector ( '.bandwidth-overlay .close-btn' ) . textContent =
183+ bandwidthOverlay . classList . contains ( 'collapsed' ) ? '+' : '−' ;
184+ } ;
185+
186+ const timePills = document . querySelectorAll ( '.time-pill' ) ;
187+ timePills . forEach ( pill => {
188+ pill . addEventListener ( 'click' , ( e ) => {
189+ e . stopPropagation ( ) ;
190+ timePills . forEach ( p => p . classList . remove ( 'active' ) ) ;
191+ pill . classList . add ( 'active' ) ;
192+ const value = pill . dataset . value ;
193+ selectedTimeRange = value === 'all' ? 'all' : parseInt ( value ) ;
194+ drawBandwidthGraph ( ) ;
195+ } ) ;
196+ } ) ;
197+
198+ timePills [ 0 ] . classList . add ( 'active' ) ;
199+
200+ const addBandwidthData = ( bytesIn , bytesOut ) => {
201+ bandwidthHistory . timestamps . push ( Date . now ( ) ) ;
202+ bandwidthHistory . bytesIn . push ( bytesIn ) ;
203+ bandwidthHistory . bytesOut . push ( bytesOut ) ;
204+
205+ if ( bandwidthHistory . timestamps . length > 360 ) {
206+ bandwidthHistory . timestamps . shift ( ) ;
207+ bandwidthHistory . bytesIn . shift ( ) ;
208+ bandwidthHistory . bytesOut . shift ( ) ;
209+ }
210+ } ;
211+
212+ const getFilteredData = ( ) => {
213+ if ( selectedTimeRange === 'all' ) return bandwidthHistory ;
214+
215+ const cutoff = Date . now ( ) - ( selectedTimeRange * 1000 ) ;
216+ const startIndex = bandwidthHistory . timestamps . findIndex ( t => t >= cutoff ) ;
217+
218+ if ( startIndex === - 1 ) return bandwidthHistory ;
219+
220+ return {
221+ timestamps : bandwidthHistory . timestamps . slice ( startIndex ) ,
222+ bytesIn : bandwidthHistory . bytesIn . slice ( startIndex ) ,
223+ bytesOut : bandwidthHistory . bytesOut . slice ( startIndex )
224+ } ;
225+ } ;
226+
227+ const drawBandwidthGraph = ( ) => {
228+ const w = bandwidthCanvas . width ;
229+ const h = bandwidthCanvas . height ;
230+
231+ if ( w === 0 || h === 0 ) return ;
232+
233+ const pad = { t : 10 , r : 10 , b : 20 , l : 50 } ;
234+
235+ bandwidthCtx . clearRect ( 0 , 0 , w , h ) ;
236+
237+ const data = getFilteredData ( ) ;
238+ if ( data . timestamps . length < 2 ) return ;
239+
240+ const max = Math . max ( ...data . bytesIn , ...data . bytesOut ) ;
241+ if ( max === 0 ) return ;
242+
243+ bandwidthCtx . fillStyle = '#9ca3af' ;
244+ bandwidthCtx . font = '10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto' ;
245+ bandwidthCtx . textAlign = 'right' ;
246+ [ max , max / 2 , 0 ] . forEach ( ( val , i ) => {
247+ bandwidthCtx . fillText ( formatBandwidth ( val , true ) , pad . l - 5 , pad . t + ( ( h - pad . t - pad . b ) / 2 ) * i + 4 ) ;
248+ } ) ;
249+
250+ const drawLine = ( points , color ) => {
251+ bandwidthCtx . strokeStyle = color ;
252+ bandwidthCtx . lineWidth = 2 ;
253+ bandwidthCtx . beginPath ( ) ;
254+ points . forEach ( ( val , i ) => {
255+ const x = pad . l + ( i / ( points . length - 1 ) ) * ( w - pad . l - pad . r ) ;
256+ const y = pad . t + ( h - pad . t - pad . b ) - ( val / max ) * ( h - pad . t - pad . b ) ;
257+ i === 0 ? bandwidthCtx . moveTo ( x , y ) : bandwidthCtx . lineTo ( x , y ) ;
258+ } ) ;
259+ bandwidthCtx . stroke ( ) ;
260+
261+ bandwidthCtx . lineTo ( pad . l + ( w - pad . l - pad . r ) , pad . t + ( h - pad . t - pad . b ) ) ;
262+ bandwidthCtx . lineTo ( pad . l , pad . t + ( h - pad . t - pad . b ) ) ;
263+ bandwidthCtx . closePath ( ) ;
264+ bandwidthCtx . fillStyle = color + '33' ;
265+ bandwidthCtx . fill ( ) ;
266+ } ;
267+
268+ drawLine ( data . bytesIn , '#60a5fa' ) ;
269+ drawLine ( data . bytesOut , '#f97316' ) ;
270+ } ;
271+
0 commit comments