Repository: kubernetes-sigs/kueue

Issue:
KueueViz: Workload filtering
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:
Options to filter by `namespace`, `status`, `started time`

**Why is this needed**:
That's basic feature. I use KueueViz and I would use such a feature a lot.

**Completion requirements**:

This enhancement requires the following artifacts:

- [ ] Design doc
- [ ] API change
- [ ] Docs update

The artifacts should be linked in subsequent comments.


Discussion:
@shaharelys: @mimowo Hey! That's something we need. I would like to start contributing working on that issue. Is that cool?@mimowo: >  I would like to start contributing working on that issue. Is that cool?

Awesome, cc @akram @kannon92 @shaharelys: Hey guys! Thinking about the architecture, I am having a hard time deciding where the filtering logic should be placed. Should it be under the frontend or under the backend? 

I have almost no experience with FS design :)

From what I read, there are some considerations for either solution. Would appreciate your input @mimowo @akram @kannon92 
@mimowo: I would be pragmatic about it, and start simple. My guess is that frontend filtering is simpler, especially if frontend already has the complete list of workloads (to be checked). We can move it to backend when we hit scalability limits of the approach.

Solution patch:
diff --git a/Makefile-test.mk b/Makefile-test.mk
index b5d36caa4..3c3bcab61 100644
--- a/Makefile-test.mk
+++ b/Makefile-test.mk
@@ -34,6 +34,7 @@ INTEGRATION_TARGET_MULTIKUEUE ?= ./test/integration/multikueue/...
 # Verbosity level for apiserver logging.
 # The logging is disabled if 0.
 INTEGRATION_API_LOG_LEVEL ?= 0
+INTEGRATION_OUTPUT_OPTIONS ?= --output-interceptor-mode=none 
 
 # Folder where the e2e tests are located.
 E2E_TARGET ?= ./test/e2e/...
@@ -85,7 +86,7 @@ test-integration: gomod-download envtest ginkgo dep-crds kueuectl ginkgo-top ##
 	KUEUE_BIN=$(PROJECT_DIR)/bin \
 	ENVTEST_K8S_VERSION=$(ENVTEST_K8S_VERSION) \
 	TEST_LOG_LEVEL=$(TEST_LOG_LEVEL) API_LOG_LEVEL=$(INTEGRATION_API_LOG_LEVEL) \
-	$(GINKGO) $(INTEGRATION_FILTERS) $(GINKGO_ARGS) $(GOFLAGS) -procs=$(INTEGRATION_NPROCS) --race --junit-report=junit.xml --json-report=integration.json --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET)
+	$(GINKGO) $(INTEGRATION_FILTERS) $(GINKGO_ARGS) $(GOFLAGS) -procs=$(INTEGRATION_NPROCS) --race --junit-report=junit.xml --json-report=integration.json $(INTEGRATION_OUTPUT_OPTIONS) --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET)
 	$(PROJECT_DIR)/bin/ginkgo-top -i $(ARTIFACTS)/integration.json > $(ARTIFACTS)/integration-top.yaml
 
 .PHONY: test-integration-baseline
@@ -103,7 +104,7 @@ test-multikueue-integration: gomod-download envtest ginkgo dep-crds ginkgo-top #
 	KUEUE_BIN=$(PROJECT_DIR)/bin \
 	ENVTEST_K8S_VERSION=$(ENVTEST_K8S_VERSION) \
 	TEST_LOG_LEVEL=$(TEST_LOG_LEVEL) API_LOG_LEVEL=$(INTEGRATION_API_LOG_LEVEL) \
-	$(GINKGO) $(INTEGRATION_FILTERS) $(GINKGO_ARGS) $(GOFLAGS) -procs=$(INTEGRATION_NPROCS_MULTIKUEUE) --race --junit-report=multikueue-junit.xml --json-report=multikueue-integration.json --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET_MULTIKUEUE)
+	$(GINKGO) $(INTEGRATION_FILTERS) $(GINKGO_ARGS) $(GOFLAGS) -procs=$(INTEGRATION_NPROCS_MULTIKUEUE) --race --junit-report=multikueue-junit.xml --json-report=multikueue-integration.json $(INTEGRATION_OUTPUT_OPTIONS) --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET_MULTIKUEUE)
 	$(PROJECT_DIR)/bin/ginkgo-top -i $(ARTIFACTS)/multikueue-integration.json > $(ARTIFACTS)/multikueue-integration-top.yaml
 
 CREATE_KIND_CLUSTER ?= true
diff --git a/charts/kueue/templates/webhook/manifests.yaml b/charts/kueue/templates/webhook/manifests.yaml
index 5b7a9c9af..2123b9ac3 100644
--- a/charts/kueue/templates/webhook/manifests.yaml
+++ b/charts/kueue/templates/webhook/manifests.yaml
@@ -21,6 +21,10 @@ webhooks:
         path: /mutate-workload-codeflare-dev-v1beta2-appwrapper
     failurePolicy: Fail
     name: mappwrapper.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - workload.codeflare.dev
@@ -77,6 +81,10 @@ webhooks:
         path: /mutate-batch-v1-job
     failurePolicy: Fail
     name: mjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - batch
@@ -97,6 +105,10 @@ webhooks:
         path: /mutate-jobset-x-k8s-io-v1alpha2-jobset
     failurePolicy: Fail
     name: mjobset.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - jobset.x-k8s.io
@@ -117,6 +129,10 @@ webhooks:
         path: /mutate-kubeflow-org-v1-jaxjob
     failurePolicy: Fail
     name: mjaxjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -137,6 +153,10 @@ webhooks:
         path: /mutate-kubeflow-org-v1-paddlejob
     failurePolicy: Fail
     name: mpaddlejob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -157,6 +177,10 @@ webhooks:
         path: /mutate-kubeflow-org-v1-pytorchjob
     failurePolicy: Fail
     name: mpytorchjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -177,6 +201,10 @@ webhooks:
         path: /mutate-kubeflow-org-v1-tfjob
     failurePolicy: Fail
     name: mtfjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -197,6 +225,10 @@ webhooks:
         path: /mutate-kubeflow-org-v1-xgboostjob
     failurePolicy: Fail
     name: mxgboostjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -252,6 +284,10 @@ webhooks:
         path: /mutate-leaderworkerset-x-k8s-io-v1-leaderworkerset
     failurePolicy: Fail
     name: mleaderworkerset.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - leaderworkerset.x-k8s.io
@@ -273,6 +309,10 @@ webhooks:
         path: /mutate-kubeflow-org-v2beta1-mpijob
     failurePolicy: Fail
     name: mmpijob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -293,6 +333,10 @@ webhooks:
         path: /mutate-ray-io-v1-raycluster
     failurePolicy: Fail
     name: mraycluster.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - ray.io
@@ -313,6 +357,10 @@ webhooks:
         path: /mutate-ray-io-v1-rayjob
     failurePolicy: Fail
     name: mrayjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - ray.io
@@ -442,6 +490,10 @@ webhooks:
         path: /validate-workload-codeflare-dev-v1beta2-appwrapper
     failurePolicy: Fail
     name: vappwrapper.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - workload.codeflare.dev
@@ -497,6 +549,10 @@ webhooks:
         path: /validate-batch-v1-job
     failurePolicy: Fail
     name: vjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - batch
@@ -517,6 +573,10 @@ webhooks:
         path: /validate-jobset-x-k8s-io-v1alpha2-jobset
     failurePolicy: Fail
     name: vjobset.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - jobset.x-k8s.io
@@ -537,6 +597,10 @@ webhooks:
         path: /validate-kubeflow-org-v1-jaxjob
     failurePolicy: Fail
     name: vjaxjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -557,6 +621,10 @@ webhooks:
         path: /validate-kubeflow-org-v1-paddlejob
     failurePolicy: Fail
     name: vpaddlejob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -577,6 +645,10 @@ webhooks:
         path: /validate-kubeflow-org-v1-pytorchjob
     failurePolicy: Fail
     name: vpytorchjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -597,6 +669,10 @@ webhooks:
         path: /validate-kubeflow-org-v1-tfjob
     failurePolicy: Fail
     name: vtfjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -617,6 +693,10 @@ webhooks:
         path: /validate-kubeflow-org-v1-xgboostjob
     failurePolicy: Fail
     name: vxgboostjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -672,6 +752,10 @@ webhooks:
         path: /validate-leaderworkerset-x-k8s-io-v1-leaderworkerset
     failurePolicy: Fail
     name: vleaderworkerset.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - leaderworkerset.x-k8s.io
