@@ -67,9 +67,9 @@ PulseApp.ui.dashboard = (() => {
67
67
}
68
68
69
69
// Initialize charts toggle
70
- const chartsToggleButton = document . getElementById ( 'toggle-charts-button ' ) ;
71
- if ( chartsToggleButton ) {
72
- chartsToggleButton . addEventListener ( 'click ' , toggleChartsMode ) ;
70
+ const chartsToggleCheckbox = document . getElementById ( 'toggle-charts-checkbox ' ) ;
71
+ if ( chartsToggleCheckbox ) {
72
+ chartsToggleCheckbox . addEventListener ( 'change ' , toggleChartsMode ) ;
73
73
}
74
74
75
75
// Initialize mobile scroll indicators
@@ -524,18 +524,21 @@ PulseApp.ui.dashboard = (() => {
524
524
} else {
525
525
row . className = 'border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50' ;
526
526
}
527
-
527
+
528
528
// Update specific cells that might have changed
529
529
const cells = row . querySelectorAll ( 'td' ) ;
530
+
531
+ // Ensure name cell keeps sticky styling even after row class updates
532
+ if ( cells [ 0 ] ) {
533
+ cells [ 0 ] . className = 'sticky left-0 bg-white dark:bg-gray-800 z-10 p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-0' ;
534
+ }
530
535
if ( cells . length >= 10 ) {
531
536
// Cell order: name(0), type(1), id(2), uptime(3), cpu(4), memory(5), disk(6), diskread(7), diskwrite(8), netin(9), netout(10)
532
537
533
- // Update name (cell 0)
534
- const nameCell = cells [ 0 ] ;
535
-
536
- if ( nameCell . textContent !== guest . name ) {
537
- nameCell . textContent = guest . name ;
538
- nameCell . title = guest . name ;
538
+ // Update name (cell 0) content only (styling handled above)
539
+ if ( cells [ 0 ] . textContent !== guest . name ) {
540
+ cells [ 0 ] . textContent = guest . name ;
541
+ cells [ 0 ] . title = guest . name ;
539
542
}
540
543
541
544
// Ensure ID cell (2) has proper classes
@@ -682,6 +685,11 @@ PulseApp.ui.dashboard = (() => {
682
685
return ;
683
686
}
684
687
688
+ // Find the scrollable container
689
+ const scrollableContainer = PulseApp . utils . getScrollableParent ( tableBodyEl ) ||
690
+ document . querySelector ( '.table-container' ) ||
691
+ tableBodyEl . closest ( '.overflow-x-auto' ) ;
692
+
685
693
// Show loading skeleton if no data yet
686
694
const currentData = PulseApp . state . get ( 'dashboardData' ) ;
687
695
if ( ! currentData || currentData . length === 0 ) {
@@ -746,50 +754,56 @@ PulseApp.ui.dashboard = (() => {
746
754
visibleCount = sortedData . length ;
747
755
sortedData . forEach ( guest => visibleNodes . add ( ( guest . node || 'Unknown Node' ) . toLowerCase ( ) ) ) ;
748
756
} else if ( needsFullRebuild ) {
749
- // Full rebuild for normal rendering
750
- if ( groupByNode ) {
751
- const groupRenderResult = _renderGroupedByNode ( tableBodyEl , sortedData , createGuestRow ) ;
752
- visibleCount = groupRenderResult . visibleCount ;
753
- visibleNodes = groupRenderResult . visibleNodes ;
754
- } else {
755
- PulseApp . utils . renderTableBody ( tableBodyEl , sortedData , createGuestRow , "No matching guests found." , 11 ) ;
756
- visibleCount = sortedData . length ;
757
- sortedData . forEach ( guest => visibleNodes . add ( ( guest . node || 'Unknown Node' ) . toLowerCase ( ) ) ) ;
758
- }
757
+ // Full rebuild for normal rendering with scroll preservation
758
+ PulseApp . utils . preserveScrollPosition ( scrollableContainer , ( ) => {
759
+ if ( groupByNode ) {
760
+ const groupRenderResult = _renderGroupedByNode ( tableBodyEl , sortedData , createGuestRow ) ;
761
+ visibleCount = groupRenderResult . visibleCount ;
762
+ visibleNodes = groupRenderResult . visibleNodes ;
763
+ } else {
764
+ PulseApp . utils . renderTableBody ( tableBodyEl , sortedData , createGuestRow , "No matching guests found." , 11 ) ;
765
+ visibleCount = sortedData . length ;
766
+ sortedData . forEach ( guest => visibleNodes . add ( ( guest . node || 'Unknown Node' ) . toLowerCase ( ) ) ) ;
767
+ }
768
+ } ) ;
759
769
previousGroupByNode = groupByNode ;
760
770
} else {
761
- // Incremental update using DOM diffing
762
- const result = _updateTableIncremental ( tableBodyEl , sortedData , createGuestRow , groupByNode ) ;
763
- visibleCount = result . visibleCount ;
764
- visibleNodes = result . visibleNodes ;
771
+ // Incremental update using DOM diffing with scroll preservation
772
+ PulseApp . utils . preserveScrollPosition ( scrollableContainer , ( ) => {
773
+ const result = _updateTableIncremental ( tableBodyEl , sortedData , createGuestRow , groupByNode ) ;
774
+ visibleCount = result . visibleCount ;
775
+ visibleNodes = result . visibleNodes ;
776
+ } ) ;
765
777
}
766
778
767
779
previousTableData = sortedData ;
768
780
769
781
if ( visibleCount === 0 && tableBodyEl ) {
770
- const textSearchTerms = searchInput ? searchInput . value . toLowerCase ( ) . split ( ',' ) . map ( term => term . trim ( ) ) . filter ( term => term ) : [ ] ;
771
- const activeThresholds = Object . entries ( thresholdState ) . filter ( ( [ _ , state ] ) => state . value > 0 ) ;
772
- const thresholdTexts = activeThresholds . map ( ( [ key , state ] ) => {
773
- return `${ PulseApp . utils . getReadableThresholdName ( key ) } >=${ PulseApp . utils . formatThresholdValue ( key , state . value ) } ` ;
774
- } ) ;
775
-
776
- const hasFilters = filterGuestType !== FILTER_ALL || filterStatus !== FILTER_ALL || textSearchTerms . length > 0 || activeThresholds . length > 0 ;
777
-
778
- if ( PulseApp . ui . emptyStates ) {
779
- const context = {
780
- filterType : filterGuestType ,
781
- filterStatus : filterStatus ,
782
- searchTerms : textSearchTerms ,
783
- thresholds : thresholdTexts
784
- } ;
782
+ PulseApp . utils . preserveScrollPosition ( scrollableContainer , ( ) => {
783
+ const textSearchTerms = searchInput ? searchInput . value . toLowerCase ( ) . split ( ',' ) . map ( term => term . trim ( ) ) . filter ( term => term ) : [ ] ;
784
+ const activeThresholds = Object . entries ( thresholdState ) . filter ( ( [ _ , state ] ) => state . value > 0 ) ;
785
+ const thresholdTexts = activeThresholds . map ( ( [ key , state ] ) => {
786
+ return `${ PulseApp . utils . getReadableThresholdName ( key ) } >=${ PulseApp . utils . formatThresholdValue ( key , state . value ) } ` ;
787
+ } ) ;
785
788
786
- const emptyType = hasFilters ? 'no-results' : 'no-guests' ;
787
- tableBodyEl . innerHTML = PulseApp . ui . emptyStates . createTableEmptyState ( emptyType , context , 11 ) ;
788
- } else {
789
- // Fallback to simple message
790
- let message = hasFilters ? "No guests match the current filters." : "No guests found." ;
791
- tableBodyEl . innerHTML = `<tr><td colspan="11" class="p-4 text-center text-gray-500 dark:text-gray-400">${ message } </td></tr>` ;
792
- }
789
+ const hasFilters = filterGuestType !== FILTER_ALL || filterStatus !== FILTER_ALL || textSearchTerms . length > 0 || activeThresholds . length > 0 ;
790
+
791
+ if ( PulseApp . ui . emptyStates ) {
792
+ const context = {
793
+ filterType : filterGuestType ,
794
+ filterStatus : filterStatus ,
795
+ searchTerms : textSearchTerms ,
796
+ thresholds : thresholdTexts
797
+ } ;
798
+
799
+ const emptyType = hasFilters ? 'no-results' : 'no-guests' ;
800
+ tableBodyEl . innerHTML = PulseApp . ui . emptyStates . createTableEmptyState ( emptyType , context , 11 ) ;
801
+ } else {
802
+ // Fallback to simple message
803
+ let message = hasFilters ? "No guests match the current filters." : "No guests found." ;
804
+ tableBodyEl . innerHTML = `<tr><td colspan="11" class="p-4 text-center text-gray-500 dark:text-gray-400">${ message } </td></tr>` ;
805
+ }
806
+ } ) ;
793
807
}
794
808
795
809
_updateDashboardStatusMessage ( statusElementEl , visibleCount , visibleNodes , groupByNode , filterGuestType , filterStatus , searchInput , thresholdState ) ;
@@ -943,7 +957,7 @@ PulseApp.ui.dashboard = (() => {
943
957
}
944
958
945
959
row . innerHTML = `
946
- <td class="p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-0" title="${ guest . name } ">${ guest . name } </td>
960
+ <td class="sticky left-0 bg-white dark:bg-gray-800 z-10 p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-0" title="${ guest . name } ">${ guest . name } </td>
947
961
<td class="p-1 px-2">${ typeIcon } </td>
948
962
<td class="p-1 px-2">${ guest . id } </td>
949
963
<td class="p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis">${ uptimeDisplay } </td>
@@ -980,23 +994,24 @@ PulseApp.ui.dashboard = (() => {
980
994
981
995
function toggleChartsMode ( ) {
982
996
const mainContainer = document . getElementById ( 'main' ) ;
983
- const button = document . getElementById ( 'toggle-charts-button' ) ;
997
+ const checkbox = document . getElementById ( 'toggle-charts-checkbox' ) ;
998
+ const label = checkbox ? checkbox . parentElement : null ;
984
999
985
- if ( mainContainer . classList . contains ( 'charts-mode' ) ) {
986
- // Switch to metrics mode
987
- mainContainer . classList . remove ( 'charts-mode' ) ;
988
- button . title = 'Toggle Charts View' ;
989
- } else {
1000
+ if ( checkbox && checkbox . checked ) {
990
1001
// Switch to charts mode
991
1002
mainContainer . classList . add ( 'charts-mode' ) ;
992
- button . title = 'Toggle Metrics View' ;
1003
+ if ( label ) label . title = 'Toggle Metrics View' ;
993
1004
994
1005
// Immediately render charts when switching to charts mode
995
1006
if ( PulseApp . charts ) {
996
1007
requestAnimationFrame ( ( ) => {
997
1008
PulseApp . charts . updateAllCharts ( ) ;
998
1009
} ) ;
999
1010
}
1011
+ } else {
1012
+ // Switch to metrics mode
1013
+ mainContainer . classList . remove ( 'charts-mode' ) ;
1014
+ if ( label ) label . title = 'Toggle Charts View' ;
1000
1015
}
1001
1016
}
1002
1017
0 commit comments