HEX
Server: Apache/2.4.54 (Win64) OpenSSL/1.1.1p PHP/7.4.30
System: Windows NT website-api 10.0 build 20348 (Windows Server 2016) AMD64
User: SYSTEM (0)
PHP: 7.4.30
Disabled: NONE
Upload Files
File: C:/github_repos/casibase_customer_0058/object/application_view_util.go
// Copyright 2025 The Casibase Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package object

import (
	"context"
	"fmt"
	"sync"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	metricsclientset "k8s.io/metrics/pkg/client/clientset/versioned"

	"github.com/casibase/casibase/i18n"
	networkingv1 "k8s.io/api/networking/v1"
)

func AddDetails(apps []*Application, lang string) {
	if len(apps) == 0 {
		return
	}

	if ensureK8sClient(lang) != nil {
		return
	}

	var wg sync.WaitGroup

	for _, app := range apps {
		wg.Add(1)
		go func(app *Application) {
			defer wg.Done()
			if details, err := GetApplicationView(app.Namespace, lang); err == nil {
				app.Status = details.Status
				app.Details = details

				if app.URL == "" {
					if url, err := GetURL(app.Namespace, lang); err == nil && url != "" {
						app.URL = url
					}
				}
			}
		}(app)
	}

	wg.Wait()
}

// GetURL retrieves the access URL for an application
func GetURL(namespace string, lang string) (string, error) {
	nodeIPs := getNodeIPsFromCache()
	services := getServicesFromCache(namespace, nodeIPs)

	// Find first available access URL from services
	for _, service := range services {
		for _, port := range service.Ports {
			if port.URL != "" {
				return port.URL, nil
			}
		}
	}

	return "", fmt.Errorf(i18n.Translate(lang, "object:no accessible URL found for application"))
}

// findIngressURL finds the external access URL for a service in Ingress rules.
func findIngressURL(serviceName string, servicePort int32, ingresses []*networkingv1.Ingress) string {
	for _, ingress := range ingresses {
		// Iterate through Ingress rules
		for _, rule := range ingress.Spec.Rules {
			if rule.HTTP == nil {
				continue
			}

			// Build the hostname
			host := rule.Host
			if host == "" && len(ingress.Status.LoadBalancer.Ingress) > 0 {
				// If the host is not specified in the rule, try to get it from the LoadBalancer status
				lbIngress := ingress.Status.LoadBalancer.Ingress[0]
				if lbIngress.Hostname != "" {
					host = lbIngress.Hostname
				} else if lbIngress.IP != "" {
					host = lbIngress.IP
				}
			}

			// Iterate through each path
			for _, path := range rule.HTTP.Paths {
				backend := path.Backend

				if backend.Service != nil &&
					backend.Service.Name == serviceName &&
					backend.Service.Port.Number == servicePort {

					scheme := "http"
					if hasTLSForHost(ingress, host) {
						scheme = "https"
					}

					pathStr := path.Path
					if pathStr == "" {
						pathStr = "/"
					}

					return scheme + "://" + host + pathStr
				}
			}
		}
	}

	return ""
}

// hasTLSForHost examine if the ingress has TLS configured for the given host
func hasTLSForHost(ingress *networkingv1.Ingress, host string) bool {
	for _, tls := range ingress.Spec.TLS {
		for _, tlsHost := range tls.Hosts {
			if tlsHost == host {
				return true
			}
		}
	}
	return false
}

// formatCPUUsage formats CPU usage quantity to human-readable format
func formatCPUUsage(quantity resource.Quantity) string {
	milliValue := quantity.MilliValue()

	if milliValue >= 1000 {
		cores := float64(milliValue) / 1000
		if cores == float64(int(cores)) {
			return fmt.Sprintf("%d", int(cores))
		}
		return fmt.Sprintf("%.2f", cores)
	}

	// Display as milli-cores for < 1 core
	return fmt.Sprintf("%dm", milliValue)
}

// formatMemoryUsage formats memory usage quantity to human-readable format
func formatMemoryUsage(quantity resource.Quantity) string {
	bytes := quantity.Value()

	switch {
	case bytes >= 1<<30: // >= 1 GiB
		gb := float64(bytes) / (1 << 30)
		if gb >= 10 {
			return fmt.Sprintf("%.0fGi", gb)
		}
		return fmt.Sprintf("%.1fGi", gb)
	case bytes >= 1<<20: // >= 1 MiB
		mb := float64(bytes) / (1 << 20)
		if mb >= 10 {
			return fmt.Sprintf("%.0fMi", mb)
		}
		return fmt.Sprintf("%.1fMi", mb)
	case bytes >= 1<<10: // >= 1 KiB
		return fmt.Sprintf("%.0fKi", float64(bytes)/(1<<10))
	default:
		return fmt.Sprintf("%d", bytes)
	}
}

func calculateNamespaceMetrics(ctx context.Context, metricsClient *metricsclientset.Clientset, namespace string, deployCache map[string]map[string]*appsv1.Deployment, mu *sync.RWMutex, lang string) (*CachedMetrics, error) {
	if metricsClient == nil {
		return nil, fmt.Errorf(i18n.Translate(lang, "object:metrics client not available"))
	}

	podMetricsList, err := metricsClient.MetricsV1beta1().PodMetricses(namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	totalCPU := resource.NewQuantity(0, resource.DecimalSI)
	totalMemory := resource.NewQuantity(0, resource.BinarySI)

	// Aggregate resource usage from all pods
	for _, podMetrics := range podMetricsList.Items {
		for _, container := range podMetrics.Containers {
			if cpuUsage, ok := container.Usage[v1.ResourceCPU]; ok {
				totalCPU.Add(cpuUsage)
			}
			if memUsage, ok := container.Usage[v1.ResourceMemory]; ok {
				totalMemory.Add(memUsage)
			}
		}
	}

	var cpuPercentage, memPercentage float64

	// Calculate percentages if deployment cache and mutex are provided
	if deployCache != nil && mu != nil {
		mu.RLock()
		if deploys, exists := deployCache[namespace]; exists {
			totalCPULimits := resource.NewQuantity(0, resource.DecimalSI)
			totalMemoryLimits := resource.NewQuantity(0, resource.BinarySI)

			for _, deploy := range deploys {
				replicas := int32(1)
				if deploy.Spec.Replicas != nil {
					replicas = *deploy.Spec.Replicas
				}

				for _, container := range deploy.Spec.Template.Spec.Containers {
					if container.Resources.Limits != nil {
						if cpuLimit, ok := container.Resources.Limits[v1.ResourceCPU]; ok {
							for i := int32(0); i < replicas; i++ {
								totalCPULimits.Add(cpuLimit)
							}
						}
						if memLimit, ok := container.Resources.Limits[v1.ResourceMemory]; ok {
							for i := int32(0); i < replicas; i++ {
								totalMemoryLimits.Add(memLimit)
							}
						}
					}
				}
			}

			if totalCPULimits.MilliValue() > 0 {
				cpuPercentage = float64(totalCPU.MilliValue()) / float64(totalCPULimits.MilliValue()) * 100
			}
			if totalMemoryLimits.Value() > 0 {
				memPercentage = float64(totalMemory.Value()) / float64(totalMemoryLimits.Value()) * 100
			}
		}
		mu.RUnlock()
	}

	return &CachedMetrics{
		TotalCPU:         *totalCPU,
		TotalMemory:      *totalMemory,
		PodCount:         len(podMetricsList.Items),
		CPUPercentage:    cpuPercentage,
		MemoryPercentage: memPercentage,
		LastUpdated:      time.Now(),
	}, nil
}