@@ -12,80 +12,116 @@ import { ContainerItem } from "@/components/container-item"
1212import ContainerCard from "@/components/container-card"
1313
1414export default function ContainersPage ( ) {
15- const { combinedData } = useWebSocket ( ) ;
16- const [ totalCpuData , setTotalCpuData ] = useState < DataPoint [ ] > ( [ { value : 0 , timestamp : Date . now ( ) - 1000 } , { value : 0 , timestamp : Date . now ( ) } ] )
17- const [ totalRamData , setTotalRamData ] = useState < DataPoint [ ] > ( [ { value : 0 , timestamp : Date . now ( ) - 1000 } , { value : 0 , timestamp : Date . now ( ) } ] )
18- const [ totalNetworkRxData , setTotalNetworkRxData ] = useState < DataPoint [ ] > ( [ { value : 0 , timestamp : Date . now ( ) - 1000 } , { value : 0 , timestamp : Date . now ( ) } ] )
19- const [ totalNetworkTxData , setTotalNetworkTxData ] = useState < DataPoint [ ] > ( [ { value : 0 , timestamp : Date . now ( ) - 1000 } , { value : 0 , timestamp : Date . now ( ) } ] )
15+ const { combinedData } = useWebSocket ( )
16+ const [ totalCpuData , setTotalCpuData ] = useState < DataPoint [ ] > ( [ ] )
17+ const [ totalRamData , setTotalRamData ] = useState < DataPoint [ ] > ( [ ] )
18+ const [ totalNetworkRxData , setTotalNetworkRxData ] = useState < DataPoint [ ] > ( [ ] )
19+ const [ totalNetworkTxData , setTotalNetworkTxData ] = useState < DataPoint [ ] > ( [ ] )
2020 const [ containers , setContainers ] = useState < Container [ ] > ( [ ] )
2121
2222 const [ totalMemory , setTotalMemory ] = useState ( 0 ) ;
2323
2424 const [ isExpanded , setIsExpanded ] = useState ( false )
2525
2626 useEffect ( ( ) => {
27- const maxPoints = 7 ; // Keep last 8 data points for the graph
28-
29- if ( combinedData ) {
30- // Sort containers: running first (by RAM usage), then others by name
31- const sortedContainers = [ ...combinedData . containers ] . sort ( ( a , b ) => {
32- // Prioritize running containers
33- const aIsRunning = a . status === "running" ;
34- const bIsRunning = b . status === "running" ;
35-
36- if ( aIsRunning && ! bIsRunning ) return - 1 ; // a comes before b
37- if ( ! aIsRunning && bIsRunning ) return 1 ; // b comes before a
38-
39- // If both are running, sort by RAM usage (descending), then by ID for stability
40- if ( aIsRunning && bIsRunning ) {
41- const aRam = a . ramUsage && a . ramUsage . length > 0 ? a . ramUsage [ a . ramUsage . length - 1 ] . value : 0 ;
42- const bRam = b . ramUsage && b . ramUsage . length > 0 ? b . ramUsage [ b . ramUsage . length - 1 ] . value : 0 ;
43- if ( aRam !== bRam ) {
44- return bRam - aRam ;
45- }
46- return a . id . localeCompare ( b . id ) ; // Stable sort by ID
47- }
48-
49- // If both are not running (stopped, exited, restarting), sort by display name, then by ID for stability
50- const nameComparison = ( a . name || '' ) . localeCompare ( b . name || '' ) ;
51- if ( nameComparison !== 0 ) {
52- return nameComparison ;
53- }
54- return a . id . localeCompare ( b . id ) ; // Stable sort by ID
55- } ) ;
56-
27+ const maxPoints = 7 ;
28+ const now = Date . now ( ) ;
29+
30+ // Initialize with proper default values if empty
31+ if ( totalCpuData . length === 0 ) {
32+ setTotalCpuData ( [
33+ { value : 0 , timestamp : now - 1000 } ,
34+ { value : 0 , timestamp : now }
35+ ] ) ;
36+ }
37+ if ( totalRamData . length === 0 ) {
38+ setTotalRamData ( [
39+ { value : 0 , timestamp : now - 1000 } ,
40+ { value : 0 , timestamp : now }
41+ ] ) ;
42+ }
43+ if ( totalNetworkRxData . length === 0 ) {
44+ setTotalNetworkRxData ( [
45+ { value : 0 , timestamp : now - 1000 } ,
46+ { value : 0 , timestamp : now }
47+ ] ) ;
48+ }
49+ if ( totalNetworkTxData . length === 0 ) {
50+ setTotalNetworkTxData ( [
51+ { value : 0 , timestamp : now - 1000 } ,
52+ { value : 0 , timestamp : now }
53+ ] ) ;
54+ }
5755
58- setContainers ( sortedContainers ) ;
56+ if ( ! combinedData || ! combinedData . containers ) {
57+ // Set default values if no data (Safe fallback)
58+ setContainers ( [ ] ) ;
59+ setTotalCpuData ( prev => [ ...prev ] . slice ( - maxPoints ) ) ;
60+ setTotalRamData ( prev => [ ...prev ] . slice ( - maxPoints ) ) ;
61+ setTotalNetworkRxData ( prev => [ ...prev ] . slice ( - maxPoints ) ) ;
62+ setTotalNetworkTxData ( prev => [ ...prev ] . slice ( - maxPoints ) ) ;
63+ return ;
64+ }
5965
60- // Update total system memory for scaling
61- if ( combinedData . systemInfo && combinedData . systemInfo . resourceData && combinedData . systemInfo . resourceData . memory && combinedData . systemInfo . resourceData . memory . length > 0 ) {
62- setTotalMemory ( combinedData . systemInfo . resourceData . memory [ 0 ] . totalMemory ) ;
66+ // Sort containers: running first (by RAM usage), then others by name
67+ const sortedContainers = [ ...combinedData . containers ] . sort ( ( a , b ) => {
68+ // Prioritize running containers
69+ const aIsRunning = a . status === "running" ;
70+ const bIsRunning = b . status === "running" ;
71+
72+ if ( aIsRunning && ! bIsRunning ) return - 1 ; // a comes before b
73+ if ( ! aIsRunning && bIsRunning ) return 1 ; // b comes before a
74+
75+ // If both are running, sort by RAM usage (descending)
76+ if ( aIsRunning && bIsRunning ) {
77+ const aRam = a . ramUsage ?. length > 0 ? a . ramUsage [ a . ramUsage . length - 1 ] ?. value || 0 : 0 ;
78+ const bRam = b . ramUsage ?. length > 0 ? b . ramUsage [ b . ramUsage . length - 1 ] ?. value || 0 : 0 ;
79+ if ( aRam !== bRam ) {
80+ return bRam - aRam ;
81+ }
6382 }
6483
65- // Calculate total CPU and RAM for containers
66- const currentTotalCpu = combinedData . containers . reduce ( ( sum , c ) => sum + ( c . cpuUsage [ c . cpuUsage . length - 1 ] ?. value || 0 ) , 0 ) ;
67- const currentTotalRam = combinedData . containers . reduce ( ( sum , c ) => sum + ( c . ramUsage [ c . ramUsage . length - 1 ] ?. value || 0 ) , 0 ) ;
68-
69- setTotalCpuData ( prev => [ ...prev , { value : currentTotalCpu , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
70- setTotalRamData ( prev => [ ...prev , { value : currentTotalRam , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
71-
72- const currentTotalNetworkRx = combinedData . containers . reduce ( ( sum , c ) => sum + ( c . networkRxBytes [ c . networkRxBytes . length - 1 ] ?. value || 0 ) , 0 ) ;
73- const currentTotalNetworkTx = combinedData . containers . reduce ( ( sum , c ) => sum + ( c . networkTxBytes [ c . networkTxBytes . length - 1 ] ?. value || 0 ) , 0 ) ;
74-
75- setTotalNetworkRxData ( prev => [ ...prev , { value : currentTotalNetworkRx , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
76- setTotalNetworkTxData ( prev => [ ...prev , { value : currentTotalNetworkTx , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
77-
78- // Update total system memory for scaling
79- if ( combinedData . systemInfo && combinedData . systemInfo . resourceData && combinedData . systemInfo . resourceData . memory && combinedData . systemInfo . resourceData . memory . length > 0 ) {
80- setTotalMemory ( combinedData . systemInfo . resourceData . memory [ 0 ] . totalMemory ) ;
81- }
84+ // Sort by name as fallback
85+ return ( a . name || '' ) . localeCompare ( b . name || '' ) ;
86+ } ) ;
87+
88+ setContainers ( sortedContainers ) ;
89+
90+ // Calculate totals
91+ const currentTotalCpu = combinedData . containers . reduce ( ( sum , c ) =>
92+ sum + ( ( c . cpuUsage && c . cpuUsage . length > 0 ) ? ( c . cpuUsage [ c . cpuUsage . length - 1 ] ?. value || 0 ) : 0 ) , 0 ) ;
93+
94+ const currentTotalRam = combinedData . containers . reduce ( ( sum , c ) =>
95+ sum + ( ( c . ramUsage && c . ramUsage . length > 0 ) ? ( c . ramUsage [ c . ramUsage . length - 1 ] ?. value || 0 ) : 0 ) , 0 ) ;
96+
97+ const currentTotalNetworkRx = combinedData . containers . reduce ( ( sum , c ) =>
98+ sum + ( ( c . networkRxBytes && c . networkRxBytes . length > 0 ) ? ( c . networkRxBytes [ c . networkRxBytes . length - 1 ] ?. value || 0 ) : 0 ) , 0 ) ;
99+
100+ const currentTotalNetworkTx = combinedData . containers . reduce ( ( sum , c ) =>
101+ sum + ( ( c . networkTxBytes && c . networkTxBytes . length > 0 ) ? ( c . networkTxBytes [ c . networkTxBytes . length - 1 ] ?. value || 0 ) : 0 ) , 0 ) ;
102+
103+ setTotalCpuData ( prev => [ ...prev , { value : currentTotalCpu , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
104+ setTotalRamData ( prev => [ ...prev , { value : currentTotalRam , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
105+ setTotalNetworkRxData ( prev => [ ...prev , { value : currentTotalNetworkRx , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
106+ setTotalNetworkTxData ( prev => [ ...prev , { value : currentTotalNetworkTx , timestamp : Date . now ( ) } ] . slice ( - maxPoints ) ) ;
107+
108+ // Update total system memory for scaling
109+ if ( combinedData . systemInfo ?. resourceData ?. memory ?. [ 0 ] ?. totalMemory ) {
110+ setTotalMemory ( combinedData . systemInfo . resourceData . memory [ 0 ] . totalMemory ) ;
82111 }
83- } , [ combinedData ] ) ;
112+ } , [ combinedData , totalCpuData . length , totalRamData . length , totalNetworkRxData . length , totalNetworkTxData . length ] ) ;
84113
85- const currentTotalCpu = totalCpuData [ totalCpuData . length - 1 ] ?. value || 0
86- const currentTotalRam = totalRamData [ totalRamData . length - 1 ] ?. value || 0
87- const currentTotalNetworkRx = totalNetworkRxData [ totalNetworkRxData . length - 1 ] ?. value || 0 ;
88- const currentTotalNetworkTx = totalNetworkTxData [ totalNetworkTxData . length - 1 ] ?. value || 0 ;
114+ // Safe data access
115+ const currentTotalCpu = totalCpuData [ totalCpuData . length - 1 ] ?. value ?? 0 ;
116+ const currentTotalRam = totalRamData [ totalRamData . length - 1 ] ?. value ?? 0 ;
117+ const currentTotalNetworkRx = totalNetworkRxData [ totalNetworkRxData . length - 1 ] ?. value ?? 0 ;
118+ const currentTotalNetworkTx = totalNetworkTxData [ totalNetworkTxData . length - 1 ] ?. value ?? 0 ;
119+
120+ // Ensure data arrays are never null for ResourceCard components
121+ const safeNetworkRxData = totalNetworkRxData || [ ] ;
122+ const safeNetworkTxData = totalNetworkTxData || [ ] ;
123+ const safeCpuData = totalCpuData || [ ] ;
124+ const safeRamData = totalRamData || [ ] ;
89125
90126 const formattedTotalRamValue = formatMemorySize ( currentTotalRam ) ;
91127
@@ -119,35 +155,35 @@ export default function ContainersPage() {
119155 </ div >
120156
121157 < div className = "grid grid-cols-1 sm:grid-cols-2 gap-1.5" >
122- < ResourceCard icon = { Cog } label = "Compute" value = { currentTotalCpu } data = { totalCpuData } color = "#1e90ff" maxValue = { 100 } />
158+ < ResourceCard icon = { Cog } label = "Compute" value = { currentTotalCpu } data = { safeCpuData } color = "#1e90ff" maxValue = { 100 } />
123159 < ResourceCard
124160 icon = { Layers }
125161 label = "Memory"
126162 value = { currentTotalRam }
127- data = { totalRamData . map ( d => ( { value : d . value , timestamp : d . timestamp } ) ) }
163+ data = { safeRamData }
128164 color = "#00ced1"
129165 valueDisplay = { formattedTotalRamValue }
130- maxValue = { totalMemory } // Pass totalMemory for scaling
166+ maxValue = { totalMemory || 1 }
131167 />
132168 < ResourceCard
133169 icon = { ArrowDown }
134170 label = "Inbound"
135171 value = { currentTotalNetworkRx }
136- data = { totalNetworkRxData }
172+ data = { safeNetworkRxData }
137173 color = "#8b5cf6"
138174 unit = "B/s"
139175 valueDisplay = { formatNetworkSpeed ( currentTotalNetworkRx ) }
140- maxValue = { Math . max ( ...totalNetworkRxData . map ( d => d . value ) , ...totalNetworkTxData . map ( d => d . value ) , 1 ) * 1.15 }
176+ maxValue = { Math . max ( ...safeNetworkRxData . map ( d => d ? .value ?? 0 ) , ...safeNetworkTxData . map ( d => d ? .value ?? 0 ) , 1 ) }
141177 />
142178 < ResourceCard
143179 icon = { ArrowUp }
144180 label = "Outbound"
145181 value = { currentTotalNetworkTx }
146- data = { totalNetworkTxData }
182+ data = { safeNetworkTxData }
147183 color = "#8b5cf6"
148184 unit = "B/s"
149185 valueDisplay = { formatNetworkSpeed ( currentTotalNetworkTx ) }
150- maxValue = { Math . max ( ...totalNetworkRxData . map ( d => d . value ) , ...totalNetworkTxData . map ( d => d . value ) , 1 ) * 1.15 }
186+ maxValue = { Math . max ( ...safeNetworkRxData . map ( d => d ? .value ?? 0 ) , ...safeNetworkTxData . map ( d => d ? .value ?? 0 ) , 1 ) }
151187 />
152188 </ div >
153189 </ motion . div >
@@ -176,30 +212,43 @@ export default function ContainersPage() {
176212 </ AnimatePresence >
177213 </ Card >
178214
179- { /* Display individual container cards */ }
180- { combinedData && combinedData . containers . length > 0 && (
215+ { /* Display individual container cards with safety checks */ }
216+ { ( combinedData ?. containers || [ ] ) . length > 0 ? (
181217 < motion . div
182218 className = "flex flex-col items-center justify-start w-full space-y-4 pt-4"
183219 initial = { { opacity : 0 } }
184220 animate = { { opacity : 1 } }
185221 transition = { { duration : 0.5 } }
186222 >
187- { combinedData . containers
188-
223+ { ( combinedData ?. containers || [ ] )
189224 . sort ( ( a , b ) => {
190- // Sort by status: running first, then others
191225 const statusOrder = { "running" : 0 , "restarting" : 1 , "stopped" : 2 , "exited" : 3 } ;
192- const statusComparison = statusOrder [ a . status ] - statusOrder [ b . status ] ;
226+ const statusComparison = ( statusOrder [ a ? .status ] ?? 999 ) - ( statusOrder [ b ? .status ] ?? 999 ) ;
193227 if ( statusComparison !== 0 ) {
194228 return statusComparison ;
195229 }
196- // Then sort by name ascending
197- return a . name . localeCompare ( b . name ) ;
230+ return ( ( a ?. name || '' ) || '' ) . localeCompare ( ( b ?. name || '' ) || '' ) ;
198231 } )
199232 . map ( ( container ) => (
200- < ContainerCard key = { container . id } container = { container } />
233+ < ContainerCard
234+ key = { container ?. id || Math . random ( ) . toString ( ) }
235+ container = { container }
236+ />
201237 ) ) }
202238 </ motion . div >
239+ ) : (
240+ < motion . div
241+ className = "flex flex-col items-center justify-center w-full p-8 text-center"
242+ initial = { { opacity : 0 } }
243+ animate = { { opacity : 1 } }
244+ transition = { { duration : 0.5 } }
245+ >
246+ < Package className = "w-12 h-12 text-muted-foreground mb-4" />
247+ < h3 className = "text-lg font-medium mb-2" > No Containers Found</ h3 >
248+ < p className = "text-sm text-muted-foreground" >
249+ There are currently no Docker containers to display.
250+ </ p >
251+ </ motion . div >
203252 ) }
204253 </ motion . div >
205254 )
0 commit comments