11// SPDX-FileCopyrightText: Copyright 2024 LG Electronics Inc.
22// SPDX-License-Identifier: Apache-2.0
3- import { useState } from "react" ;
3+ import { useEffect , useState } from "react" ;
44import {
55 Card ,
66 CardContent ,
@@ -19,6 +19,7 @@ import {
1919import { Badge } from "./ui/badge" ;
2020import { Button } from "./ui/button" ;
2121import { Input } from "./ui/input" ;
22+ import { Progress } from "./ui/progress" ;
2223import {
2324 Search ,
2425 MoreHorizontal ,
@@ -33,44 +34,116 @@ export function Cluster() {
3334 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
3435
3536 // Mock data
36- const nodes = [
37- {
38- name : "master-node-1" ,
39- internalIP : "192.168.1.10" ,
40- os : "Ubuntu 22.04" ,
41- arch : "amd64" ,
42- cpuCapacity : "4" ,
43- memoryCapacity : "8Gi" ,
44- storage : "50Gi" ,
45- } ,
46- {
47- name : "worker-node-1" ,
48- internalIP : "192.168.1.11" ,
49- os : "Ubuntu 22.04" ,
50- arch : "amd64" ,
51- cpuCapacity : "8" ,
52- memoryCapacity : "16Gi" ,
53- storage : "100Gi" ,
54- } ,
55- {
56- name : "worker-node-2" ,
57- internalIP : "192.168.1.12" ,
58- os : "Ubuntu 22.04" ,
59- arch : "amd64" ,
60- cpuCapacity : "8" ,
61- memoryCapacity : "16Gi" ,
62- storage : "100Gi" ,
63- } ,
64- {
65- name : "worker-node-3" ,
66- internalIP : "192.168.1.13" ,
67- os : "Ubuntu 22.04" ,
68- arch : "arm64" ,
69- cpuCapacity : "8" ,
70- memoryCapacity : "16Gi" ,
71- storage : "100Gi" ,
72- } ,
73- ] ;
37+ // nodes fetched from settingsservice
38+ const [ nodesDataToUse , setNodesDataToUse ] = useState < any [ ] > ( [ ] ) ;
39+ const [ nodesFetchSuccess , setNodesFetchSuccess ] = useState ( false ) ;
40+ const [ nodesFetchError , setNodesFetchError ] = useState < string | null > ( null ) ;
41+
42+ useEffect ( ( ) => {
43+ const settingserviceApiUrl = import . meta. env . VITE_SETTING_SERVICE_API_URL ;
44+ const endpoint = settingserviceApiUrl
45+ ? `${ settingserviceApiUrl . replace ( / \/ + $ / , "" ) } /api/v1/nodes`
46+ : "/api/v1/nodes" ;
47+
48+ const fetchNodes = async ( ) => {
49+ setNodesFetchError ( null ) ;
50+ const tryRelative = async ( ) => {
51+ // Try the metrics path first (Workloads uses /api/v1/metrics/nodes)
52+ const candidates = [
53+ "/api/v1/metrics/nodes" ,
54+ "/api/v1/nodes" ,
55+ ] ;
56+ for ( const path of candidates ) {
57+ try {
58+ const res = await fetch ( path ) ;
59+ if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` ) ;
60+ return await res . json ( ) ;
61+ } catch ( e ) {
62+ console . debug ( `Relative ${ path } fetch failed:` , e ) ;
63+ // try next
64+ }
65+ }
66+ throw new Error ( "All relative node endpoints failed" ) ;
67+ } ;
68+
69+ const tryAbsolute = async ( ) => {
70+ if ( ! endpoint ) throw new Error ( "No endpoint configured" ) ;
71+ try {
72+ const res = await fetch ( endpoint ) ;
73+ if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` ) ;
74+ return await res . json ( ) ;
75+ } catch ( e ) {
76+ console . debug ( "Absolute settingsservice fetch failed:" , e ) ;
77+ throw e ;
78+ }
79+ } ;
80+
81+ let data : any = null ;
82+ try {
83+ // Prefer relative path (dev proxy or same-origin)
84+ data = await tryRelative ( ) ;
85+ } catch ( eRel ) {
86+ // If relative failed, try absolute (explicit settingservice URL)
87+ try {
88+ data = await tryAbsolute ( ) ;
89+ } catch ( eAbs ) {
90+ const msg = ( eAbs && ( eAbs as Error ) . message ) || String ( eAbs || eRel ) ;
91+ console . error ( "Nodes fetch failed:" , eAbs || eRel ) ;
92+ setNodesFetchError ( msg ) ;
93+ setNodesFetchSuccess ( false ) ;
94+ setNodesDataToUse ( [ ] ) ;
95+ return ;
96+ }
97+ }
98+
99+ // Normalize response shape
100+ let nodes : any [ ] = [ ] ;
101+ if ( Array . isArray ( data ) ) nodes = data ;
102+ else if ( data && Array . isArray ( ( data as any ) . nodes ) ) nodes = ( data as any ) . nodes ;
103+ else {
104+ setNodesFetchError ( "Unexpected response shape from nodes API" ) ;
105+ setNodesFetchSuccess ( false ) ;
106+ setNodesDataToUse ( [ ] ) ;
107+ return ;
108+ }
109+
110+ if ( nodes . length > 0 ) {
111+ const normalized = nodes . map ( ( n ) => ( {
112+ ...n ,
113+ cpu_usage : typeof n . cpu_usage === "number" ? n . cpu_usage : Number ( n . cpu_usage ) || 0 ,
114+ cpu_count : typeof n . cpu_count === "number" ? n . cpu_count : Number ( n . cpu_count ) || 0 ,
115+ used_memory : typeof n . used_memory === "number" ? n . used_memory : Number ( n . used_memory ) || 0 ,
116+ total_memory : typeof n . total_memory === "number" ? n . total_memory : Number ( n . total_memory ) || 0 ,
117+ mem_usage : typeof n . mem_usage === "number" ? n . mem_usage : Number ( n . mem_usage ) || 0 ,
118+ } ) ) ;
119+ setNodesDataToUse ( normalized ) ;
120+ setNodesFetchSuccess ( true ) ;
121+ } else {
122+ setNodesFetchSuccess ( false ) ;
123+ setNodesDataToUse ( [ ] ) ;
124+ }
125+ } ;
126+
127+ fetchNodes ( ) ;
128+ const interval = setInterval ( fetchNodes , import . meta. env . VITE_SETTING_SERVICE_TIMEOUT || 5000 ) ;
129+ return ( ) => clearInterval ( interval ) ;
130+ } , [ ] ) ;
131+
132+ const GB = 1024 * 1024 * 1024 ;
133+ const nodes = nodesFetchSuccess
134+ ? nodesDataToUse . map ( ( node : any ) => ( {
135+ name : node . node_name ,
136+ internalIP : node . ip || node . internal_ip || "" ,
137+ os : node . os || "" ,
138+ arch : node . arch || "" ,
139+ // Use raw values from API: cpu_usage and mem_usage
140+ cpuUsage : node . cpu_usage ?? 0 ,
141+ memoryUsage : node . mem_usage ?? 0 ,
142+ totalStorage : node . total_storage || node . storage_total || 0 ,
143+ storageUsage : node . storage_usage || node . used_storage || 0 ,
144+ // pods removed from this table per request
145+ } ) )
146+ : [ ] ;
74147
75148
76149
@@ -125,7 +198,7 @@ export function Cluster() {
125198 </ div >
126199 < div >
127200 < CardTitle className = "text-foreground" >
128- Cluster Nodes
201+ Nodes
129202 </ CardTitle >
130203 < CardDescription >
131204 Physical and virtual machines in your cluster
@@ -134,7 +207,12 @@ export function Cluster() {
134207 </ div >
135208 </ CardHeader >
136209 < CardContent >
137- < div className = "overflow-hidden rounded-xl border border-border/30" >
210+ < div className = "overflow-hidden rounded-xl border border-border/30" >
211+ { nodesFetchError && (
212+ < div className = "p-3 text-sm text-yellow-700 bg-yellow-50 border-b border-yellow-100" >
213+ Nodes fetch error: { nodesFetchError }
214+ </ div >
215+ ) }
138216 < Table >
139217 < TableHeader className = "bg-muted/80" >
140218 < TableRow className = "border-border/30" >
@@ -150,16 +228,9 @@ export function Cluster() {
150228 < TableHead className = "font-semibold text-foreground" >
151229 Internal IP
152230 </ TableHead >
153- < TableHead className = "font-semibold text-foreground" >
154- CPU
155- </ TableHead >
156- < TableHead className = "font-semibold text-foreground" >
157- Memory
158- </ TableHead >
159- < TableHead className = "font-semibold text-foreground" >
160- Storage
161- </ TableHead >
162- < TableHead className = "font-semibold text-foreground" > </ TableHead >
231+ < TableHead className = "font-semibold text-foreground" > CPU Usage</ TableHead >
232+ < TableHead className = "font-semibold text-foreground" > Memory Usage</ TableHead >
233+ < TableHead className = "font-semibold text-foreground" > Storage</ TableHead >
163234 </ TableRow >
164235 </ TableHeader >
165236 < TableBody >
@@ -183,37 +254,25 @@ export function Cluster() {
183254 { node . internalIP }
184255 </ TableCell >
185256 < TableCell >
186- < div className = "flex items-center gap-1 " >
187- < Cpu className = "w-3 h-3 text-muted-foreground" / >
188- < span className = "font-mono text-sm " >
189- { node . cpuCapacity }
190- </ span >
257+ < div className = "flex items-center gap-2 " >
258+ < span className = "font-mono text-sm" > { typeof node . cpuUsage === 'number' ? node . cpuUsage . toFixed ( 2 ) : String ( node . cpuUsage ) } </ span >
259+ < div className = "w-24 " >
260+ < Progress value = { Math . min ( 100 , Math . max ( 0 , Number ( node . cpuUsage ) || 0 ) ) } className = "h-2" />
261+ </ div >
191262 </ div >
192263 </ TableCell >
193264 < TableCell >
194- < div className = "flex items-center gap-1 " >
195- < MemoryStick className = "w-3 h-3 text-muted-foreground" / >
196- < span className = "font-mono text-sm " >
197- { node . memoryCapacity }
198- </ span >
265+ < div className = "flex items-center gap-2 " >
266+ < span className = "font-mono text-sm" > { typeof node . memoryUsage === 'number' ? node . memoryUsage . toFixed ( 2 ) : String ( node . memoryUsage ) } </ span >
267+ < div className = "w-36 " >
268+ < Progress value = { Math . min ( 100 , Math . max ( 0 , Number ( node . memoryUsage ) || 0 ) ) } className = "h-2" />
269+ </ div >
199270 </ div >
200271 </ TableCell >
201- < TableCell >
202- < div className = "flex items-center gap-1" >
203- < HardDrive className = "w-3 h-3 text-muted-foreground" />
204- < span className = "font-mono text-sm" >
205- { node . storage }
206- </ span >
207- </ div >
208- </ TableCell >
209- < TableCell >
210- < Button
211- variant = "ghost"
212- size = "sm"
213- className = "w-8 h-8 hover:bg-muted"
214- >
215- < MoreHorizontal className = "h-3 w-3" />
216- </ Button >
272+ < TableCell className = "text-sm text-muted-foreground" >
273+ { node . totalStorage && node . totalStorage > 0
274+ ? `${ ( node . storageUsage / GB ) . toFixed ( 1 ) } Gi / ${ ( node . totalStorage / GB ) . toFixed ( 1 ) } Gi`
275+ : "N/A" }
217276 </ TableCell >
218277 </ TableRow >
219278 ) ) }
0 commit comments