@@ -499,8 +499,13 @@ function dashboardPage() {
499499 _charts : { } ,
500500
501501 async init ( ) {
502- await Promise . all ( [ this . loadStats ( ) , this . loadServices ( ) , this . loadSslAlerts ( ) ] ) ;
503- this . loading = false ;
502+ try {
503+ await Promise . all ( [ this . loadStats ( ) , this . loadServices ( ) , this . loadSslAlerts ( ) ] ) ;
504+ } catch ( e ) {
505+ console . warn ( '[VortexPanel] Dashboard init had an error, showing what loaded so far:' , e ) ;
506+ } finally {
507+ this . loading = false ;
508+ }
504509 this . _waitForChartJs ( ) ;
505510 setInterval ( ( ) => this . loadStats ( ) , 5000 ) ;
506511 setInterval ( ( ) => this . loadSslAlerts ( ) , 60000 ) ; // recheck every minute, not every 5s
@@ -512,12 +517,12 @@ function dashboardPage() {
512517 if ( r . ok ) this . sslAlerts = r . alerts || [ ] ;
513518 } ,
514519 async loadStats ( ) {
515- const r = await get ( '/api/dashboard/stats' ) ;
520+ const r = await get ( '/api/dashboard/stats' ) . catch ( ( ) => ( { ok : false } ) ) ;
516521 if ( r . ok ) { this . stats = r ; this . _pushHistory ( r ) ; }
517522 } ,
518523 async loadServices ( ) {
519- const r = await get ( '/api/services' ) ;
520- if ( r . ok ) this . services = r . services . slice ( 0 , 8 ) ;
524+ const r = await get ( '/api/services' ) . catch ( ( ) => ( { ok : false } ) ) ;
525+ if ( r . ok ) this . services = ( r . services || [ ] ) . slice ( 0 , 8 ) ;
521526 } ,
522527 go ( page ) { window . dispatchEvent ( new CustomEvent ( 'nav' , { detail :{ page} } ) ) ; } ,
523528 ramPct ( ) { const r = this . stats . ram ; return ( r && r . total ) ? Math . round ( r . used / r . total * 100 ) : 0 ; } ,
@@ -583,9 +588,9 @@ function dashboardPage() {
583588 if ( cpuRamEl && ! this . _charts . cpuram ) {
584589 this . _charts . cpuram = new Chart ( cpuRamEl , {
585590 type : 'line' ,
586- data : { labels : this . _hist . labels , datasets : [
587- { label :'CPU %' , data :this . _hist . cpu , borderColor :c . cpu , backgroundColor :c . cpu + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
588- { label :'RAM %' , data :this . _hist . ram , borderColor :c . ram , backgroundColor :c . ram + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
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 } ,
589594 ] } ,
590595 options : baseOpts ( 100 , '%' ) ,
591596 } ) ;
@@ -595,9 +600,9 @@ function dashboardPage() {
595600 if ( netEl && ! this . _charts . net ) {
596601 this . _charts . net = new Chart ( netEl , {
597602 type : 'line' ,
598- data : { labels : this . _hist . labels , datasets : [
599- { label :'↓ In' , data :this . _hist . netRx , borderColor :c . rx , backgroundColor :c . rx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
600- { label :'↑ Out' , data :this . _hist . netTx , borderColor :c . tx , backgroundColor :c . tx + '22' , fill :true , tension :.35 , pointRadius :0 , borderWidth :2 } ,
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 } ,
601606 ] } ,
602607 options : { ...baseOpts ( undefined , '' ) ,
603608 scales : { ...baseOpts ( undefined , '' ) . scales ,
@@ -608,14 +613,21 @@ function dashboardPage() {
608613
609614 _updateCharts ( ) {
610615 if ( ! this . _charts . cpuram ) { this . _initCharts ( ) ; if ( ! this . _charts . cpuram ) return ; }
611- this . _charts . cpuram . data . labels = this . _hist . labels ;
612- this . _charts . cpuram . data . datasets [ 0 ] . data = this . _hist . cpu ;
613- this . _charts . cpuram . data . datasets [ 1 ] . data = this . _hist . ram ;
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 ) ;
614626 this . _charts . cpuram . update ( 'none' ) ;
615627 if ( this . _charts . net ) {
616- this . _charts . net . data . labels = this . _hist . labels ;
617- this . _charts . net . data . datasets [ 0 ] . data = this . _hist . netRx ;
618- this . _charts . net . data . datasets [ 1 ] . data = this . _hist . netTx ;
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 ) ;
619631 this . _charts . net . update ( 'none' ) ;
620632 }
621633 } ,
@@ -646,6 +658,14 @@ function websitesPage() {
646658 integrityStatus :{ enabled :false , created :'' , file_count :0 } ,
647659 diskUsage :{ loading :false , size_human :'' , size_bytes :0 , file_count :0 , dir_count :0 } ,
648660 integrityScanning :false , integrityResult :null , integrityLoading :false ,
661+ // directory.path has a real backend endpoint (GET/PUT /api/websites/<domain>/directory)
662+ // and working load code that was missing this object to assign into.
663+ // antixss/accesslog have NO backend support anywhere in the codebase --
664+ // kept here only so the checkboxes don't throw console errors; they are
665+ // not wired to anything real yet and need actual backend work.
666+ directory : { path :'' , antixss :false , accesslog :false } ,
667+ hotlink : { enabled :false , suffixes :'jpg,jpeg,gif,png,js,css' , domains :'' , response :'404' } ,
668+ limitRules : [ ] , limitForm : { name :'' , path :'/admin' , user :'' , pass :'' } ,
649669 } ,
650670 drawerTabs :[
651671 { id :'domains' , label :'🌐 Domain Manager' } ,
@@ -716,6 +736,9 @@ function websitesPage() {
716736 nodejsEnabled :false , nodejsForm :{ app_path :s . path || '' , startup :'index.js' , port :'3000' , runtime :'node' } ,
717737 maintEnabled :false , maintMessage :'We are performing scheduled maintenance.' ,
718738 sslEmail :'' , sslKey :'' , sslCert :'' , sslOutput :'' , sslInfo :'' ,
739+ directory : { path :'' , antixss :false , accesslog :false } ,
740+ hotlink : { enabled :false , suffixes :'jpg,jpeg,gif,png,js,css' , domains :'' , response :'404' } ,
741+ limitRules : [ ] , limitForm : { name :'' , path :'/admin' , user :'' , pass :'' } ,
719742 } ;
720743 this . refreshPhpVersions ( ) ;
721744 this . loadDrawerTab ( ) ;
@@ -754,7 +777,7 @@ function websitesPage() {
754777 else if ( d . tab === 'domains' ) { const r = await get ( '/api/websites/' + domain + '/domains' ) ; if ( r . ok ) d . domains = r . domains || [ ] ; }
755778 else if ( d . tab === 'directory' ) { const r = await get ( '/api/websites/' + domain + '/directory' ) ; if ( r . ok ) d . directory . path = r . path || d . site ?. path || '' ; this . loadDiskUsage ( ) ; }
756779 else if ( d . tab === 'rewrite' ) { const r = await get ( '/api/websites/' + domain + '/rewrite' ) ; if ( r . ok ) d . rewriteContent = r . content || '' ; const rt = await get ( '/api/websites/' + domain + '/rewrite/templates' ) ; if ( rt . ok ) d . rewriteTemplates = rt . templates || [ ] ; }
757- else if ( d . tab === 'hotlink' ) { const r = await get ( '/api/websites/' + domain + '/hotlink' ) ; if ( r . ok ) { d . hotlink . enabled = r . enabled || false ; d . hotlink . suffixes = r . suffixes || 'jpg,jpeg,gif,png,js,css' ; d . hotlink . domains = r . domains || '' ; d . hotlink . response = r . response || '404' ; } }
780+ else if ( d . tab === 'hotlink' ) { const r = await get ( '/api/websites/' + domain + '/hotlink' ) ; if ( r . ok ) { d . hotlink . enabled = r . enabled || false ; d . hotlink . suffixes = r . suffixes || 'jpg,jpeg,gif,png,js,css' ; d . hotlink . domains = r . access_domain || '' ; d . hotlink . response = r . response || '404' ; } }
758781 else if ( d . tab === 'limit' ) { const r = await get ( '/api/websites/' + domain + '/limit-access' ) ; if ( r . ok ) d . limitRules = r . rules || [ ] ; }
759782 else if ( d . tab === 'logs' ) { const r = await get ( '/api/websites/' + domain + '/logs' ) ; if ( r . ok ) { d . accessLog = r . access || 'No access logs' ; d . errorLog = r . error || 'No error logs' ; } }
760783 else if ( d . tab === 'integrity' ) { await this . loadIntegrityStatus ( ) ; }
@@ -1920,6 +1943,7 @@ function filesPage() {
19201943function aiAssistant ( ) {
19211944 return {
19221945 open : false ,
1946+ loggedIn : false ,
19231947 configured : false ,
19241948 modelName : 'NeonCodex' ,
19251949 messages : [ ] , // [{role:'user'|'assistant', content}]
@@ -1946,6 +1970,10 @@ function aiAssistant() {
19461970 ] ,
19471971
19481972 async init ( ) {
1973+ const chk = await get ( '/api/auth/check' ) . catch ( ( ) => ( { ok :false } ) ) ;
1974+ this . loggedIn = ! ! ( chk . ok && chk . logged_in ) ;
1975+ document . addEventListener ( 'vortex-logged-in' , ( ) => { this . loggedIn = true ; } ) ;
1976+ document . addEventListener ( 'vortex-logged-out' , ( ) => { this . loggedIn = false ; } ) ;
19491977 // Listen for sidebar button toggle
19501978 document . addEventListener ( 'vortex-toggle-ai' , ( ) => {
19511979 this . open = ! this . open ;
@@ -2899,6 +2927,7 @@ function backupsPage() {
28992927 restoreModal : { show :false , name :'' , type :'' , target :'' , customPath :'' } ,
29002928 showUpload : false , uploadFile :null , uploadType :'website' , uploadTarget :'' ,
29012929 uploading : false ,
2930+ form : { website :{ domain :'' } , db :{ name :'' } } ,
29022931
29032932 async init ( ) { await Promise . all ( [ this . load ( ) , this . loadInfo ( ) ] ) ; document . addEventListener ( "vortex-logged-in" , ( ) => { this . init ( ) ; } ) ; window . addEventListener ( "vp:page" , ( e ) => { if ( e . detail === "backups" ) this . load ( ) ; } ) ; } ,
29042933
@@ -3532,9 +3561,12 @@ function securityPage() {
35323561 health : { config :{ enabled :false , protocol :'http' , check_path :'/health' , interval_seconds :10 ,
35333562 timeout_seconds :3 , unhealthy_threshold :3 , healthy_threshold :2 , servers :[ ] } ,
35343563 state :{ } , service_active :false , log :'' } ,
3564+ sites : [ ] ,
35353565
35363566 async init ( ) {
35373567 if ( window . __vpPendingSecurityTab ) { this . tab = window . __vpPendingSecurityTab ; window . __vpPendingSecurityTab = null ; }
3568+ const sr = await get ( '/api/websites' ) . catch ( ( ) => ( { ok :false } ) ) ;
3569+ if ( sr . ok ) this . sites = sr . sites || [ ] ;
35383570 await Promise . all ( [ this . loadScore ( ) , this . loadSSH ( ) ] ) ; document . addEventListener ( "vortex-logged-in" , ( ) => { this . init ( ) ; } ) ; window . addEventListener ( "vp:page" , ( e ) => { if ( e . detail === "security" ) { this . loadScore ( ) ; this . loadSSH ( ) ; } } ) ;
35393571 window . addEventListener ( 'vp:module-changed' , ( e ) => {
35403572 if ( e . detail ?. id === 'modsecurity' ) this . loadModsec ( ) ;
0 commit comments