@@ -262,24 +262,16 @@ export default function Dashboard({ status: serverStatus, onRefresh }) {
262262 }
263263
264264 // Polling Fallback for Logs (If WS is dead/unstable)
265- if ( serverStatus . recent_logs && Array . isArray ( serverStatus . recent_logs ) && serverStatus . recent_logs . length > 0 ) {
265+ const isWsConnected = ws . current && ws . current . readyState === WebSocket . OPEN ;
266+ if ( ! isWsConnected && serverStatus . recent_logs && Array . isArray ( serverStatus . recent_logs ) && serverStatus . recent_logs . length > 0 ) {
266267 setLocalLogs ( prev => {
267- // Update if local is empty
268- if ( prev . length === 0 ) return serverStatus . recent_logs . slice ( - 100 ) ;
269-
270- // If the last log from polling isn't in our local logs (or differs from our last log),
271- // it means we missed something or are out of sync.
268+ if ( prev . length === 0 ) return serverStatus . recent_logs . slice ( - 50 ) ;
272269 const lastPoll = serverStatus . recent_logs [ serverStatus . recent_logs . length - 1 ] ;
273270 const lastLocal = prev [ prev . length - 1 ] ;
274271
275272 if ( lastPoll && ( ! lastLocal || lastPoll . message !== lastLocal . message ) ) {
276- // Check if it's really new (isn't already in the last few local logs)
277- const isRepetition = prev . slice ( - 5 ) . some ( l => l . message === lastPoll . message && l . time === lastPoll . time ) ;
278- if ( ! isRepetition ) {
279- return serverStatus . recent_logs . slice ( - 100 ) ;
280- }
273+ return serverStatus . recent_logs . slice ( - 50 ) ;
281274 }
282-
283275 return prev ;
284276 } ) ;
285277 }
@@ -323,96 +315,109 @@ export default function Dashboard({ status: serverStatus, onRefresh }) {
323315
324316 // --- WebSocket for Real-time Logs & Status Updates ---
325317 useEffect ( ( ) => {
326- // Clear previous logs on mount or server switch
327- setLocalLogs ( [ ] ) ;
318+ let timeoutId ;
319+ let isMounted = true ;
328320
329- ws . current = new WebSocket ( 'ws://127.0.0.1:8000/ws/console' ) ;
321+ const connect = ( ) => {
322+ if ( ws . current ) {
323+ ws . current . onclose = null ;
324+ ws . current . close ( ) ;
325+ }
330326
331- ws . current . onopen = ( ) => {
332- // console.log("Dashboard WS Connected");
333- } ;
327+ ws . current = new WebSocket ( 'ws://127.0.0.1:8000/ws/console' ) ;
334328
335- ws . current . onmessage = ( event ) => {
336- try {
337- const data = JSON . parse ( event . data ) ;
338-
339- const processItem = ( item , isHistory = false ) => {
340- // 1. Handle explicit status change events from Backend (Fixes stuck on Stopping)
341- if ( item . type === 'status_change' ) {
342- setLocalStatus ( item . status ) ;
343- setLoading ( false ) ;
344- if ( item . status === 'offline' ) {
345- isStoppingRef . current = false ;
346- if ( onRefresh ) onRefresh ( ) ;
347- } else if ( item . status === 'online' ) {
348- if ( onRefresh ) onRefresh ( ) ;
349- }
350- return ;
351- }
329+ ws . current . onopen = ( ) => { } ;
352330
353- // 2. Handle Tunnel Events
354- if ( item . type === 'tunnel_connected' ) {
355- setTunnelAddress ( item . address ) ;
356- setTunnelConnecting ( false ) ;
357- return ;
358- }
359- if ( item . type === 'tunnel_disconnected' ) {
360- setTunnelAddress ( null ) ;
361- setTunnelConnecting ( false ) ;
362- return ;
363- }
331+ ws . current . onmessage = ( event ) => {
332+ if ( ! isMounted ) return ;
333+ try {
334+ const data = JSON . parse ( event . data ) ;
364335
365- // Logs - ensure message is a string
366- if ( item . message !== undefined || item . level ) {
367- const msgText = typeof item . message === 'string' ? item . message : JSON . stringify ( item . message || '' ) ;
368-
369- setLocalLogs ( prev => {
370- // OPTIMIZACIÓN: Reducir buffer visual en Dashboard
371- // En PCs lentos, renderizar 100 elementos complejos cuesta mucho.
372- // Bajamos a 50 para la vista rápida (mini consola).
373- const newLogs = [ ...prev , { ...item , message : msgText } ] ;
374- if ( newLogs . length > 50 ) {
375- return newLogs . slice ( newLogs . length - 50 ) ;
336+ const processItem = ( item , isHistory = false ) => {
337+ if ( item . type === 'status_change' ) {
338+ setLocalStatus ( item . status ) ;
339+ setLoading ( false ) ;
340+ if ( item . status === 'offline' ) {
341+ isStoppingRef . current = false ;
342+ if ( onRefresh ) onRefresh ( ) ;
343+ } else if ( item . status === 'online' ) {
344+ if ( onRefresh ) onRefresh ( ) ;
376345 }
377- return newLogs ;
378- } ) ;
346+ return ;
347+ }
379348
380- // Fallback: Detect status from log text (Skip for historical logs)
381- if ( isHistory ) return ;
349+ if ( item . type === 'tunnel_connected' ) {
350+ setTunnelAddress ( item . address ) ;
351+ setTunnelConnecting ( false ) ;
352+ return ;
353+ }
354+ if ( item . type === 'tunnel_disconnected' ) {
355+ setTunnelAddress ( null ) ;
356+ setTunnelConnecting ( false ) ;
357+ return ;
358+ }
382359
383- const msg = msgText . toString ( ) ;
384- if ( msg . includes ( "Done" ) && msg . includes ( "For help" ) ) {
385- setLocalStatus ( 'online' ) ;
386- setLoading ( false ) ;
387- isStoppingRef . current = false ;
388- if ( onRefresh ) onRefresh ( ) ;
389- } else if ( msg . includes ( "Stopping server" ) || msg . includes ( "Stopping the server" ) ) {
390- setLocalStatus ( 'stopping' ) ;
391- isStoppingRef . current = true ;
360+ if ( item . message !== undefined || item . level ) {
361+ const msgText = typeof item . message === 'string' ? item . message : JSON . stringify ( item . message || '' ) ;
362+
363+ setLocalLogs ( prev => {
364+ const newLogs = [ ...prev , { ...item , message : msgText } ] ;
365+ return newLogs . length > 50 ? newLogs . slice ( newLogs . length - 50 ) : newLogs ;
366+ } ) ;
367+
368+ if ( isHistory ) return ;
369+
370+ const msg = msgText . toString ( ) ;
371+ if ( msg . includes ( "Done" ) && msg . includes ( "For help" ) ) {
372+ setLocalStatus ( 'online' ) ;
373+ setLoading ( false ) ;
374+ isStoppingRef . current = false ;
375+ if ( onRefresh ) onRefresh ( ) ;
376+ } else if ( msg . includes ( "Stopping server" ) || msg . includes ( "Stopping the server" ) ) {
377+ setLocalStatus ( 'stopping' ) ;
378+ isStoppingRef . current = true ;
379+ }
392380 }
393- }
394- } ;
395-
396- if ( data . type === 'batch' && Array . isArray ( data . items ) ) {
397- const logsBatch = data . items . filter ( i => i . message !== undefined || i . level ) . map ( i => {
398- return { ...i , message : typeof i . message === 'string' ? i . message : JSON . stringify ( i . message || '' ) } ;
399- } ) ;
400- if ( logsBatch . length > 0 ) {
401- setLocalLogs ( prev => {
402- const newLogs = [ ...prev , ...logsBatch ] ;
403- return newLogs . length > 50 ? newLogs . slice ( - 50 ) : newLogs ;
381+ } ;
382+
383+ if ( data . type === 'batch' && Array . isArray ( data . items ) ) {
384+ const logsBatch = data . items . filter ( i => i . message !== undefined || i . level ) . map ( i => {
385+ return { ...i , message : typeof i . message === 'string' ? i . message : JSON . stringify ( i . message || '' ) } ;
404386 } ) ;
387+ if ( logsBatch . length > 0 ) {
388+ setLocalLogs ( prev => {
389+ const newLogs = [ ...prev , ...logsBatch ] ;
390+ return newLogs . length > 50 ? newLogs . slice ( - 50 ) : newLogs ;
391+ } ) ;
392+ }
393+ } else {
394+ processItem ( data , false ) ;
405395 }
406- } else {
407- processItem ( data , false ) ;
396+ } catch ( e ) { }
397+ } ;
398+
399+ ws . current . onclose = ( ) => {
400+ if ( isMounted ) {
401+ timeoutId = setTimeout ( connect , 3000 ) ;
408402 }
409- } catch ( e ) { }
403+ } ;
404+
405+ ws . current . onerror = ( err ) => {
406+ console . error ( 'WS Error:' , err ) ;
407+ } ;
410408 } ;
411409
410+ connect ( ) ;
411+
412412 return ( ) => {
413- if ( ws . current ) ws . current . close ( ) ;
413+ isMounted = false ;
414+ clearTimeout ( timeoutId ) ;
415+ if ( ws . current ) {
416+ ws . current . onclose = null ;
417+ ws . current . close ( ) ;
418+ }
414419 } ;
415- } , [ ] ) ; // Empty dependency array ensures this runs once per mount (which happens on server switch due to App.jsx key)
420+ } , [ ] ) ;
416421
417422 // Robust Auto-scroll logs
418423 useEffect ( ( ) => {
0 commit comments