@@ -692,6 +776,10 @@ webhooks:
         path: /validate-kubeflow-org-v2beta1-mpijob
     failurePolicy: Fail
     name: vmpijob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - kubeflow.org
@@ -712,6 +800,10 @@ webhooks:
         path: /validate-ray-io-v1-raycluster
     failurePolicy: Fail
     name: vraycluster.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - ray.io
@@ -732,6 +824,10 @@ webhooks:
         path: /validate-ray-io-v1-rayjob
     failurePolicy: Fail
     name: vrayjob.kb.io
+    {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+    namespaceSelector:
+      {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+    {{- end }}
     rules:
       - apiGroups:
           - ray.io
diff --git a/cmd/kueueviz/backend/handlers/handlers.go b/cmd/kueueviz/backend/handlers/handlers.go
index 4bd17b0fd..20c90d52b 100644
--- a/cmd/kueueviz/backend/handlers/handlers.go
+++ b/cmd/kueueviz/backend/handlers/handlers.go
@@ -19,10 +19,12 @@ package handlers
 import (
 	"github.com/gin-gonic/gin"
 	"k8s.io/client-go/dynamic"
-	"k8s.io/client-go/kubernetes"
 )
 
-func InitializeWebSocketRoutes(router *gin.Engine, dynamicClient dynamic.Interface, k8sClient *kubernetes.Clientset) {
+func InitializeWebSocketRoutes(router *gin.Engine, dynamicClient dynamic.Interface) {
+	// Namespaces
+	router.GET("/ws/namespaces", NamespacesWebSocketHandler(dynamicClient))
+
 	// Workloads
 	router.GET("/ws/workloads", WorkloadsWebSocketHandler(dynamicClient))
 	router.GET("/ws/workloads/dashboard", WorkloadsDashboardWebSocketHandler(dynamicClient))
diff --git a/cmd/kueueviz/backend/handlers/namespaces.go b/cmd/kueueviz/backend/handlers/namespaces.go
new file mode 100644
index 000000000..ffa684fed
--- /dev/null
+++ b/cmd/kueueviz/backend/handlers/namespaces.go
@@ -0,0 +1,74 @@
+/*
+Copyright The Kubernetes Authors.
+
+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 handlers
+
+import (
+	"context"
+	"sort"
+
+	"github.com/gin-gonic/gin"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/dynamic"
+)
+
+// NamespacesWebSocketHandler streams namespaces that are related to Kueue
+func NamespacesWebSocketHandler(dynamicClient dynamic.Interface) gin.HandlerFunc {
+	return GenericWebSocketHandler(func() (any, error) {
+		return fetchNamespaces(dynamicClient)
+	})
+}
+
+// Fetch namespaces that have LocalQueues (Kueue-related namespaces)
+func fetchNamespaces(dynamicClient dynamic.Interface) (any, error) {
+	// First, get all LocalQueues to find namespaces that have them
+	localQueues, err := dynamicClient.Resource(LocalQueuesGVR()).List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	// Extract unique namespaces from LocalQueues
+	namespaceSet := make(map[string]bool)
+	for _, lq := range localQueues.Items {
+		namespace := lq.GetNamespace()
+		if namespace != "" {
+			namespaceSet[namespace] = true
+		}
+	}
+
+	// If no LocalQueues found, return empty result with proper structure
+	if len(namespaceSet) == 0 {
+		return map[string]any{
+			"namespaces": []string{},
+		}, nil
+	}
+
+	// Convert namespace set to a slice of strings (just the names)
+	var namespaceNames []string
+	for namespaceName := range namespaceSet {
+		namespaceNames = append(namespaceNames, namespaceName)
+	}
+
+	// Sort namespaces alphabetically for consistent ordering
+	sort.Strings(namespaceNames)
+
+	// Return in the same format as other endpoints
+	result := map[string]any{
+		"namespaces": namespaceNames,
+	}
+
+	return result, nil
+}
diff --git a/cmd/kueueviz/backend/handlers/workloads.go b/cmd/kueueviz/backend/handlers/workloads.go
index 3949006e1..1b97e38c7 100644
--- a/cmd/kueueviz/backend/handlers/workloads.go
+++ b/cmd/kueueviz/backend/handlers/workloads.go
@@ -26,13 +26,17 @@ import (
 )
 
 func WorkloadsWebSocketHandler(dynamicClient dynamic.Interface) gin.HandlerFunc {
-	return GenericWebSocketHandler(func() (any, error) {
-		workloads, err := fetchWorkloads(dynamicClient)
-		result := map[string]any{
-			"workloads": workloads,
-		}
-		return result, err
-	})
+	return func(c *gin.Context) {
+		// Extract namespace query parameter if provided
+		namespace := c.Query("namespace")
+		GenericWebSocketHandler(func() (any, error) {
+			workloads, err := fetchWorkloads(dynamicClient, namespace)
+			result := map[string]any{
+				"workloads": workloads,
+			}
+			return result, err
+		})(c)
+	}
 }
 
 func WorkloadDetailsWebSocketHandler(dynamicClient dynamic.Interface) gin.HandlerFunc {
@@ -45,11 +49,14 @@ func WorkloadDetailsWebSocketHandler(dynamicClient dynamic.Interface) gin.Handle
 	}
 }
 
-func fetchWorkloads(dynamicClient dynamic.Interface) (any, error) {
-	result, err := dynamicClient.Resource(WorkloadsGVR()).List(context.TODO(), metav1.ListOptions{})
+func fetchWorkloads(dynamicClient dynamic.Interface, namespace string) (any, error) {
+	// Using a single code path with Namespace() method handling empty namespace
+	result, err := dynamicClient.Resource(WorkloadsGVR()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
+
 	if err != nil {
-		return nil, fmt.Errorf("error fetching resource flavors: %v", err)
+		return nil, fmt.Errorf("error fetching workloads: %v", err)
 	}
+
 	return result, nil
 }
 
diff --git a/cmd/kueueviz/backend/handlers/workloads_dashboard.go b/cmd/kueueviz/backend/handlers/workloads_dashboard.go
index a531a7304..804ebb01a 100644
--- a/cmd/kueueviz/backend/handlers/workloads_dashboard.go
+++ b/cmd/kueueviz/backend/handlers/workloads_dashboard.go
@@ -30,17 +30,23 @@ import (
 // WorkloadsDashboardWebSocketHandler streams workloads along with attached pod details
 func WorkloadsDashboardWebSocketHandler(dynamicClient dynamic.Interface) gin.HandlerFunc {
 	return func(c *gin.Context) {
-		GenericWebSocketHandler(func() (any, error) {
-			return fetchDashboardData(dynamicClient)
-		})(c)
+		// Extract namespace query parameter if provided
+		namespace := c.Query("namespace")
+
+		// Create a closure that captures the namespace parameter
+		dataFetcher := func() (any, error) {
+			return fetchDashboardData(dynamicClient, namespace)
+		}
+
+		GenericWebSocketHandler(dataFetcher)(c)
 	}
 }
 
-func fetchDashboardData(dynamicClient dynamic.Interface) (map[string]any, error) {
+func fetchDashboardData(dynamicClient dynamic.Interface, namespace string) (map[string]any, error) {
 	resourceFlavors, _ := fetchResourceFlavors(dynamicClient)
 	clusterQueues, _ := fetchClusterQueues(dynamicClient)
 	localQueues, _ := fetchLocalQueues(dynamicClient)
-	workloads := fetchWorkloadsDashboardData(dynamicClient)
+	workloads := fetchWorkloadsDashboardData(dynamicClient, namespace)
 	result := map[string]any{
 		"flavors":       removeManagedFields(resourceFlavors),
 		"clusterQueues": removeManagedFields(clusterQueues),
@@ -50,8 +56,10 @@ func fetchDashboardData(dynamicClient dynamic.Interface) (map[string]any, error)
 	return result, nil
 }
 
-func fetchWorkloadsDashboardData(dynamicClient dynamic.Interface) any {
-	workloadList, err := dynamicClient.Resource(WorkloadsGVR()).List(context.TODO(), metav1.ListOptions{})
+func fetchWorkloadsDashboardData(dynamicClient dynamic.Interface, namespace string) any {
+	// Filter workloads by namespace if provided, otherwise fetch all
+	workloadList, err := dynamicClient.Resource(WorkloadsGVR()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
+
 	if err != nil {
 		fmt.Printf("error fetching workloads: %v", err)
 	}
diff --git a/cmd/kueueviz/backend/main.go b/cmd/kueueviz/backend/main.go
index 381d00230..aeab290b3 100644
--- a/cmd/kueueviz/backend/main.go
+++ b/cmd/kueueviz/backend/main.go
@@ -38,7 +38,7 @@ func main() {
 		}
 	}()
 
-	k8sClient, dynamicClient, err := createK8sClient()
+	_, dynamicClient, err := createK8sClient()
 	if err != nil {
 		log.Fatalf("Error creating Kubernetes client: %v", err)
 	}
@@ -49,7 +49,7 @@ func main() {
 		log.Fatalf("Error setting trusted proxies: %v", err)
 	}
 
-	handlers.InitializeWebSocketRoutes(r, dynamicClient, k8sClient)
+	handlers.InitializeWebSocketRoutes(r, dynamicClient)
 
 	viper.SetDefault("KUEUEVIZ_PORT", "8080")
 	if err := r.Run(fmt.Sprintf(":%s", viper.GetString("KUEUEVIZ_PORT"))); err != nil {
diff --git a/cmd/kueueviz/frontend/src/Dashboard.jsx b/cmd/kueueviz/frontend/src/Dashboard.jsx
index 6053e7458..0428b9813 100644
--- a/cmd/kueueviz/frontend/src/Dashboard.jsx
+++ b/cmd/kueueviz/frontend/src/Dashboard.jsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, IconButton, Collapse, Button } from '@mui/material';
+import { Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, IconButton, Collapse, Button, FormControl, InputLabel, MenuItem, Select } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { Link } from 'react-router-dom';
 import { toast, ToastContainer } from 'react-toastify';
@@ -33,8 +33,22 @@ const Dashboard = () => {
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(null);
   const [workloadsByUid, setWorkloadsByUid] = useState({});
+  const [selectedNamespace, setSelectedNamespace] = useState('');
+  
+  // Fetch namespaces from our new endpoint
+  const { data: namespacesData, error: namespacesError } = useWebSocket('/ws/namespaces');
+  const [namespaces, setNamespaces] = useState([]);
+  
+  // Fetch dashboard data with namespace filter
+  const { data: kueueData, error: kueueError } = useWebSocket(`/ws/workloads/dashboard?namespace=${selectedNamespace}`);
 
-  const { data: kueueData, error: kueueError } = useWebSocket('/ws/workloads/dashboard');
+  useEffect(() => {
+    if (namespacesData?.namespaces) {
+      // Sort namespaces alphabetically
+      const sortedNamespaces = [...namespacesData.namespaces].sort();
+      setNamespaces(sortedNamespaces);
+    }
+  }, [namespacesData]);
 
   useEffect(() => {
     if (kueueData) {
@@ -48,7 +62,7 @@ const Dashboard = () => {
         }
       });
     }
-    if (kueueError) setError(kueueError);
+    if (kueueError || namespacesError) setError(kueueError || namespacesError);
 
     setLoading(false);
   }, [kueueData, kueueError]);
@@ -140,16 +154,49 @@ const Dashboard = () => {
         <Typography variant="h5" gutterBottom style={{ marginTop: '20px' }}>
           Workloads
         </Typography>
+        
         <TableContainer component={Paper} className="tableContainerWithBorder">
           <Table>
             <TableHead>
               <TableRow>
-                <TableCell className="name-column">Namespace</TableCell>
                 <TableCell className="icon-column">
                   <IconButton variant="contained" color="primary" onClick={toggleExpandAll}>
                     {expandAll ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
                   </IconButton>
                 </TableCell>
+                <TableCell className="name-column">
+                  <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
+                    <span>Namespace</span>
+                    <FormControl variant="standard" size="small">
+                      <Select
+                        value={selectedNamespace}
+                        onChange={(e) => setSelectedNamespace(e.target.value)}
+                        displayEmpty
+                        disableUnderline
+                        renderValue={() => ''}
+                        style={{ 
+                          width: '20px',
+                          minWidth: '20px',
+                          '& .MuiSelect-select': {
+                            padding: '0px',
+                            paddingRight: '16px !important',
+                            minHeight: 'unset',
+                          }
+                        }}
+                        IconComponent={(props) => (
+                          <KeyboardArrowDownIcon {...props} style={{ fontSize: '14px', right: '2px', position: 'absolute' }} />
+                        )}
+                      >
+                        <MenuItem value="">All</MenuItem>
+                        {namespaces.map((namespace) => (
+                          <MenuItem key={namespace} value={namespace}>
+                            {namespace}
+                          </MenuItem>
+                        ))}
+                      </Select>
+                    </FormControl>
+                  </div>
+                </TableCell>
                 <TableCell className="name-column">Name</TableCell>
                 <TableCell className="pods-count-column">Pods Count</TableCell>
                 <TableCell className="status-column">Status</TableCell>
@@ -176,12 +223,12 @@ const Dashboard = () => {
                 return (
                   <React.Fragment key={workload.metadata.name}>
                     <TableRow>
-                      <TableCell className="name-column">{workload.metadata.namespace}</TableCell>
                       <TableCell className="icon-column">
                         <IconButton onClick={() => toggleRow(workload.metadata.name)}>
                           {isExpanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
                         </IconButton>
                       </TableCell>
+                      <TableCell className="name-column">{workload.metadata.namespace}</TableCell>
                       <TableCell className="name-column">
                         <Link to={`/workload/${workload.metadata.namespace}/${workload.metadata.name}`}>
                           {workload.metadata.name}
diff --git a/cmd/kueueviz/frontend/src/Workloads.jsx b/cmd/kueueviz/frontend/src/Workloads.jsx
index d8970c125..14869fd6c 100644
--- a/cmd/kueueviz/frontend/src/Workloads.jsx
+++ b/cmd/kueueviz/frontend/src/Workloads.jsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { CircularProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography } from '@mui/material';
+import { CircularProgress, FormControl, InputLabel, MenuItem, Paper, Select, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { Link } from 'react-router-dom';
 import 'react-toastify/dist/ReactToastify.css';
@@ -23,11 +23,30 @@ import './App.css';
 import ErrorMessage from './ErrorMessage';
 
 const Workloads = () => {
-  const { data: data, error } = useWebSocket('/ws/workloads');
+  const [selectedNamespace, setSelectedNamespace] = useState('');
+  
+  // Fetch namespaces from our new endpoint
+  const { data: namespacesData, error: namespacesError } = useWebSocket('/ws/namespaces');
+  const [namespaces, setNamespaces] = useState([]);
+  
+  // Fetch workloads with namespace filter
+  const workloadsUrl = selectedNamespace === '' ? '/ws/workloads' : `/ws/workloads?namespace=${selectedNamespace}`;
+  const { data: workloadsData, error: workloadsError } = useWebSocket(workloadsUrl);
   const [workloads, setWorkloads] = useState([]);
+  
   useEffect(() => {
-    setWorkloads(data?.workloads?.items || []);
-  }, [data]);
+    if (namespacesData?.namespaces) {
+      // Make sure we're getting an array of strings, not complex objects
+      const namespaceNames = namespacesData.namespaces;
+      setNamespaces(namespaceNames);
+    }
+  }, [namespacesData]);
+  
+  useEffect(() => {
+    setWorkloads(workloadsData?.workloads?.items || []);
+  }, [workloadsData]);
+
+  const error = workloadsError || namespacesError;
 
   if (error) return <ErrorMessage error={error} />;
 
@@ -45,8 +64,32 @@ const Workloads = () => {
     <Paper className="parentContainer">
       {/* Display a table with workload details */}
       <Typography variant="h5" gutterBottom style={{ marginTop: '20px' }}>Workloads</Typography>
+      
+      {/* Namespace filter dropdown */}
+      <FormControl variant="outlined" style={{ minWidth: 200, marginBottom: '20px' }}>
+        <InputLabel id="namespace-select-label">Filter by Namespace</InputLabel>
+        <Select
+          labelId="namespace-select-label"
+          value={selectedNamespace}
+          onChange={(e) => setSelectedNamespace(e.target.value)}
+          label="Filter by Namespace"
+        >
+          <MenuItem value="">All Namespaces</MenuItem>
+          {namespaces.map((namespace) => (
+            <MenuItem key={namespace} value={namespace}>
+              {namespace}
+            </MenuItem>
+          ))}
+        </Select>
+      </FormControl>
+      
       {workloads.length === 0 ? (
-        <Typography>No Workloads found.</Typography>
+        <Typography>
+          {selectedNamespace === '' 
+            ? 'No workloads found in any namespace.' 
+            : `No workloads found in namespace "${selectedNamespace}". Ready to monitor when workloads are submitted!`
+          }
+        </Typography>
       ) : (
         <TableContainer component={Paper} className="tableContainerWithBorder">
           <Table>
diff --git a/hack/internal/tools/go.mod b/hack/internal/tools/go.mod
index c371a8a1e..a799e4db3 100644
--- a/hack/internal/tools/go.mod
+++ b/hack/internal/tools/go.mod
@@ -3,7 +3,7 @@ module sigs.k8s.io/kueue/internal/tools
 go 1.24.0
 
 require (
-	github.com/gohugoio/hugo v0.147.3
+	github.com/gohugoio/hugo v0.147.5
 	github.com/golangci/golangci-lint/v2 v2.1.6
 	github.com/google/go-cmp v0.7.0
 	github.com/mikefarah/yq/v4 v4.45.4
@@ -16,7 +16,7 @@ require (
 	k8s.io/code-generator v0.32.5
 	sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240813183042-b901db121e1f
 	sigs.k8s.io/controller-tools v0.17.3
-	sigs.k8s.io/kind v0.28.0
+	sigs.k8s.io/kind v0.29.0
 	sigs.k8s.io/kustomize/kustomize/v5 v5.6.0
 	sigs.k8s.io/mdtoc v1.4.0
 	sigs.k8s.io/yaml v1.4.0
diff --git a/hack/internal/tools/go.sum b/hack/internal/tools/go.sum
index e6592d111..e6ed674bd 100644
--- a/hack/internal/tools/go.sum
+++ b/hack/internal/tools/go.sum
@@ -85,8 +85,8 @@ github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
 github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/gohugoio/hugo v0.147.3 h1:0rmefV+uJbisSRGrPT5iV3ETCdTjZrT2YwBmpEgCRVc=
-github.com/gohugoio/hugo v0.147.3/go.mod h1:OuccInajjJHOqzbWB6JoVa20Jg9vUaf+n25lO+FLqq4=
+github.com/gohugoio/hugo v0.147.5 h1:WU15Zwpfreg4O5xJW5uJ2QbQWiZhNhC3HwB72Sk79no=
+github.com/gohugoio/hugo v0.147.5/go.mod h1:Sb2COQPDPYG+tRSpePtzKytiuVDqkBivEhgIew1QbNo=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w=
@@ -345,8 +345,8 @@ sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3Ew
 sigs.k8s.io/controller-tools v0.17.3/go.mod h1:1ii+oXcYZkxcBXzwv3YZBlzjt1fvkrCGjVF73blosJI=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
-sigs.k8s.io/kind v0.28.0 h1:YLfMxeB0+8nHPs4IgpskdVerMUiYtP8jcbML0OVpUXg=
-sigs.k8s.io/kind v0.28.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o=
+sigs.k8s.io/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI=
+sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o=
 sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
 sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
 sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA=
diff --git a/hack/processing-plan.yaml b/hack/processing-plan.yaml
index 73731e57f..7260558ca 100644
--- a/hack/processing-plan.yaml
+++ b/hack/processing-plan.yaml
@@ -320,6 +320,222 @@ files:
             {{- end }}
         onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
         onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-apps-v1-statefulset"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-batch-v1-job"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-jobset-x-k8s-io-v1alpha2-jobset"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v1-jaxjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v1-paddlejob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v1-pytorchjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v1-tfjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v1-xgboostjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-leaderworkerset-x-k8s-io-v1-leaderworkerset"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-kubeflow-org-v2beta1-mpijob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-ray-io-v1-raycluster"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-ray-io-v1-rayjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-workload-codeflare-dev-v1beta2-appwrapper"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-batch-v1-job"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-jobset-x-k8s-io-v1alpha2-jobset"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v1-jaxjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v1-paddlejob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v1-pytorchjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v1-tfjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v1-xgboostjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-leaderworkerset-x-k8s-io-v1-leaderworkerset"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-kubeflow-org-v2beta1-mpijob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-ray-io-v1-raycluster"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "MutatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/mutate-ray-io-v1-rayjob"'
+      - type: INSERT_TEXT
+        key: .webhooks.[].name
+        value: |
+            {{- if (hasKey $managerConfig "managedJobsNamespaceSelector") -}}
+            namespaceSelector:
+              {{- toYaml $managerConfig.managedJobsNamespaceSelector | nindent 6 -}}
+            {{- end }}
+        onFileCondition: '.kind == "ValidatingWebhookConfiguration"'
+        onItemCondition: '.webhooks.[].clientConfig.service.path == "/validate-workload-codeflare-dev-v1beta2-appwrapper"'
   - path: ./config/components/kueueviz/*.yaml
     outputDir: ./charts/kueue/templates/kueueviz
     excludes:
diff --git a/netlify.toml b/netlify.toml
index 5c0993f81..f4c3cd721 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -4,7 +4,7 @@
   command = "npm install && hugo --gc --minify"
 
 [build.environment]
-  HUGO_VERSION = "0.147.3"
+  HUGO_VERSION = "0.147.5"
 
 [context.production.environment]
   HUGO_ENV = "production"
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
index d37e1af1f..d15e6a461 100644
--- a/pkg/cache/cache.go
+++ b/pkg/cache/cache.go
@@ -42,7 +42,6 @@ import (
 	"sigs.k8s.io/kueue/pkg/metrics"
 	"sigs.k8s.io/kueue/pkg/queue"
 	"sigs.k8s.io/kueue/pkg/resources"
-	utiltas "sigs.k8s.io/kueue/pkg/util/tas"
 	"sigs.k8s.io/kueue/pkg/workload"
 )
 
@@ -114,7 +113,7 @@ type Cache struct {
 
 	hm hierarchy.Manager[*clusterQueue, *cohort]
 
-	tasCache TASCache
+	tasCache tasCache
 }
 
 func New(client client.Client, opts ...Option) *Cache {
@@ -247,7 +246,7 @@ func (c *Cache) ActiveClusterQueues() sets.Set[kueue.ClusterQueueReference] {
 	return cqs
 }
 
-func (c *Cache) TASCache() *TASCache {
+func (c *Cache) TASCache() *tasCache {
 	return &c.tasCache
 }
 
@@ -255,6 +254,9 @@ func (c *Cache) AddOrUpdateResourceFlavor(log logr.Logger, rf *kueue.ResourceFla
 	c.Lock()
 	defer c.Unlock()
 	c.resourceFlavors[kueue.ResourceFlavorReference(rf.Name)] = rf
+	if handleTASFlavor(rf) {
+		c.tasCache.AddFlavor(rf)
+	}
 	return c.updateClusterQueues(log)
 }
 
@@ -262,28 +264,32 @@ func (c *Cache) DeleteResourceFlavor(log logr.Logger, rf *kueue.ResourceFlavor)
 	c.Lock()
 	defer c.Unlock()
 	delete(c.resourceFlavors, kueue.ResourceFlavorReference(rf.Name))
+	if handleTASFlavor(rf) {
+		c.tasCache.DeleteFlavor(kueue.ResourceFlavorReference(rf.Name))
+	}
 	return c.updateClusterQueues(log)
 }
 
-func (c *Cache) AddTopologyForFlavor(log logr.Logger, topology *kueuealpha.Topology, flv *kueue.ResourceFlavor) sets.Set[kueue.ClusterQueueReference] {
+func (c *Cache) AddOrUpdateTopology(log logr.Logger, topology *kueuealpha.Topology) sets.Set[kueue.ClusterQueueReference] {
 	c.Lock()
 	defer c.Unlock()
-	levels := utiltas.Levels(topology)
-	tasFlavor := kueue.ResourceFlavorReference(flv.Name)
-	if c.tasCache.Get(tasFlavor) == nil {
-		tasInfo := c.tasCache.NewTASFlavorCache(kueue.TopologyReference(topology.Name), levels, flv.Spec.NodeLabels, flv.Spec.Tolerations)
-		c.tasCache.Set(tasFlavor, tasInfo)
-	}
+	c.tasCache.AddTopology(topology)
 	return c.updateClusterQueues(log)
 }
 
-func (c *Cache) DeleteTopologyForFlavor(log logr.Logger, flv kueue.ResourceFlavorReference) sets.Set[kueue.ClusterQueueReference] {
+func (c *Cache) DeleteTopology(log logr.Logger, name kueue.TopologyReference) sets.Set[kueue.ClusterQueueReference] {
 	c.Lock()
 	defer c.Unlock()
-	c.tasCache.Delete(flv)
+	c.tasCache.DeleteTopology(name)
 	return c.updateClusterQueues(log)
 }
 
+func (c *Cache) CloneTASCache() map[kueue.ResourceFlavorReference]*TASFlavorCache {
+	c.RLock()
+	defer c.RUnlock()
+	return c.tasCache.Clone()
+}
+
 func (c *Cache) AddOrUpdateAdmissionCheck(log logr.Logger, ac *kueue.AdmissionCheck) sets.Set[kueue.ClusterQueueReference] {
 	c.Lock()
 	defer c.Unlock()
@@ -832,11 +838,11 @@ func (c *Cache) LocalQueueUsage(qObj *kueue.LocalQueue) (*LocalQueueUsageStats,
 				if rf, ok := c.resourceFlavors[rgFlavor]; ok {
 					flavor.NodeLabels = rf.Spec.NodeLabels
 					flavor.NodeTaints = rf.Spec.NodeTaints
-					if features.Enabled(features.TopologyAwareScheduling) && rf.Spec.TopologyName != nil {
-						if topology, ok := c.tasCache.flavors[rgFlavor]; ok {
+					if handleTASFlavor(rf) {
+						if cache := c.tasCache.Get(rgFlavor); cache != nil {
 							flavor.Topology = &kueue.TopologyInfo{
-								Name:   topology.TopologyName,
-								Levels: topology.Levels,
+								Name:   cache.flavor.TopologyName,
+								Levels: cache.topology.Levels,
 							}
 						}
 					}
@@ -855,6 +861,10 @@ func (c *Cache) LocalQueueUsage(qObj *kueue.LocalQueue) (*LocalQueueUsageStats,
 	}, nil
 }
 
+func handleTASFlavor(rf *kueue.ResourceFlavor) bool {
+	return features.Enabled(features.TopologyAwareScheduling) && rf.Spec.TopologyName != nil
+}
+
 func filterLocalQueueUsage(orig resources.FlavorResourceQuantities, resourceGroups []ResourceGroup) []kueue.LocalQueueFlavorUsage {
 	qFlvUsages := make([]kueue.LocalQueueFlavorUsage, 0, len(orig))
 	for _, rg := range resourceGroups {
diff --git a/pkg/cache/clusterqueue.go b/pkg/cache/clusterqueue.go
index 4dab24fd1..b29d35599 100644
--- a/pkg/cache/clusterqueue.go
+++ b/pkg/cache/clusterqueue.go
@@ -86,7 +86,7 @@ type clusterQueue struct {
 	resourceNode resourceNode
 	hierarchy.ClusterQueue[*cohort]
 
-	tasCache *TASCache
+	tasCache *tasCache
 
 	workloadsNotAccountedForTAS sets.Set[string]
 }
@@ -435,7 +435,7 @@ func (c *clusterQueue) addOrUpdateWorkload(log logr.Logger, w *kueue.Workload) {
 	}
 	wi := workload.NewInfo(w, c.workloadInfoOptions...)
 	c.Workloads[k] = wi
-	c.updateWorkloadUsage(log, wi, 1)
+	c.updateWorkloadUsage(log, wi, add)
 	if c.podsReadyTracking && !apimeta.IsStatusConditionTrue(w.Status.Conditions, kueue.WorkloadPodsReady) {
 		c.WorkloadsNotReady.Insert(k)
 	}
@@ -453,7 +453,7 @@ func (c *clusterQueue) deleteWorkload(log logr.Logger, w *kueue.Workload) {
 	if !exist {
 		return
 	}
-	c.updateWorkloadUsage(log, wi, -1)
+	c.updateWorkloadUsage(log, wi, subtract)
 	if c.podsReadyTracking && !apimeta.IsStatusConditionTrue(w.Status.Conditions, kueue.WorkloadPodsReady) {
 		c.WorkloadsNotReady.Delete(k)
 	}
@@ -478,29 +478,29 @@ func (q *LocalQueue) reportActiveWorkloads() {
 
 // updateWorkloadUsage updates the usage of the ClusterQueue for the workload
 // and the number of admitted workloads for local queues.
-func (c *clusterQueue) updateWorkloadUsage(log logr.Logger, wi *workload.Info, m int64) {
+func (c *clusterQueue) updateWorkloadUsage(log logr.Logger, wi *workload.Info, op usageOp) {
 	admitted := workload.IsAdmitted(wi.Obj)
 	frUsage := wi.FlavorResourceUsage()
 	for fr, q := range frUsage {
-		if m == 1 {
+		if op == add {
 			addUsage(c, fr, q)
 		}
-		if m == -1 {
+		if op == subtract {
 			removeUsage(c, fr, q)
 		}
 	}
-	c.updateWorkloadTASUsage(log, wi, m)
+	c.updateWorkloadTASUsage(log, wi, op)
 	if admitted {
-		updateFlavorUsage(frUsage, c.AdmittedUsage, m)
-		c.admittedWorkloadsCount += int(m)
+		updateFlavorUsage(frUsage, c.AdmittedUsage, op)
+		c.admittedWorkloadsCount += op.asSignedOne()
 	}
 	qKey := queue.KeyFromWorkload(wi.Obj)
 	if lq, ok := c.localQueues[qKey]; ok {
-		updateFlavorUsage(frUsage, lq.totalReserved, m)
-		lq.reservingWorkloads += int(m)
+		updateFlavorUsage(frUsage, lq.totalReserved, op)
+		lq.reservingWorkloads += op.asSignedOne()
 		if admitted {
-			lq.updateAdmittedUsage(frUsage, m)
-			lq.admittedWorkloads += int(m)
+			lq.updateAdmittedUsage(frUsage, op)
+			lq.admittedWorkloads += op.asSignedOne()
 		}
 		if features.Enabled(features.LocalQueueMetrics) {
 			lq.reportActiveWorkloads()
@@ -508,7 +508,7 @@ func (c *clusterQueue) updateWorkloadUsage(log logr.Logger, wi *workload.Info, m
 	}
 }
 
-func (c *clusterQueue) updateWorkloadTASUsage(log logr.Logger, wi *workload.Info, m int64) {
+func (c *clusterQueue) updateWorkloadTASUsage(log logr.Logger, wi *workload.Info, op usageOp) {
 	if !features.Enabled(features.TopologyAwareScheduling) || !wi.IsUsingTAS() {
 		return
 	}
@@ -525,9 +525,9 @@ func (c *clusterQueue) updateWorkloadTASUsage(log logr.Logger, wi *workload.Info
 		switch {
 		case tasFlvCache == nil:
 			log.V(2).Info("TAS flavor used by workload not found in cache", "tasFlavor", tasFlavor)
-		case m == 1:
+		case op == add:
 			tasFlvCache.addUsage(tasUsage)
-		case m == -1:
+		case op == subtract:
 			// If the workload is not accounted for TAS, we haven't called
 			// addUsage on startup, and so we don't subtract the capacity now.
 			if c.workloadsNotAccountedForTAS.Has(key) {
@@ -541,9 +541,9 @@ func (c *clusterQueue) updateWorkloadTASUsage(log logr.Logger, wi *workload.Info
 	c.workloadsNotAccountedForTAS.Delete(key)
 }
 
-func updateFlavorUsage(newUsage resources.FlavorResourceQuantities, oldUsage resources.FlavorResourceQuantities, m int64) {
+func updateFlavorUsage(newUsage resources.FlavorResourceQuantities, oldUsage resources.FlavorResourceQuantities, op usageOp) {
 	for fr, q := range newUsage {
-		oldUsage[fr] += q * m
+		oldUsage[fr] += q * int64(op.asSignedOne())
 	}
 }
 
@@ -563,10 +563,10 @@ func (c *clusterQueue) addLocalQueue(q *kueue.LocalQueue) error {
 	for _, wl := range c.Workloads {
 		if workloadBelongsToLocalQueue(wl.Obj, q) {
 			frq := wl.FlavorResourceUsage()
-			updateFlavorUsage(frq, qImpl.totalReserved, 1)
+			updateFlavorUsage(frq, qImpl.totalReserved, add)
 			qImpl.reservingWorkloads++
 			if workload.IsAdmitted(wl.Obj) {
-				qImpl.updateAdmittedUsage(frq, 1)
+				qImpl.updateAdmittedUsage(frq, add)
 				qImpl.admittedWorkloads++
 			}
 		}
diff --git a/pkg/cache/localqueue.go b/pkg/cache/localqueue.go
index 242e411f8..26c828110 100644
--- a/pkg/cache/localqueue.go
+++ b/pkg/cache/localqueue.go
@@ -40,7 +40,7 @@ func (lq *LocalQueue) GetAdmittedUsage() corev1.ResourceList {
 	return lq.admittedUsage.FlattenFlavors().ToResourceList()
 }
 
-func (lq *LocalQueue) updateAdmittedUsage(usage resources.FlavorResourceQuantities, op int64) {
+func (lq *LocalQueue) updateAdmittedUsage(usage resources.FlavorResourceQuantities, op usageOp) {
 	lq.Lock()
 	defer lq.Unlock()
 	updateFlavorUsage(usage, lq.admittedUsage, op)
diff --git a/pkg/cache/tas_cache.go b/pkg/cache/tas_cache.go
index 0502148dc..f1e73c47e 100644
--- a/pkg/cache/tas_cache.go
+++ b/pkg/cache/tas_cache.go
@@ -18,47 +18,94 @@ package cache
 
 import (
 	"maps"
+	"slices"
 	"sync"
 
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
+	kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1"
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
+	utiltas "sigs.k8s.io/kueue/pkg/util/tas"
 )
 
-type TASCache struct {
+type tasCache struct {
 	sync.RWMutex
-	client  client.Client
-	flavors map[kueue.ResourceFlavorReference]*TASFlavorCache
+	client      client.Client
+	flavors     map[kueue.ResourceFlavorReference]flavorInformation
+	topologies  map[kueue.TopologyReference]topologyInformation
+	flavorCache map[kueue.ResourceFlavorReference]*TASFlavorCache
 }
 
-func NewTASCache(client client.Client) TASCache {
-	return TASCache{
-		client:  client,
-		flavors: make(map[kueue.ResourceFlavorReference]*TASFlavorCache),
+func NewTASCache(client client.Client) tasCache {
+	return tasCache{
+		client:      client,
+		flavors:     make(map[kueue.ResourceFlavorReference]flavorInformation),
+		topologies:  make(map[kueue.TopologyReference]topologyInformation),
+		flavorCache: make(map[kueue.ResourceFlavorReference]*TASFlavorCache),
 	}
 }
 
-func (t *TASCache) Get(name kueue.ResourceFlavorReference) *TASFlavorCache {
+func (t *tasCache) Get(name kueue.ResourceFlavorReference) *TASFlavorCache {
 	t.RLock()
 	defer t.RUnlock()
-	return t.flavors[name]
+	return t.flavorCache[name]
 }
 
 // Clone returns a shallow copy of the map
-func (t *TASCache) Clone() map[kueue.ResourceFlavorReference]*TASFlavorCache {
+func (t *tasCache) Clone() map[kueue.ResourceFlavorReference]*TASFlavorCache {
 	t.RLock()
 	defer t.RUnlock()
-	return maps.Clone(t.flavors)
+	return maps.Clone(t.flavorCache)
 }
 
-func (t *TASCache) Set(name kueue.ResourceFlavorReference, info *TASFlavorCache) {
+func (t *tasCache) AddFlavor(flavor *kueue.ResourceFlavor) {
 	t.Lock()
 	defer t.Unlock()
-	t.flavors[name] = info
+	name := kueue.ResourceFlavorReference(flavor.Name)
+	if _, ok := t.flavors[name]; !ok {
+		flavorInfo := flavorInformation{
+			TopologyName: *flavor.Spec.TopologyName,
+			NodeLabels:   maps.Clone(flavor.Spec.NodeLabels),
+			Tolerations:  slices.Clone(flavor.Spec.Tolerations),
+		}
+		t.flavors[name] = flavorInfo
+		if tInfo, ok := t.topologies[flavorInfo.TopologyName]; ok {
+			t.flavorCache[name] = t.NewTASFlavorCache(tInfo, flavorInfo)
+		}
+	}
+}
+
+func (t *tasCache) AddTopology(topology *kueuealpha.Topology) {
+	t.Lock()
+	defer t.Unlock()
+	name := kueue.TopologyReference(topology.Name)
+	if _, ok := t.topologies[name]; !ok {
+		tInfo := topologyInformation{
+			Levels: utiltas.Levels(topology),
+		}
+		t.topologies[name] = tInfo
+		for fName, flavorInfo := range t.flavors {
+			if flavorInfo.TopologyName == name {
+				t.flavorCache[fName] = t.NewTASFlavorCache(tInfo, flavorInfo)
+			}
+		}
+	}
 }
 
-func (t *TASCache) Delete(name kueue.ResourceFlavorReference) {
+func (t *tasCache) DeleteFlavor(name kueue.ResourceFlavorReference) {
 	t.Lock()
 	defer t.Unlock()
 	delete(t.flavors, name)
+	delete(t.flavorCache, name)
+}
+
+func (t *tasCache) DeleteTopology(name kueue.TopologyReference) {
+	t.Lock()
+	defer t.Unlock()
+	delete(t.topologies, name)
+	for flavor, c := range t.flavorCache {
+		if c.flavor.TopologyName == name {
+			delete(t.flavorCache, flavor)
+		}
+	}
 }
diff --git a/pkg/cache/tas_flavor.go b/pkg/cache/tas_flavor.go
index 94657f0d7..22dd7b248 100644
--- a/pkg/cache/tas_flavor.go
+++ b/pkg/cache/tas_flavor.go
@@ -50,37 +50,55 @@ const (
 	subtract
 )
 
-type TASFlavorCache struct {
-	sync.RWMutex
-
-	client client.Client
+func (u usageOp) asSignedOne() int {
+	if u == add {
+		return 1
+	}
+	return -1
+}
 
-	// TopologyName indicates the name of the topology specified in the
+type flavorInformation struct {
+	// Name indicates the name of the topology specified in the
 	// ResourceFlavor spec.topologyName field.
 	TopologyName kueue.TopologyReference
+
 	// nodeLabels is a map of nodeLabels defined in the ResourceFlavor object.
 	NodeLabels map[string]string
+	// tolerations represents the list of tolerations specified for the resource
+	// flavor
+	Tolerations []corev1.Toleration
+}
+
+type topologyInformation struct {
 	// levels is a list of levels defined in the Topology object referenced
 	// by the flavor corresponding to the cache.
 	Levels []string
+}
 
-	// tolerations represents the list of tolerations specified for the resource
-	// flavor
-	Tolerations []corev1.Toleration
+type TASFlavorCache struct {
+	sync.RWMutex
+
+	client client.Client
+
+	// topology represents the part of the Topology specification, e.g. the list
+	// of topology levels, relevant for TAS-scheduling.
+	topology topologyInformation
+
+	// flavor represents the part of the ResourceFlavor specification, e.g. the
+	// list of node labels and tolerations, relevant for TAS-scheduling.
+	flavor flavorInformation
 
 	// usage maintains the usage per topology domain
 	usage map[utiltas.TopologyDomainID]resources.Requests
 }
 
-func (t *TASCache) NewTASFlavorCache(topologyName kueue.TopologyReference, levels []string, nodeLabels map[string]string,
-	tolerations []corev1.Toleration) *TASFlavorCache {
+func (t *tasCache) NewTASFlavorCache(topologyInfo topologyInformation,
+	flavorInfo flavorInformation) *TASFlavorCache {
 	return &TASFlavorCache{
-		client:       t.client,
-		TopologyName: topologyName,
-		Levels:       slices.Clone(levels),
-		NodeLabels:   maps.Clone(nodeLabels),
-		Tolerations:  slices.Clone(tolerations),
-		usage:        make(map[utiltas.TopologyDomainID]resources.Requests),
+		client:   t.client,
+		topology: topologyInfo,
+		flavor:   flavorInfo,
+		usage:    make(map[utiltas.TopologyDomainID]resources.Requests),
 	}
 }
 
@@ -88,8 +106,8 @@ func (c *TASFlavorCache) snapshot(ctx context.Context) (*TASFlavorSnapshot, erro
 	log := ctrl.LoggerFrom(ctx)
 	nodes := &corev1.NodeList{}
 
-	var requiredLabels client.MatchingLabels = maps.Clone(c.NodeLabels)
-	var requiredLabelKeys client.HasLabels = slices.Clone(c.Levels)
+	var requiredLabels client.MatchingLabels = maps.Clone(c.flavor.NodeLabels)
+	var requiredLabelKeys client.HasLabels = slices.Clone(c.topology.Levels)
 
 	err := c.client.List(ctx, nodes, requiredLabels, requiredLabelKeys, client.MatchingFields{
 		indexer.ReadyNode:       "true",
@@ -113,13 +131,25 @@ func (c *TASFlavorCache) snapshot(ctx context.Context) (*TASFlavorSnapshot, erro
 	return c.snapshotForNodes(log, nodes.Items, pods.Items), nil
 }
 
+func (c *TASFlavorCache) NodeLabels() map[string]string {
+	return c.flavor.NodeLabels
+}
+
+func (c *TASFlavorCache) Topology() kueue.TopologyReference {
+	return c.flavor.TopologyName
+}
+
+func (c *TASFlavorCache) TopologyLevels() []string {
+	return c.topology.Levels
+}
+
 func (c *TASFlavorCache) snapshotForNodes(log logr.Logger, nodes []corev1.Node, pods []corev1.Pod) *TASFlavorSnapshot {
 	c.RLock()
 	defer c.RUnlock()
 
-	log.V(3).Info("Constructing TAS snapshot", "nodeLabels", c.NodeLabels,
-		"levels", c.Levels, "nodeCount", len(nodes), "podCount", len(pods))
-	snapshot := newTASFlavorSnapshot(log, c.TopologyName, c.Levels, c.Tolerations)
+	log.V(3).Info("Constructing TAS snapshot", "nodeLabels", c.flavor.NodeLabels,
+		"levels", c.topology.Levels, "nodeCount", len(nodes), "podCount", len(pods))
+	snapshot := newTASFlavorSnapshot(log, c.flavor.TopologyName, c.topology.Levels, c.flavor.Tolerations)
 	nodeToDomain := make(map[string]utiltas.TopologyDomainID)
 	for _, node := range nodes {
 		nodeToDomain[node.Name] = snapshot.addNode(node)
diff --git a/pkg/controller/tas/resource_flavor.go b/pkg/controller/tas/resource_flavor.go
index 909884518..0c6ce685f 100644
--- a/pkg/controller/tas/resource_flavor.go
+++ b/pkg/controller/tas/resource_flavor.go
@@ -19,6 +19,7 @@ package tas
 import (
 	"context"
 
+	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/tools/record"
@@ -35,7 +36,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/source"
 
 	configapi "sigs.k8s.io/kueue/apis/config/v1beta1"
-	kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1"
 	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
 	"sigs.k8s.io/kueue/pkg/cache"
 	"sigs.k8s.io/kueue/pkg/constants"
@@ -44,9 +44,9 @@ import (
 )
 
 type rfReconciler struct {
+	log      logr.Logger
 	queues   *queue.Manager
 	cache    *cache.Cache
-	tasCache *cache.TASCache
 	client   client.Client
 	recorder record.EventRecorder
 }
@@ -60,17 +60,17 @@ var _ predicate.TypedPredicate[*kueue.ResourceFlavor] = (*rfReconciler)(nil)
 
 func newRfReconciler(c client.Client, queues *queue.Manager, cache *cache.Cache, recorder record.EventRecorder) *rfReconciler {
 	return &rfReconciler{
+		log:      ctrl.Log.WithName(TASResourceFlavorController),
 		client:   c,
 		queues:   queues,
 		cache:    cache,
-		tasCache: cache.TASCache(),
 		recorder: recorder,
 	}
 }
 
 func (r *rfReconciler) setupWithManager(mgr ctrl.Manager, cache *cache.Cache, cfg *configapi.Configuration) (string, error) {
 	nodeHandler := nodeHandler{
-		tasCache: cache.TASCache(),
+		cache: cache,
 	}
 	return TASResourceFlavorController, builder.TypedControllerManagedBy[reconcile.Request](mgr).
 		Named("tas_resource_flavor_controller").
@@ -89,7 +89,7 @@ var _ handler.EventHandler = (*nodeHandler)(nil)
 
 // nodeHandler handles node update events.
 type nodeHandler struct {
-	tasCache *cache.TASCache
+	cache *cache.Cache
 }
 
 func (h *nodeHandler) Create(_ context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
@@ -123,8 +123,8 @@ func (h *nodeHandler) queueReconcileForNode(node *corev1.Node, q workqueue.Typed
 		return
 	}
 	// trigger reconcile for TAS flavors affected by the node being created or updated
-	for name, flavor := range h.tasCache.Clone() {
-		if nodeBelongsToFlavor(node, flavor.NodeLabels, flavor.Levels) {
+	for name, cache := range h.cache.CloneTASCache() {
+		if nodeBelongsToFlavor(node, cache.NodeLabels(), cache.TopologyLevels()) {
 			q.AddAfter(reconcile.Request{NamespacedName: types.NamespacedName{
 				Name: string(name),
 			}}, constants.UpdatesBatchPeriod)
@@ -144,20 +144,8 @@ func (r *rfReconciler) Reconcile(ctx context.Context, req reconcile.Request) (re
 		if client.IgnoreNotFound(err) != nil {
 			return reconcile.Result{}, err
 		}
-		r.tasCache.Delete(kueue.ResourceFlavorReference(req.Name))
 	}
 	if flv.Spec.TopologyName != nil {
-		flavorReference := kueue.ResourceFlavorReference(flv.Name)
-		if r.tasCache.Get(flavorReference) == nil {
-			topology := kueuealpha.Topology{}
-			if err := r.client.Get(ctx, types.NamespacedName{Name: string(*flv.Spec.TopologyName)}, &topology); err != nil {
-				return reconcile.Result{}, client.IgnoreNotFound(err)
-			}
-			log.V(3).Info("Adding topology to cache for flavor", "flavorName", flv.Name)
-			r.cache.AddTopologyForFlavor(log, &topology, flv)
-		} else {
-			log.V(3).Info("Skip topology update to cache as already present for flavor", "flavorName", flv.Name)
-		}
 		// requeue inadmissible workloads as a change to the resource flavor
 		// or the set of nodes can allow admitting a workload which was
 		// previously inadmissible.
@@ -169,16 +157,20 @@ func (r *rfReconciler) Reconcile(ctx context.Context, req reconcile.Request) (re
 }
 
 func (r *rfReconciler) Create(event event.TypedCreateEvent[*kueue.ResourceFlavor]) bool {
-	return event.Object.Spec.TopologyName != nil
-}
-
-func (r *rfReconciler) Delete(event event.TypedDeleteEvent[*kueue.ResourceFlavor]) bool {
 	if event.Object.Spec.TopologyName != nil {
-		r.tasCache.Delete(kueue.ResourceFlavorReference(event.Object.Name))
+		log := r.log.WithValues("flavor", event.Object.Name)
+		log.V(2).Info("Topology TAS ResourceFlavor event")
+
+		r.cache.AddOrUpdateResourceFlavor(log, event.Object)
+		return true
 	}
 	return false
 }
 
+func (r *rfReconciler) Delete(event event.TypedDeleteEvent[*kueue.ResourceFlavor]) bool {
+	return event.Object.Spec.TopologyName != nil
+}
+
 func (r *rfReconciler) Update(event event.TypedUpdateEvent[*kueue.ResourceFlavor]) bool {
 	switch {
 	case ptr.Equal(event.ObjectOld.Spec.TopologyName, event.ObjectNew.Spec.TopologyName):
@@ -187,7 +179,6 @@ func (r *rfReconciler) Update(event event.TypedUpdateEvent[*kueue.ResourceFlavor
 		return true
 	default:
 		// topologyName was set so is changed or removed
-		r.tasCache.Delete(kueue.ResourceFlavorReference(*event.ObjectOld.Spec.TopologyName))
 		return event.ObjectNew.Spec.TopologyName != nil
 	}
 }
diff --git a/pkg/controller/tas/topology_controller.go b/pkg/controller/tas/topology_controller.go
index ee2a90711..5ef80eb5a 100644
--- a/pkg/controller/tas/topology_controller.go
+++ b/pkg/controller/tas/topology_controller.go
@@ -41,7 +41,6 @@ import (
 	"sigs.k8s.io/kueue/pkg/cache"
 	"sigs.k8s.io/kueue/pkg/constants"
 	"sigs.k8s.io/kueue/pkg/controller/core"
-	"sigs.k8s.io/kueue/pkg/controller/tas/indexer"
 	"sigs.k8s.io/kueue/pkg/queue"
 )
 
@@ -54,7 +53,6 @@ type topologyReconciler struct {
 	client           client.Client
 	queues           *queue.Manager
 	cache            *cache.Cache
-	tasCache         *cache.TASCache
 	topologyUpdateCh chan event.GenericEvent
 }
 
@@ -67,7 +65,6 @@ func newTopologyReconciler(c client.Client, queues *queue.Manager, cache *cache.
 		client:           c,
 		queues:           queues,
 		cache:            cache,
-		tasCache:         cache.TASCache(),
 		topologyUpdateCh: make(chan event.GenericEvent, updateChBuffer),
 	}
 }
@@ -102,8 +99,8 @@ func (r *topologyReconciler) Reconcile(ctx context.Context, req reconcile.Reques
 	if !topology.DeletionTimestamp.IsZero() {
 		if controllerutil.ContainsFinalizer(topology, kueue.ResourceInUseFinalizerName) {
 			var flavors []kueue.ResourceFlavorReference
-			for flName, flCache := range r.tasCache.Clone() {
-				if flCache.TopologyName == kueue.TopologyReference(topology.Name) {
+			for flName, flCache := range r.cache.CloneTASCache() {
+				if flCache.Topology() == kueue.TopologyReference(topology.Name) {
 					flavors = append(flavors, flName)
 				}
 			}
@@ -135,28 +132,8 @@ func (r *topologyReconciler) Create(e event.TypedCreateEvent[*kueuealpha.Topolog
 	log := r.log.WithValues("topology", klog.KObj(e.Object))
 	log.V(2).Info("Topology create event")
 
-	ctx := context.Background()
-
-	flavors := &kueue.ResourceFlavorList{}
-	if err := r.client.List(ctx, flavors, client.MatchingFields{indexer.ResourceFlavorTopologyNameKey: e.Object.Name}); err != nil {
-		log.Error(err, "Could not list resource flavors")
-		return true
-	}
-
 	defer r.queues.NotifyTopologyUpdateWatchers(nil, e.Object)
-
-	// Update the cache to account for the created topology, before
-	// notifying the listeners.
-	for _, flv := range flavors.Items {
-		if flv.Spec.TopologyName == nil {
-			continue
-		}
-		if *flv.Spec.TopologyName == kueue.TopologyReference(e.Object.Name) {
-			log.V(3).Info("Updating Topology cache for flavor", "flavor", flv.Name)
-			r.cache.AddTopologyForFlavor(log, e.Object, &flv)
-		}
-	}
-
+	r.cache.AddOrUpdateTopology(log, e.Object)
 	return true
 }
 
@@ -169,14 +146,7 @@ func (r *topologyReconciler) Delete(e event.TypedDeleteEvent[*kueuealpha.Topolog
 	log.V(2).Info("Topology delete event")
 
 	defer r.queues.NotifyTopologyUpdateWatchers(e.Object, nil)
-	// Update the cache to account for the deleted topology, before notifying
-	// the listeners.
-	for flName, flCache := range r.tasCache.Clone() {
-		if kueue.TopologyReference(e.Object.Name) == flCache.TopologyName {
-			log.V(3).Info("Deleting topology from cache for flavor", "flavorName", flName)
-			r.cache.DeleteTopologyForFlavor(log, flName)
-		}
-	}
+	r.cache.DeleteTopology(log, kueue.TopologyReference(e.Object.Name))
 	return true
 }
 



Test patch:
diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go
index 3cc7414b3..c1596756a 100644
--- a/pkg/cache/cache_test.go
+++ b/pkg/cache/cache_test.go
@@ -3886,8 +3886,18 @@ func TestSnapshotError(t *testing.T) {
 	cache := New(client)
 	cache.AddOrUpdateResourceFlavor(log, &flavor)
 	if flavor.Spec.TopologyName != nil {
-		tasFlavorCache := cache.tasCache.NewTASFlavorCache(*flavor.Spec.TopologyName, []string{corev1.LabelHostname}, flavor.Spec.NodeLabels, flavor.Spec.Tolerations)
-		cache.tasCache.Set(kueue.ResourceFlavorReference(flavor.Name), tasFlavorCache)
+		cache.AddOrUpdateTopology(log, &kueuealpha.Topology{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: string(*flavor.Spec.TopologyName),
+			},
+			Spec: kueuealpha.TopologySpec{
+				Levels: []kueuealpha.TopologyLevel{
+					{
+						NodeLabel: corev1.LabelHostname,
+					},
+				},
+			},
+		})
 	}
 	if err := cache.AddClusterQueue(ctx, &clusterQueue); err != nil {
 		t.Fatalf("failed to add CQ: %v", err)
diff --git a/pkg/cache/clusterqueue_test.go b/pkg/cache/clusterqueue_test.go
index 72bedc42e..f9bc339ce 100644
--- a/pkg/cache/clusterqueue_test.go
+++ b/pkg/cache/clusterqueue_test.go
@@ -595,7 +595,7 @@ func TestClusterQueueReadinessWithTAS(t *testing.T) {
 			cqCache.AddOrUpdateResourceFlavor(log, rf)
 
 			if !tc.skipTopology {
-				cqCache.AddTopologyForFlavor(log, topology, rf)
+				cqCache.AddOrUpdateTopology(log, topology)
 			}
 
 			mkAC := utiltesting.MakeAdmissionCheck("mk-check").ControllerName(kueue.MultiKueueControllerName).Active(metav1.ConditionTrue).Obj()
diff --git a/pkg/cache/tas_cache_test.go b/pkg/cache/tas_cache_test.go
index 8078f7545..3f823e27d 100644
--- a/pkg/cache/tas_cache_test.go
+++ b/pkg/cache/tas_cache_test.go
@@ -2078,7 +2078,15 @@ func TestFindTopologyAssignment(t *testing.T) {
 			client := clientBuilder.Build()
 
 			tasCache := NewTASCache(client)
-			tasFlavorCache := tasCache.NewTASFlavorCache("default", tc.levels, tc.nodeLabels, tc.tolerations)
+			topologyInformation := topologyInformation{
+				Levels: tc.levels,
+			}
+			flavorInformation := flavorInformation{
+				TopologyName: "default",
+				NodeLabels:   tc.nodeLabels,
+				Tolerations:  tc.tolerations,
+			}
+			tasFlavorCache := tasCache.NewTASFlavorCache(topologyInformation, flavorInformation)
 
 			snapshot, err := tasFlavorCache.snapshot(ctx)
 			if err != nil {
@@ -2225,7 +2233,13 @@ func buildSnapshot(ctx context.Context, t *testing.T, nodes []corev1.Node, level
 	client := clientBuilder.Build()
 
 	tasCache := NewTASCache(client)
-	tasFlavorCache := tasCache.NewTASFlavorCache("default", levels, map[string]string{}, []corev1.Toleration{})
+	topologyInformation := topologyInformation{
+		Levels: levels,
+	}
+	flavorInformation := flavorInformation{
+		TopologyName: "default",
+	}
+	tasFlavorCache := tasCache.NewTASFlavorCache(topologyInformation, flavorInformation)
 
 	snapshot, err := tasFlavorCache.snapshot(ctx)
 	if err != nil {
diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go
index 41154d974..a38bbb658 100644
--- a/pkg/scheduler/scheduler_test.go
+++ b/pkg/scheduler/scheduler_test.go
@@ -6812,10 +6812,7 @@ func TestScheduleForTAS(t *testing.T) {
 				cqCache.AddOrUpdateResourceFlavor(log, &flavor)
 				if flavor.Spec.TopologyName != nil {
 					t := topologyByName[*flavor.Spec.TopologyName]
-					tasCache := cqCache.TASCache()
-					levels := utiltas.Levels(&t)
-					tasFlavorCache := tasCache.NewTASFlavorCache(*flavor.Spec.TopologyName, levels, flavor.Spec.NodeLabels, flavor.Spec.Tolerations)
-					tasCache.Set(kueue.ResourceFlavorReference(flavor.Name), tasFlavorCache)
+					cqCache.AddOrUpdateTopology(log, &t)
 				}
 			}
 			for _, cq := range tc.clusterQueues {
@@ -7378,10 +7375,7 @@ func TestScheduleForTASPreemption(t *testing.T) {
 				cqCache.AddOrUpdateResourceFlavor(log, &flavor)
 				if flavor.Spec.TopologyName != nil {
 					t := topologyByName[*flavor.Spec.TopologyName]
-					tasCache := cqCache.TASCache()
-					levels := utiltas.Levels(&t)
-					tasFlavorCache := tasCache.NewTASFlavorCache(*flavor.Spec.TopologyName, levels, flavor.Spec.NodeLabels, flavor.Spec.Tolerations)
-					tasCache.Set(kueue.ResourceFlavorReference(flavor.Name), tasFlavorCache)
+					cqCache.AddOrUpdateTopology(log, &t)
 				}
 			}
 			for _, cq := range tc.clusterQueues {
@@ -8647,10 +8641,7 @@ func TestScheduleForTASCohorts(t *testing.T) {
 				cqCache.AddOrUpdateResourceFlavor(log, &flavor)
 				if flavor.Spec.TopologyName != nil {
 					t := topologyByName[*flavor.Spec.TopologyName]
-					tasCache := cqCache.TASCache()
-					levels := utiltas.Levels(&t)
-					tasFlavorCache := tasCache.NewTASFlavorCache(*flavor.Spec.TopologyName, levels, flavor.Spec.NodeLabels, flavor.Spec.Tolerations)
-					tasCache.Set(kueue.ResourceFlavorReference(flavor.Name), tasFlavorCache)
+					cqCache.AddOrUpdateTopology(log, &t)
 				}
 			}
 			for _, cq := range tc.clusterQueues {