[dashboard] Add external ips count to Tenant details page

Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
This commit is contained in:
Kirill Ilin 2026-01-30 13:54:01 +05:00
parent f68fefbc12
commit ded52c1279
No known key found for this signature in database
GPG key ID: E902D8B383F7675E
3 changed files with 49 additions and 8 deletions

View file

@ -196,6 +196,10 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
)
}
if kind == "Tenant" {
leftColStack = append(leftColStack, antdFlexVertical("tenant-external-ip-count", 4, []any{
antdText("tenant-external-ip-count-label", true, "External IPs count", nil),
parsedText("tenant-external-ip-count-value", `{reqsJsonPath[0]['.status.externalIPsCount']['0']}`, nil),
}))
rightColStack = append(rightColStack,
antdFlexVertical("resource-quotas-block", 4, []any{
antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{

View file

@ -47,6 +47,9 @@ type ApplicationStatus struct {
// Namespace holds the computed namespace for Tenant applications.
// +optional
Namespace string `json:"namespace,omitempty"`
// ExternalIPsCount holds the number of LoadBalancer services with assigned external IPs for Tenant applications.
// +optional
ExternalIPsCount int32 `json:"externalIPsCount,omitempty"`
}
// GetConditions returns the status conditions of the object.

View file

@ -27,6 +27,7 @@ import (
"time"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
corev1 "k8s.io/api/core/v1"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
@ -186,7 +187,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
}
// Convert the created HelmRelease back to Application
convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease)
convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease)
if err != nil {
klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", helmRelease.GetName(), err)
return nil, fmt.Errorf("conversion error: %v", err)
@ -233,7 +234,7 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions)
}
// Convert HelmRelease to Application
convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease)
convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease)
if err != nil {
klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", name, err)
return nil, fmt.Errorf("conversion error: %v", err)
@ -360,7 +361,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption
continue
}
app, err := r.ConvertHelmReleaseToApplication(hr)
app, err := r.ConvertHelmReleaseToApplication(ctx, hr)
if err != nil {
klog.Errorf("Error converting HelmRelease %s to Application: %v", hr.GetName(), err)
continue
@ -514,7 +515,7 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje
}
// Convert the updated HelmRelease back to Application
convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease)
convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease)
if err != nil {
klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", helmRelease.GetName(), err)
return nil, false, fmt.Errorf("conversion error: %v", err)
@ -796,7 +797,7 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio
// Note: All HelmReleases already match the required labels due to server-side label selector filtering
// Convert HelmRelease to Application
app, err := r.ConvertHelmReleaseToApplication(hr)
app, err := r.ConvertHelmReleaseToApplication(ctx, hr)
if err != nil {
klog.Errorf("Error converting HelmRelease to Application: %v", err)
continue
@ -967,11 +968,11 @@ func filterPrefixedMap(original map[string]string, prefix string) map[string]str
}
// ConvertHelmReleaseToApplication converts a HelmRelease to an Application
func (r *REST) ConvertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) {
func (r *REST) ConvertHelmReleaseToApplication(ctx context.Context, hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) {
klog.V(6).Infof("Converting HelmRelease to Application for resource %s", hr.GetName())
// Convert HelmRelease struct to Application struct
app, err := r.convertHelmReleaseToApplication(hr)
app, err := r.convertHelmReleaseToApplication(ctx, hr)
if err != nil {
klog.Errorf("Error converting from HelmRelease to Application: %v", err)
return appsv1alpha1.Application{}, err
@ -1029,7 +1030,7 @@ func validateNoInternalKeys(values *apiextv1.JSON) error {
}
// convertHelmReleaseToApplication implements the actual conversion logic
func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) {
func (r *REST) convertHelmReleaseToApplication(ctx context.Context, hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) {
// Filter out internal keys (starting with "_") from spec
filteredSpec := filterInternalKeys(hr.Spec.Values)
@ -1071,6 +1072,12 @@ func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1al
// Add namespace field for Tenant applications
if r.kindName == "Tenant" {
app.Status.Namespace = r.computeTenantNamespace(hr.Namespace, app.Name)
externalIPsCount, err := r.countTenantExternalIPs(ctx, app.Status.Namespace)
if err != nil {
klog.Warningf("Failed to count external IPs for tenant %s/%s: %v", hr.Namespace, app.Name, err)
} else {
app.Status.ExternalIPsCount = externalIPsCount
}
}
return app, nil
@ -1265,6 +1272,33 @@ func (r *REST) computeTenantNamespace(currentNamespace, tenantName string) strin
}
}
func (r *REST) countTenantExternalIPs(ctx context.Context, namespace string) (int32, error) {
if namespace == "" {
return 0, nil
}
var services corev1.ServiceList
if err := r.c.List(ctx, &services, client.InNamespace(namespace)); err != nil {
return 0, err
}
var count int32
for i := range services.Items {
svc := &services.Items[i]
if svc.Spec.Type != corev1.ServiceTypeLoadBalancer {
continue
}
for _, ingress := range svc.Status.LoadBalancer.Ingress {
if ingress.IP != "" {
count++
break
}
}
}
return count, nil
}
// Destroy releases resources associated with REST
func (r *REST) Destroy() {
// No additional actions needed to release resources.