@@ -1397,22 +1397,80 @@ func (m Model) fetchFloatingIPsData() dataLoadedMsg {
13971397 return dataLoadedMsg {data : floatingIPs , err : nil }
13981398}
13991399
1400- // fetchLoadBalancersData fetches load balancers at project level
1400+ // fetchLoadBalancersData fetches load balancers from octavia-capable regions
14011401func (m Model ) fetchLoadBalancersData () dataLoadedMsg {
14021402 if m .cloudProject == "" {
1403- return dataLoadedMsg {
1404- err : fmt .Errorf ("no cloud project selected" ),
1403+ return dataLoadedMsg {err : fmt .Errorf ("no cloud project selected" )}
1404+ }
1405+
1406+ regionEndpoint := fmt .Sprintf ("/v1/cloud/project/%s/region" , m .cloudProject )
1407+
1408+ // Get region names then filter by octavialoadbalancer feature
1409+ var allNames []string
1410+ if err := httpLib .Client .Get (regionEndpoint , & allNames ); err != nil {
1411+ return dataLoadedMsg {err : err }
1412+ }
1413+ ids := make ([]any , len (allNames ))
1414+ for i , n := range allNames {
1415+ ids [i ] = n
1416+ }
1417+ details , _ := httpLib .FetchObjectsParallel [map [string ]any ](regionEndpoint + "/%s" , ids , true )
1418+
1419+ var regions []any
1420+ var regionNames []string
1421+ for i , r := range details {
1422+ if r == nil {
1423+ continue
14051424 }
1425+ if services , ok := r ["services" ].([]interface {}); ok {
1426+ for _ , svc := range services {
1427+ if sm , ok := svc .(map [string ]interface {}); ok {
1428+ if sm ["name" ] == "octavialoadbalancer" && sm ["status" ] == "UP" {
1429+ regions = append (regions , allNames [i ])
1430+ regionNames = append (regionNames , allNames [i ])
1431+ break
1432+ }
1433+ }
1434+ }
1435+ }
1436+ }
1437+
1438+ if len (regions ) == 0 {
1439+ return dataLoadedMsg {data : nil , err : nil }
14061440 }
14071441
1408- var loadbalancers []map [string ]interface {}
1409- endpoint := fmt .Sprintf ("/v1/cloud/project/%s/networkloadbalancer" , m .cloudProject )
1410- err := httpLib .Client .Get (endpoint , & loadbalancers )
1442+ allRegionLBs , _ := httpLib .FetchObjectsParallel [[]map [string ]any ](regionEndpoint + "/%s/loadbalancing/loadbalancer" , regions , true )
14111443
1412- return dataLoadedMsg {
1413- data : loadbalancers ,
1414- err : err ,
1444+ // Build flavorId -> name map per region in parallel
1445+ allRegionFlavors , _ := httpLib .FetchObjectsParallel [[]map [string ]any ](regionEndpoint + "/%s/loadbalancing/flavor" , regions , true )
1446+ flavorNameMap := make (map [string ]string )
1447+ for _ , flavors := range allRegionFlavors {
1448+ for _ , f := range flavors {
1449+ if id , ok := f ["id" ].(string ); ok {
1450+ if name , ok := f ["name" ].(string ); ok {
1451+ flavorNameMap [id ] = name
1452+ }
1453+ }
1454+ }
14151455 }
1456+
1457+ var lbs []map [string ]interface {}
1458+ for i , regionLBs := range allRegionLBs {
1459+ for _ , lb := range regionLBs {
1460+ if r , _ := lb ["region" ].(string ); r == "" {
1461+ lb ["region" ] = regionNames [i ]
1462+ }
1463+ // Replace flavorId with flavor name
1464+ if fid , ok := lb ["flavorId" ].(string ); ok {
1465+ if fname , found := flavorNameMap [fid ]; found {
1466+ lb ["_flavorName" ] = fname
1467+ }
1468+ }
1469+ lbs = append (lbs , lb )
1470+ }
1471+ }
1472+
1473+ return dataLoadedMsg {data : lbs , err : nil }
14161474}
14171475
14181476// fetchGatewaysData fetches gateways from network-capable regions
@@ -1654,6 +1712,8 @@ func (m Model) handleDataLoaded(msg dataLoadedMsg) (tea.Model, tea.Cmd) {
16541712 m .table = createFloatingIPsTable (msg .data , m .width , m .height )
16551713 case ProductNetworkGateway :
16561714 m .table = createGatewaysTable (msg .data , m .width , m .height )
1715+ case ProductNetworkLB :
1716+ m .table = createLoadBalancersTable (msg .data , m .width , m .height )
16571717 default :
16581718 m .table = createGenericTable (msg .data , m .width , m .height )
16591719 }
@@ -2123,6 +2183,78 @@ func createPrivateNetworksTable(data []map[string]interface{}, width, height int
21232183 return t
21242184}
21252185
2186+ // createLoadBalancersTable creates a table for load balancers.
2187+ func createLoadBalancersTable (data []map [string ]interface {}, width , height int ) table.Model {
2188+ columns := []table.Column {
2189+ {Title : "Name" , Width : 22 },
2190+ {Title : "Region" , Width : 16 },
2191+ {Title : "Size" , Width : 14 },
2192+ {Title : "Private Network" , Width : 36 },
2193+ {Title : "Public IP" , Width : 18 },
2194+ {Title : "Private IP" , Width : 16 },
2195+ {Title : "Supply Status" , Width : 14 },
2196+ {Title : "Status" , Width : 12 },
2197+ }
2198+
2199+ var rows []table.Row
2200+ for _ , lb := range data {
2201+ name := getString (lb , "name" )
2202+ region := getString (lb , "region" )
2203+ size := getString (lb , "_flavorName" )
2204+ if size == "" {
2205+ size = getString (lb , "flavorId" )
2206+ }
2207+ privateNetwork := getString (lb , "vipNetworkId" )
2208+ privateIP := getString (lb , "vipAddress" )
2209+ provisioning := getString (lb , "provisioningStatus" )
2210+ status := getString (lb , "operatingStatus" )
2211+
2212+ publicIP := "-"
2213+ if fi , ok := lb ["floatingIp" ].(map [string ]interface {}); ok {
2214+ if v := getString (fi , "ip" ); v != "" {
2215+ publicIP = v
2216+ }
2217+ }
2218+ if privateNetwork == "" {
2219+ privateNetwork = "-"
2220+ }
2221+ if privateIP == "" {
2222+ privateIP = "-"
2223+ }
2224+
2225+ rows = append (rows , table.Row {name , region , size , privateNetwork , publicIP , privateIP , provisioning , status })
2226+ }
2227+
2228+ tableHeight := height - 15
2229+ if tableHeight < 5 {
2230+ tableHeight = 5
2231+ }
2232+ if tableHeight > 20 {
2233+ tableHeight = 20
2234+ }
2235+
2236+ t := table .New (
2237+ table .WithColumns (columns ),
2238+ table .WithRows (rows ),
2239+ table .WithFocused (true ),
2240+ table .WithHeight (tableHeight ),
2241+ )
2242+
2243+ s := table .DefaultStyles ()
2244+ s .Header = s .Header .
2245+ BorderStyle (lipgloss .NormalBorder ()).
2246+ BorderForeground (lipgloss .Color ("240" )).
2247+ BorderBottom (true ).
2248+ Bold (true )
2249+ s .Selected = s .Selected .
2250+ Foreground (lipgloss .Color ("229" )).
2251+ Background (lipgloss .Color ("57" )).
2252+ Bold (false )
2253+ t .SetStyles (s )
2254+
2255+ return t
2256+ }
2257+
21262258// createGatewaysTable creates a table for gateways.
21272259func createGatewaysTable (data []map [string ]interface {}, width , height int ) table.Model {
21282260 columns := []table.Column {
0 commit comments