[dashboard] Add external ips count to Tenant details page (#1938)
<!-- Thank you for making a contribution! Here are some tips for you: - Start the PR title with the [label] of Cozystack component: - For system components: [platform], [system], [linstor], [cilium], [kube-ovn], [dashboard], [cluster-api], etc. - For managed apps: [apps], [tenant], [kubernetes], [postgres], [virtual-machine] etc. - For development and maintenance: [tests], [ci], [docs], [maintenance]. - If it's a work in progress, consider creating this PR as a draft. - Don't hesistate to ask for opinion and review in the community chats, even if it's still a draft. - Add the label `backport` if it's a bugfix that needs to be backported to a previous version. --> ## What this PR does Add external ips (LoadBalancer services with assigned IP address) count to the Tenant details page <img width="2560" height="1330" alt="external ip count on tenant details page" src="https://github.com/user-attachments/assets/f2256629-3815-4892-b80d-e150d0fd3908" /> ### Release note <!-- Write a release note: - Explain what has changed internally and for users. - Start with the same [label] as in the PR title - Follow the guidelines at https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md. --> ```release-note [dashboard] add external ips count (count of services of type LoadBalancer with assigned ip) to the Tenant details page ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * External IPs count now shown in the Tenant application Details tab — a left-column item displays the number of external IPs for LoadBalancer services. * Application status now exposes an External IPs Count metric for Tenant applications, so UI and APIs can surface the number of LoadBalancer external IPs without changing other behaviors. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
commit
855aa76b29
4 changed files with 62 additions and 8 deletions
|
|
@ -175,6 +175,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{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -149,6 +149,17 @@ func (c completedConfig) New() (*CozyServer, error) {
|
|||
return nil, fmt.Errorf("failed to build manager: %w", err)
|
||||
}
|
||||
|
||||
if err := mgr.GetFieldIndexer().IndexField(
|
||||
context.Background(),
|
||||
&corev1.Service{},
|
||||
"spec.type",
|
||||
func(rawObj client.Object) []string {
|
||||
svc := rawObj.(*corev1.Service)
|
||||
return []string{string(svc.Spec.Type)}
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to index service spec.type field: %w", err)
|
||||
}
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
if err = mustGetInformers(ctx, mgr,
|
||||
|
|
|
|||
|
|
@ -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,35 @@ 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),
|
||||
client.MatchingFields{"spec.type": string(corev1.ServiceTypeLoadBalancer)},
|
||||
); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var count int32
|
||||
for i := range services.Items {
|
||||
svc := &services.Items[i]
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue