@@ -480,6 +480,16 @@ function rootApp() {
480480
481481// --- DASHBOARD ------------------------------------------------------------------
482482function dashboardPage ( ) {
483+ // Chart history lives here, OUTSIDE the object Alpine wraps in reactivity below.
484+ // Alpine.raw() was tried first to unwrap the Proxy before handing arrays to
485+ // Chart.js, but a real browser console dump confirmed recursion crashes
486+ // persisted even with genuinely fresh, cache-busted code -- meaning that
487+ // approach wasn't fully solving it. Keeping this data in a closure variable
488+ // instead means it's plain JS from the start and can never become a Proxy,
489+ // so there's nothing left for Chart.js's own internal instrumentation to
490+ // conflict with.
491+ const hist = { labels :[ ] , cpu :[ ] , ram :[ ] , netRx :[ ] , netTx :[ ] } ;
492+ const charts = { } ;
483493 return {
484494 stats :{ cpu :0 , ram :0 , disk :0 , uptime :'' , load :'' , ramTotal :'' , diskTotal :'' , network :'' } ,
485495 wsConflict :{ conflict :false , active :[ ] , message :'' } ,
@@ -493,10 +503,7 @@ function dashboardPage() {
493503 ] ,
494504 loading : true ,
495505 sslAlerts : [ ] ,
496- // Rolling client-side history for charts (last 30 polls ≈ 2.5 min at 5s interval)
497- _hist : { labels :[ ] , cpu :[ ] , ram :[ ] , netRx :[ ] , netTx :[ ] } ,
498506 _prevNet : null ,
499- _charts : { } ,
500507
501508 async init ( ) {
502509 try {
@@ -531,7 +538,7 @@ function dashboardPage() {
531538 _pushHistory ( r ) {
532539 const now = new Date ( ) ;
533540 const label = now . getHours ( ) . toString ( ) . padStart ( 2 , '0' ) + ':' + now . getMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) + ':' + now . getSeconds ( ) . toString ( ) . padStart ( 2 , '0' ) ;
534- const h = this . _hist ;
541+ const h = hist ;
535542 h . labels . push ( label ) ;
536543 h . cpu . push ( r . cpu || 0 ) ;
537544 h . ram . push ( this . ramPct ( ) ) ;
@@ -585,24 +592,24 @@ function dashboardPage() {
585592 } ) ;
586593
587594 const cpuRamEl = document . getElementById ( 'vp-chart-cpuram' ) ;
588- if ( cpuRamEl && ! this . _charts . cpuram ) {
589- this . _charts . cpuram = new Chart ( cpuRamEl , {
595+ if ( cpuRamEl && ! charts . cpuram ) {
596+ charts . cpuram = new Chart ( cpuRamEl , {
590597 type : 'line' ,
591- data : { labels : Alpine . raw ( this . _hist . labels ) , datasets : [
592- { label :'CPU %' , data :Alpine . raw ( this . _hist . cpu ) , borderColor :c . cpu , backgroundColor :c . cpu + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
593- { label :'RAM %' , data :Alpine . raw ( this . _hist . ram ) , borderColor :c . ram , backgroundColor :c . ram + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
598+ data : { labels : hist . labels , datasets : [
599+ { label :'CPU %' , data :hist . cpu , borderColor :c . cpu , backgroundColor :c . cpu + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
600+ { label :'RAM %' , data :hist . ram , borderColor :c . ram , backgroundColor :c . ram + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
594601 ] } ,
595602 options : baseOpts ( 100 , '%' ) ,
596603 } ) ;
597604 }
598605
599606 const netEl = document . getElementById ( 'vp-chart-net' ) ;
600- if ( netEl && ! this . _charts . net ) {
601- this . _charts . net = new Chart ( netEl , {
607+ if ( netEl && ! charts . net ) {
608+ charts . net = new Chart ( netEl , {
602609 type : 'line' ,
603- data : { labels : Alpine . raw ( this . _hist . labels ) , datasets : [
604- { label :'↓ In' , data :Alpine . raw ( this . _hist . netRx ) , borderColor :c . rx , backgroundColor :c . rx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
605- { label :'↑ Out' , data :Alpine . raw ( this . _hist . netTx ) , borderColor :c . tx , backgroundColor :c . tx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
610+ data : { labels : hist . labels , datasets : [
611+ { label :'↓ In' , data :hist . netRx , borderColor :c . rx , backgroundColor :c . rx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
612+ { label :'↑ Out' , data :hist . netTx , borderColor :c . tx , backgroundColor :c . tx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
606613 ] } ,
607614 options : { ...baseOpts ( undefined , '' ) ,
608615 scales : { ...baseOpts ( undefined , '' ) . scales ,
@@ -612,23 +619,23 @@ function dashboardPage() {
612619 } ,
613620
614621 _updateCharts ( ) {
615- if ( ! this . _charts . cpuram ) { this . _initCharts ( ) ; if ( ! this . _charts . cpuram ) return ; }
616- // Alpine.raw() unwraps the reactive Proxy before handing arrays to
617- // Chart.js -- confirmed via a real browser console dump that passing
618- // the raw reactive Proxy directly caused an infinite recursion crash
619- // (Chart.js's own internal property reads kept re-triggering Alpine's
620- // Proxy traps, which re-triggered Chart.js, forever). This is Alpine's
621- // documented pattern for integrating with libraries that do their own
622- // internal instrumentation on the data they're given .
623- this . _charts . cpuram . data . labels = Alpine . raw ( this . _hist . labels ) ;
624- this . _charts . cpuram . data . datasets [ 0 ] . data = Alpine . raw ( this . _hist . cpu ) ;
625- this . _charts . cpuram . data . datasets [ 1 ] . data = Alpine . raw ( this . _hist . ram ) ;
626- this . _charts . cpuram . update ( 'none' ) ;
627- if ( this . _charts . net ) {
628- this . _charts . net . data . labels = Alpine . raw ( this . _hist . labels ) ;
629- this . _charts . net . data . datasets [ 0 ] . data = Alpine . raw ( this . _hist . netRx ) ;
630- this . _charts . net . data . datasets [ 1 ] . data = Alpine . raw ( this . _hist . netTx ) ;
631- this . _charts . net . update ( 'none' ) ;
622+ if ( ! charts . cpuram ) { this . _initCharts ( ) ; if ( ! charts . cpuram ) return ; }
623+ // hist is a plain closure variable (see top of dashboardPage), never
624+ // touched by Alpine's reactivity -- confirmed via a real browser
625+ // console dump that an earlier Alpine.raw()-unwrapping approach still
626+ // caused an infinite recursion crash even with genuinely fresh code.
627+ // Keeping this data outside Alpine's reactive object entirely means
628+ // there's nothing for Chart.js's own internal instrumentation to
629+ // conflict with .
630+ charts . cpuram . data . labels = hist . labels ;
631+ charts . cpuram . data . datasets [ 0 ] . data = hist . cpu ;
632+ charts . cpuram . data . datasets [ 1 ] . data = hist . ram ;
633+ charts . cpuram . update ( 'none' ) ;
634+ if ( charts . net ) {
635+ charts . net . data . labels = hist . labels ;
636+ charts . net . data . datasets [ 0 ] . data = hist . netRx ;
637+ charts . net . data . datasets [ 1 ] . data = hist . netTx ;
638+ charts . net . update ( 'none' ) ;
632639 }
633640 } ,
634641 } ;
0 commit comments