Repository: kubernetes-sigs/kueue

Issue:
[Feature Request] Kueue Dashboard resources support View YAML
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:

when i use kueue web dashboard(kubeviz) view resources.

I wonder if it's possible to provide the ability to view YAML.
(workload,localqueue,clusterqueue,cohort,resourceflavor)

**Why is this needed**:

In most cases, in addition to the basic resource definitions, I would like to see detailed definitions.

**Completion requirements**:

This enhancement requires the following artifacts:

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

The artifacts should be linked in subsequent comments.


Discussion:
@mimowo: +1 for the feature known from k9s, we could also add an option to see describe, based on the k9s inspiration (might be separate issue)@tenzen-y: /area dashboard

Solution patch:
diff --git a/cmd/kueueviz/backend/README.md b/cmd/kueueviz/backend/README.md
index 2e21bdb10..32db7809d 100644
--- a/cmd/kueueviz/backend/README.md
+++ b/cmd/kueueviz/backend/README.md
@@ -44,6 +44,7 @@ KUEUE_VIZ_PORT=8181 ./kueue_ws_app
 | Environment variables | Description              | Default value |
 | --------------------- | ------------------------ | ------------- |
 | `KUEUE_VIZ_PORT`      | Default application port | 8080          |
+| `GIN_MODE`            | Gin mode                 | debug         |
 
 ## Endpoints
 
@@ -63,3 +64,9 @@ KUEUE_VIZ_PORT=8181 ./kueue_ws_app
 | `/ws/cohort/{cohort_name}`                        | Streams updates for a specific cohort   |
 | `/ws/workload/{namespace}/{workload_name}`        | Streams updates for a specific workload |
 | `/ws/workload/{namespace}/{workload_name}/events` | Streams events for a specific workload  |
+
+### REST API
+
+| Endpoint                                          | Description                             |
+| ------------------------------------------------- | --------------------------------------- |
+| `/api/{resource_type}/{name}?namespace={namespace}&output={output_type}` | Returns content for a specific resource and output type |
diff --git a/cmd/kueueviz/backend/config/server.go b/cmd/kueueviz/backend/config/server.go
new file mode 100644
index 000000000..cdf8b6581
--- /dev/null
+++ b/cmd/kueueviz/backend/config/server.go
@@ -0,0 +1,81 @@
+/*
+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 config
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+	"github.com/spf13/viper"
+	"kueueviz/middleware"
+
+	_ "net/http/pprof"
+)
+
+// ServerConfig holds server configuration
+type ServerConfig struct {
+	Port string
+}
+
+// NewServerConfig creates a new server configuration
+func NewServerConfig() *ServerConfig {
+	viper.AutomaticEnv()
+	viper.SetDefault("KUEUEVIZ_PORT", "8080")
+
+	return &ServerConfig{
+		Port: viper.GetString("KUEUEVIZ_PORT"),
+	}
+}
+
+// SetupPprof starts the pprof server in development mode
+func SetupPprof() {
+	if gin.Mode() != gin.ReleaseMode {
+		go func() {
+			log.Println("Starting pprof server on localhost:6060")
+			if err := http.ListenAndServe("localhost:6060", nil); err != nil {
+				log.Printf("Error starting pprof server: %v", err)
+			}
+		}()
+	}
+}
+
+// SetupGinEngine creates and configures the Gin engine with middleware
+func SetupGinEngine() (*gin.Engine, error) {
+	r := gin.New()
+	r.Use(gin.Logger())
+	r.Use(gin.Recovery())
+
+	// Configure CORS with environment-aware settings
+	corsMiddleware, err := middleware.SetupCORS()
+	if err != nil {
+		return nil, fmt.Errorf("error setting up CORS: %v", err)
+	}
+	r.Use(corsMiddleware)
+
+	if err := r.SetTrustedProxies(nil); err != nil {
+		return nil, fmt.Errorf("error setting trusted proxies: %v", err)
+	}
+
+	return r, nil
+}
+
+// GetServerAddress returns the formatted server address
+func (c *ServerConfig) GetServerAddress() string {
+	return fmt.Sprintf(":%s", c.Port)
+}
diff --git a/cmd/kueueviz/backend/go.mod b/cmd/kueueviz/backend/go.mod
index 7a0bc0520..54078a7fd 100644
--- a/cmd/kueueviz/backend/go.mod
+++ b/cmd/kueueviz/backend/go.mod
@@ -3,6 +3,7 @@ module kueueviz
 go 1.24.0
 
 require (
+	github.com/gin-contrib/cors v1.7.6
 	github.com/gin-gonic/gin v1.10.1
 	github.com/gorilla/websocket v1.5.3
 	k8s.io/api v0.32.7
@@ -23,54 +24,53 @@ require (
 )
 
 require (
-	github.com/bytedance/sonic v1.11.6 // indirect
-	github.com/bytedance/sonic/loader v0.1.1 // indirect
-	github.com/cloudwego/base64x v0.1.4 // indirect
-	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/bytedance/sonic v1.13.3 // indirect
+	github.com/bytedance/sonic/loader v0.2.4 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
-	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.9 // indirect
+	github.com/gin-contrib/sse v1.1.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/swag v0.23.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/go-playground/validator/v10 v10.20.0 // indirect
-	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/go-playground/validator/v10 v10.26.0 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
-	github.com/google/go-cmp v0.6.0 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.10 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
-	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect
 	github.com/spf13/viper v1.20.1
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
-	github.com/ugorji/go/codec v1.2.12 // indirect
+	github.com/ugorji/go/codec v1.3.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/arch v0.8.0 // indirect
-	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/arch v0.18.0 // indirect
+	golang.org/x/crypto v0.39.0 // indirect
+	golang.org/x/net v0.41.0 // indirect
 	golang.org/x/oauth2 v0.27.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/term v0.30.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
+	golang.org/x/sys v0.33.0 // indirect
+	golang.org/x/term v0.32.0 // indirect
+	golang.org/x/text v0.26.0 // indirect
 	golang.org/x/time v0.8.0 // indirect
-	google.golang.org/protobuf v1.36.1 // indirect
+	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -79,5 +79,5 @@ require (
 	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
 	sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
-	sigs.k8s.io/yaml v1.4.0 // indirect
+	sigs.k8s.io/yaml v1.4.0
 )
diff --git a/cmd/kueueviz/backend/go.sum b/cmd/kueueviz/backend/go.sum
index c757004f7..5b45ff55c 100644
--- a/cmd/kueueviz/backend/go.sum
+++ b/cmd/kueueviz/backend/go.sum
@@ -1,10 +1,10 @@
-github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
-github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
-github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
+github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
-github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
-github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
-github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -19,10 +19,12 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
 github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
 github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
+github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
+github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
+github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
+github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
+github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
 github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -41,14 +43,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
-github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
+github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
 github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
 github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -56,8 +58,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
 github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -74,8 +76,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
-github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -101,8 +103,8 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM
 github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
 github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -136,8 +138,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
-github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
+github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -146,22 +148,21 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
 go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
-golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
+golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
+golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
+golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
 golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
 golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -170,30 +171,29 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
+golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
 golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
-golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
-google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -217,7 +217,6 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 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/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
diff --git a/cmd/kueueviz/backend/handlers/api_handlers.go b/cmd/kueueviz/backend/handlers/api_handlers.go
new file mode 100644
index 000000000..19407ef51
--- /dev/null
+++ b/cmd/kueueviz/backend/handlers/api_handlers.go
@@ -0,0 +1,96 @@
+/*
+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"
+	"fmt"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/dynamic"
+	"sigs.k8s.io/yaml"
+)
+
+// ResourceResponse represents the response structure for resource endpoints
+type ResourceResponse struct {
+	Name    string `json:"name"`
+	Type    string `json:"type"`
+	Content string `json:"content"`
+	Format  string `json:"format"`
+}
+
+var resourceGVRMap = map[string]schema.GroupVersionResource{
+	"workload":       WorkloadsGVR(),
+	"clusterqueue":   ClusterQueuesGVR(),
+	"localqueue":     LocalQueuesGVR(),
+	"resourceflavor": ResourceFlavorsGVR(),
+	"cohort":         CohortsGVR(),
+}
+
+func GetResource(dynamicClient dynamic.Interface) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		resourceType := c.Param("resourceType")
+		name := c.Param("name")
+		namespace := c.Query("namespace")
+		outputType := c.Query("output")
+
+		if outputType != "yaml" {
+			c.JSON(http.StatusBadRequest, gin.H{
+				"error": fmt.Sprintf("Unsupported output format: %s.", outputType),
+			})
+			return
+		}
+
+		gvr, ok := resourceGVRMap[resourceType]
+		if !ok {
+			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Unsupported resource type: %s", resourceType)})
+			return
+		}
+
+		resource, fetchErr := func() (any, error) {
+			if namespace != "" {
+				return dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+			}
+			return dynamicClient.Resource(gvr).Get(context.TODO(), name, metav1.GetOptions{})
+		}()
+
+		if fetchErr != nil {
+			c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"})
+			return
+		}
+
+		// Convert to YAML
+		yamlBytes, err := yaml.Marshal(resource)
+		if err != nil {
+			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process resource"})
+			return
+		}
+		content := string(yamlBytes)
+
+		response := ResourceResponse{
+			Name:    name,
+			Type:    resourceType,
+			Content: content,
+			Format:  outputType,
+		}
+
+		c.JSON(http.StatusOK, response)
+	}
+}
diff --git a/cmd/kueueviz/backend/handlers/handlers.go b/cmd/kueueviz/backend/handlers/handlers.go
index 20c90d52b..71ef1372c 100644
--- a/cmd/kueueviz/backend/handlers/handlers.go
+++ b/cmd/kueueviz/backend/handlers/handlers.go
@@ -49,3 +49,8 @@ func InitializeWebSocketRoutes(router *gin.Engine, dynamicClient dynamic.Interfa
 	router.GET("/ws/resource-flavors", ResourceFlavorsWebSocketHandler(dynamicClient))
 	router.GET("/ws/resource-flavor/:flavor_name", ResourceFlavorDetailsWebSocketHandler(dynamicClient))
 }
+
+func InitializeAPIRoutes(router *gin.Engine, dynamicClient dynamic.Interface) {
+	// Generic API route
+	router.GET("/api/:resourceType/:name", GetResource(dynamicClient))
+}
diff --git a/cmd/kueueviz/backend/main.go b/cmd/kueueviz/backend/main.go
index aeab290b3..aa3ca18b9 100644
--- a/cmd/kueueviz/backend/main.go
+++ b/cmd/kueueviz/backend/main.go
@@ -17,42 +17,37 @@ limitations under the License.
 package main
 
 import (
-	"fmt"
 	"log"
-	"net/http"
 
-	"github.com/gin-gonic/gin"
-	"github.com/spf13/viper"
+	"kueueviz/config"
 	"kueueviz/handlers"
-
-	_ "net/http/pprof"
 )
 
 func main() {
-	viper.AutomaticEnv()
-	// Start pprof server for profiling
-	go func() {
-		log.Println("Starting pprof server on :6060")
-		if err := http.ListenAndServe("localhost:6060", nil); err != nil {
-			log.Fatalf("Error starting pprof server: %v", err)
-		}
-	}()
+	// Initialize server configuration
+	serverConfig := config.NewServerConfig()
+
+	// Setup pprof for development
+	config.SetupPprof()
 
+	// Create Kubernetes client
 	_, dynamicClient, err := createK8sClient()
 	if err != nil {
 		log.Fatalf("Error creating Kubernetes client: %v", err)
 	}
-	r := gin.New()
-	r.Use(gin.Logger())
 
-	if err := r.SetTrustedProxies(nil); err != nil {
-		log.Fatalf("Error setting trusted proxies: %v", err)
+	// Setup Gin engine with middleware
+	r, err := config.SetupGinEngine()
+	if err != nil {
+		log.Fatalf("Error setting up Gin engine: %v", err)
 	}
 
+	// Initialize routes
 	handlers.InitializeWebSocketRoutes(r, dynamicClient)
+	handlers.InitializeAPIRoutes(r, dynamicClient)
 
-	viper.SetDefault("KUEUEVIZ_PORT", "8080")
-	if err := r.Run(fmt.Sprintf(":%s", viper.GetString("KUEUEVIZ_PORT"))); err != nil {
+	// Start server
+	if err := r.Run(serverConfig.GetServerAddress()); err != nil {
 		log.Fatalf("Failed to start server: %v", err)
 	}
 }
diff --git a/cmd/kueueviz/backend/middleware/cors.go b/cmd/kueueviz/backend/middleware/cors.go
new file mode 100644
index 000000000..4ab594ba7
--- /dev/null
+++ b/cmd/kueueviz/backend/middleware/cors.go
@@ -0,0 +1,112 @@
+/*
+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 middleware
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/gin-contrib/cors"
+	"github.com/gin-gonic/gin"
+	"github.com/spf13/viper"
+)
+
+// validateOrigin validates and sanitizes a CORS origin URL
+func validateOrigin(origin string) (string, bool) {
+	u, err := url.Parse(origin)
+	if err != nil {
+		return "", false
+	}
+
+	if u.Scheme != "http" && u.Scheme != "https" {
+		return "", false
+	}
+
+	if u.Hostname() == "" {
+		return "", false
+	}
+
+	cleanOrigin := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
+	return cleanOrigin, true
+}
+
+// ConfigureCORS sets up CORS configuration based on environment
+func ConfigureCORS() (cors.Config, error) {
+	config := cors.DefaultConfig()
+
+	// Get allowed origins from environment variable
+	allowedOriginsEnv := viper.GetString("KUEUEVIZ_ALLOWED_ORIGINS")
+	var allowedOrigins []string
+
+	if allowedOriginsEnv != "" {
+		// Split comma-separated origins and validate each
+		rawOrigins := strings.Split(allowedOriginsEnv, ",")
+		for _, origin := range rawOrigins {
+			origin = strings.TrimSpace(origin)
+			if cleanOrigin, valid := validateOrigin(origin); valid {
+				allowedOrigins = append(allowedOrigins, cleanOrigin)
+			} else {
+				log.Printf("Warning: Invalid origin '%s' rejected", origin)
+			}
+		}
+	} else {
+		// Default development origins (only in development mode)
+		if gin.Mode() != gin.ReleaseMode {
+			allowedOrigins = []string{
+				"http://localhost:3000",
+				"http://127.0.0.1:3000",
+			}
+			log.Println("KUEUEVIZ_ALLOWED_ORIGINS not set, using default development origins")
+		} else {
+			log.Println("Production mode: KUEUEVIZ_ALLOWED_ORIGINS must be explicitly set")
+		}
+	}
+
+	config.AllowOrigins = allowedOrigins
+	config.AllowMethods = []string{"GET", "HEAD", "OPTIONS"}
+	config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization"}
+
+	if gin.Mode() == gin.ReleaseMode {
+		log.Println("Running in production mode, applying security settings")
+		config.AllowCredentials = false
+		config.MaxAge = 12 * time.Hour
+
+		log.Printf("CORS allowed origins: %v", config.AllowOrigins)
+
+		if len(allowedOrigins) == 0 {
+			return config, errors.New("production mode requires valid CORS origins to be configured")
+		}
+	} else {
+		log.Println("Running in development mode")
+		config.AllowCredentials = true
+		config.MaxAge = 5 * time.Minute
+	}
+	return config, nil
+}
+
+// SetupCORS returns a configured CORS middleware
+func SetupCORS() (gin.HandlerFunc, error) {
+	config, err := ConfigureCORS()
+	if err != nil {
+		return nil, err
+	}
+	return cors.New(config), nil
+}
diff --git a/cmd/kueueviz/frontend/package-lock.json b/cmd/kueueviz/frontend/package-lock.json
index 923c62e9f..d8a612707 100644
--- a/cmd/kueueviz/frontend/package-lock.json
+++ b/cmd/kueueviz/frontend/package-lock.json
@@ -13,7 +13,9 @@
         "@emotion/styled": "^11.14.1",
         "@mui/icons-material": "^7.2.0",
         "@mui/material": "^7.2.0",
+        "ace-builds": "^1.43.2",
         "react": "^19.1.0",
+        "react-ace": "^14.0.1",
         "react-dom": "^19.0.0",
         "react-router-dom": "^7.7.0",
         "react-toastify": "^11.0.5"
@@ -1630,6 +1632,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/ace-builds": {
+      "version": "1.43.2",
+      "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.2.tgz",
+      "integrity": "sha512-3wzJUJX0RpMc03jo0V8Q3bSb/cKPnS7Nqqw8fVHsCCHweKMiTIxT3fP46EhjmVy6MCuxwP801ere+RW245phGw==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2005,6 +2013,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/diff-match-patch": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+      "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
+      "license": "Apache-2.0"
+    },
     "node_modules/dom-helpers": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -2499,6 +2513,20 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+      "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+      "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+      "license": "MIT"
+    },
     "node_modules/loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2784,6 +2812,23 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-ace": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-14.0.1.tgz",
+      "integrity": "sha512-z6YAZ20PNf/FqmYEic//G/UK6uw0rn21g58ASgHJHl9rfE4nITQLqthr9rHMVQK4ezwohJbp2dGrZpkq979PYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ace-builds": "^1.36.3",
+        "diff-match-patch": "^1.0.5",
+        "lodash.get": "^4.4.2",
+        "lodash.isequal": "^4.5.0",
+        "prop-types": "^15.8.1"
+      },
+      "peerDependencies": {
+        "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
     "node_modules/react-dom": {
       "version": "19.1.0",
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
diff --git a/cmd/kueueviz/frontend/package.json b/cmd/kueueviz/frontend/package.json
index da6645a6c..b27e2cac0 100644
--- a/cmd/kueueviz/frontend/package.json
+++ b/cmd/kueueviz/frontend/package.json
@@ -17,7 +17,9 @@
     "@emotion/styled": "^11.14.1",
     "@mui/icons-material": "^7.2.0",
     "@mui/material": "^7.2.0",
+    "ace-builds": "^1.43.2",
     "react": "^19.1.0",
+    "react-ace": "^14.0.1",
     "react-dom": "^19.0.0",
     "react-router-dom": "^7.7.0",
     "react-toastify": "^11.0.5"
diff --git a/cmd/kueueviz/frontend/src/ClusterQueues.jsx b/cmd/kueueviz/frontend/src/ClusterQueues.jsx
index daca3117f..306bda541 100644
--- a/cmd/kueueviz/frontend/src/ClusterQueues.jsx
+++ b/cmd/kueueviz/frontend/src/ClusterQueues.jsx
@@ -17,9 +17,10 @@ limitations under the License.
 import React, { useState, useEffect } from 'react';
 import { Link } from 'react-router-dom';
 import useWebSocket from './useWebSocket';
-import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress } from '@mui/material';
+import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress, Box } from '@mui/material';
 import './App.css';
 import ErrorMessage from './ErrorMessage';
+import ViewYamlButton from './ViewYamlButton';
 
 const ClusterQueues = () => {
   const { data: clusterQueues, error } = useWebSocket('/ws/cluster-queues');
@@ -49,6 +50,7 @@ const ClusterQueues = () => {
                 <TableCell>Admitted Workloads</TableCell>
                 <TableCell>Pending Workloads</TableCell>
                 <TableCell>Reserving Workloads</TableCell>
+                <TableCell align="right">Actions</TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
@@ -75,6 +77,14 @@ const ClusterQueues = () => {
                   <TableCell>{queue.admittedWorkloads ?? 'N/A'}</TableCell>
                   <TableCell>{queue.pendingWorkloads ?? 'N/A'}</TableCell>
                   <TableCell>{queue.reservingWorkloads ?? 'N/A'}</TableCell>
+                  <TableCell align="right">
+                    <Box display="flex" justifyContent="flex-end">
+                      <ViewYamlButton 
+                        resourceType="clusterqueue"
+                        resourceName={queue.name}
+                      />
+                    </Box>
+                  </TableCell>
                 </TableRow>
               ))}
             </TableBody>
diff --git a/cmd/kueueviz/frontend/src/Cohorts.jsx b/cmd/kueueviz/frontend/src/Cohorts.jsx
index 7eb5c4d6b..d26e637a2 100644
--- a/cmd/kueueviz/frontend/src/Cohorts.jsx
+++ b/cmd/kueueviz/frontend/src/Cohorts.jsx
@@ -16,10 +16,11 @@ limitations under the License.
 
 import React, { useState, useEffect } from 'react';
 import { Link } from 'react-router-dom';
-import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress } from '@mui/material';
+import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress, Box } from '@mui/material';
 import useWebSocket from './useWebSocket';
 import './App.css';
 import ErrorMessage from './ErrorMessage';
+import ViewYamlButton from './ViewYamlButton';
 
 const Cohorts = () => {
   const { data: cohorts, error } = useWebSocket('/ws/cohorts');
@@ -46,6 +47,7 @@ const Cohorts = () => {
                 <TableCell>Cohort Name</TableCell>
                 <TableCell>Number of Queues</TableCell>
                 <TableCell>Cluster Queue Name</TableCell>
+                <TableCell align="right">Actions</TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
@@ -66,6 +68,16 @@ const Cohorts = () => {
                       <TableCell>
                         <Link to={`/cluster-queue/${queue.name}`}>{queue.name}</Link>
                       </TableCell>
+                      {index === 0 && (
+                        <TableCell rowSpan={cohort.clusterQueues.length} align="right">
+                          <Box display="flex" justifyContent="flex-end">
+                            <ViewYamlButton 
+                              resourceType="cohort"
+                              resourceName={cohort.name}
+                            />
+                          </Box>
+                        </TableCell>
+                      )}
                     </TableRow>
                   ))
                 ) : (
@@ -75,6 +87,14 @@ const Cohorts = () => {
                     </TableCell>
                     <TableCell>{0}</TableCell>
                     <TableCell>No cluster queues found for this cohort.</TableCell>
+                    <TableCell align="right">
+                      <Box display="flex" justifyContent="flex-end">
+                        <ViewYamlButton 
+                          resourceType="cohort"
+                          resourceName={cohort.name}
+                        />
+                      </Box>
+                    </TableCell>
                   </TableRow>
                 )
               ))}
diff --git a/cmd/kueueviz/frontend/src/LocalQueues.jsx b/cmd/kueueviz/frontend/src/LocalQueues.jsx
index 2b7898a1a..f53289a8b 100644
--- a/cmd/kueueviz/frontend/src/LocalQueues.jsx
+++ b/cmd/kueueviz/frontend/src/LocalQueues.jsx
@@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { CircularProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material';
+import { CircularProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, Box } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { Link } from 'react-router-dom';
 import useWebSocket from './useWebSocket';
 import './App.css';
 import ErrorMessage from './ErrorMessage';
+import ViewYamlButton from './ViewYamlButton';
 
 const LocalQueues = () => {
   const { data: localQueues, error } = useWebSocket('/ws/local-queues');
@@ -59,6 +60,7 @@ const LocalQueues = () => {
                 <TableCell>Admitted Workloads</TableCell>
                 <TableCell>Pending Workloads</TableCell>
                 <TableCell>Reserving Workloads</TableCell>
+                <TableCell align="right">Actions</TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
@@ -81,6 +83,15 @@ const LocalQueues = () => {
                     <TableCell>{queue.status?.admittedWorkloads}</TableCell>
                     <TableCell>{queue.status?.pendingWorkloads}</TableCell>
                     <TableCell>{queue.status?.reservingWorkloads}</TableCell>
+                    <TableCell align="right">
+                      <Box display="flex" justifyContent="flex-end">
+                        <ViewYamlButton 
+                          resourceType="localqueue"
+                          resourceName={queue.name}
+                          namespace={queue.namespace}
+                        />
+                      </Box>
+                    </TableCell>
                   </TableRow>
                 ))
               ))}
diff --git a/cmd/kueueviz/frontend/src/ResourceFlavors.jsx b/cmd/kueueviz/frontend/src/ResourceFlavors.jsx
index eea61c341..fbbd7b79f 100644
--- a/cmd/kueueviz/frontend/src/ResourceFlavors.jsx
+++ b/cmd/kueueviz/frontend/src/ResourceFlavors.jsx
@@ -16,10 +16,11 @@ limitations under the License.
 
 import React, { useState, useEffect } from 'react';
 import { Link } from 'react-router-dom';
-import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress } from '@mui/material';
+import { Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress, Box } from '@mui/material';
 import useWebSocket from './useWebSocket';
 import './App.css';
 import ErrorMessage from './ErrorMessage';
+import ViewYamlButton from './ViewYamlButton';
 
 const ResourceFlavors = () => {
   const { data: flavors, error } = useWebSocket('/ws/resource-flavors');
@@ -45,6 +46,7 @@ const ResourceFlavors = () => {
               <TableRow>
                 <TableCell>Name</TableCell>
                 <TableCell>Details</TableCell>
+                <TableCell align="right">Actions</TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
@@ -54,6 +56,14 @@ const ResourceFlavors = () => {
                     <Link to={`/resource-flavor/${flavor.name}`}>{flavor.name}</Link>
                   </TableCell>
                   <TableCell>{JSON.stringify(flavor.details)}</TableCell>
+                  <TableCell align="right">
+                    <Box display="flex" justifyContent="flex-end">
+                      <ViewYamlButton 
+                        resourceType="resourceflavor"
+                        resourceName={flavor.name}
+                      />
+                    </Box>
+                  </TableCell>
                 </TableRow>
               ))}
             </TableBody>
diff --git a/cmd/kueueviz/frontend/src/ViewYamlButton.jsx b/cmd/kueueviz/frontend/src/ViewYamlButton.jsx
new file mode 100644
index 000000000..77f3a1a62
--- /dev/null
+++ b/cmd/kueueviz/frontend/src/ViewYamlButton.jsx
@@ -0,0 +1,56 @@
+/*
+Copyright 2025 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.
+*/
+
+import React, { useState } from 'react';
+import { Button } from '@mui/material';
+import YamlViewer from './YamlViewer';
+
+const ViewYamlButton = ({ resourceType, resourceName, namespace = null }) => {
+  const [yamlViewerOpen, setYamlViewerOpen] = useState(false);
+
+  const handleViewYaml = (event) => {
+    event.stopPropagation();
+    setYamlViewerOpen(true);
+  };
+
+  const handleCloseYamlViewer = () => {
+    setYamlViewerOpen(false);
+  };
+
+  return (
+    <>
+      <Button
+        variant="text"
+        size="small"
+        color="primary"
+        onClick={handleViewYaml}
+        sx={{ textTransform: 'none' }}
+      >
+        View YAML
+      </Button>
+      
+      <YamlViewer
+        open={yamlViewerOpen}
+        onClose={handleCloseYamlViewer}
+        resourceType={resourceType}
+        resourceName={resourceName}
+        namespace={namespace}
+      />
+    </>
+  );
+};
+
+export default ViewYamlButton;
diff --git a/cmd/kueueviz/frontend/src/Workloads.jsx b/cmd/kueueviz/frontend/src/Workloads.jsx
index 14869fd6c..46a0814b6 100644
--- a/cmd/kueueviz/frontend/src/Workloads.jsx
+++ b/cmd/kueueviz/frontend/src/Workloads.jsx
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { CircularProgress, FormControl, InputLabel, MenuItem, Paper, Select, 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, Box } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { Link } from 'react-router-dom';
 import 'react-toastify/dist/ReactToastify.css';
 import useWebSocket from './useWebSocket';
 import './App.css';
 import ErrorMessage from './ErrorMessage';
+import ViewYamlButton from './ViewYamlButton';
 
 const Workloads = () => {
   const [selectedNamespace, setSelectedNamespace] = useState('');
@@ -103,6 +104,7 @@ const Workloads = () => {
                 <TableCell>Preemption Reason</TableCell>
                 <TableCell>Priority</TableCell>
                 <TableCell>Priority Class Name</TableCell>
+                <TableCell align="right">Actions</TableCell>
               </TableRow>
             </TableHead>
             <TableBody>
@@ -135,10 +137,20 @@ const Workloads = () => {
                       </Tooltip>
                     </TableCell>
                     <TableCell><Link to={`/local-queue/${workload.metadata.namespace}/${workload.spec.queueName}`}>{workload.spec.queueName}</Link></TableCell>
+                    <TableCell>{workload.status?.conditions?.[0]?.type || 'Unknown'}</TableCell>
                     <TableCell>{workload.preemption?.preempted ? "Yes" : "No"}</TableCell>
                     <TableCell>{workload.preemption?.reason || "N/A"}</TableCell>
                     <TableCell>{workload.spec.priority}</TableCell>
                     <TableCell>{workload.spec.priorityClassName}</TableCell>
+                    <TableCell align="right">
+                      <Box display="flex" justifyContent="flex-end">
+                        <ViewYamlButton 
+                          resourceType="workload"
+                          resourceName={workload.metadata.name}
+                          namespace={workload.metadata.namespace}
+                        />
+                      </Box>
+                    </TableCell>
                   </TableRow>
                 ))
               ))}
diff --git a/cmd/kueueviz/frontend/src/YamlViewer.jsx b/cmd/kueueviz/frontend/src/YamlViewer.jsx
new file mode 100644
index 000000000..021faf89c
--- /dev/null
+++ b/cmd/kueueviz/frontend/src/YamlViewer.jsx
@@ -0,0 +1,188 @@
+/*
+Copyright 2025 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.
+*/
+
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import {
+  Dialog,
+  DialogTitle,
+  DialogContent,
+  Typography,
+  CircularProgress,
+  Box,
+  IconButton,
+} from '@mui/material';
+import {
+  Close as CloseIcon,
+} from '@mui/icons-material';
+import AceEditor from 'react-ace';
+import { buildResourceUrl } from './utils/urlHelper';
+
+// Import Ace Editor modes and themes
+import 'ace-builds/src-noconflict/mode-yaml';
+import 'ace-builds/src-noconflict/ext-searchbox';
+
+const useYamlFetcher = (open, resourceType, resourceName, namespace) => {
+  const [yamlContent, setYamlContent] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState('');
+
+  const fetchYaml = useCallback(async () => {
+    if (!open || !resourceType || !resourceName) return;
+
+    setLoading(true);
+    setError('');
+    setYamlContent('');
+
+    try {
+      const apiUrl = buildResourceUrl(resourceType, resourceName, {
+        namespace,
+        output: 'yaml'
+      });
+      
+      const response = await fetch(apiUrl);
+
+      if (!response.ok) {
+        throw new Error(`Failed to fetch YAML: ${response.status} ${response.statusText}`);
+      }
+
+      const data = await response.json();
+      setYamlContent(data.content);
+    } catch (err) {
+      setError(err.message);
+    } finally {
+      setLoading(false);
+    }
+  }, [open, resourceType, resourceName, namespace]);
+
+  useEffect(() => {
+    fetchYaml();
+  }, [fetchYaml]);
+
+  return { yamlContent, loading, error, refetch: fetchYaml };
+};
+
+const YamlViewer = ({ open, onClose, resourceType, resourceName, namespace = null }) => {
+  const { yamlContent, loading, error } = useYamlFetcher(open, resourceType, resourceName, namespace);
+  const editorRef = useRef(null);
+
+  // Handle keyboard shortcuts
+  useEffect(() => {
+    const handleKeyDown = (event) => {
+      if ((event.metaKey || event.ctrlKey) && event.key === 'f') {
+        return;
+      }
+      
+      // ESC to close dialog
+      if (event.key === 'Escape') {
+        if (open) {
+          handleClose();
+        }
+      }
+    };
+
+    if (open) {
+      document.addEventListener('keydown', handleKeyDown);
+      return () => {
+        document.removeEventListener('keydown', handleKeyDown);
+      };
+    }
+  }, [open]);
+
+
+
+  const handleClose = () => {
+    onClose();
+  };
+
+
+  const getResourceTitle = () => {
+    const resourceTypeName = resourceType.charAt(0).toUpperCase() + resourceType.slice(1);
+    const namespaceStr = namespace ? ` (${namespace})` : '';
+    return `${resourceTypeName}: ${resourceName}${namespaceStr}`;
+  };
+
+
+  return (
+    <Dialog
+      open={open}
+      onClose={handleClose}
+      maxWidth="lg"
+      fullWidth
+    >
+      <DialogTitle>
+        <Box display="flex" justifyContent="space-between" alignItems="center">
+          <Typography variant="h6" sx={{ color: 'text.primary' }}>
+            {getResourceTitle()}
+          </Typography>
+          <IconButton onClick={handleClose} size="small" sx={{color: 'text.primary'}}>
+            <CloseIcon />
+          </IconButton>
+        </Box>
+      </DialogTitle>
+      
+      <DialogContent 
+        dividers 
+        sx={{ 
+          backgroundColor: 'background.default', 
+          padding: 0, 
+          display: 'flex', 
+          flexDirection: 'column',
+          height: '70vh'
+        }}
+      >
+        {loading && (
+          <Box display="flex" justifyContent="center" p={4}>
+            <CircularProgress />
+          </Box>
+        )}
+
+        {error && (
+          <Box p={2}>
+            <Typography color="error" variant="body2">
+              Error: {error}
+            </Typography>
+          </Box>
+        )}
+
+        {yamlContent && !loading && (
+          <AceEditor
+            ref={editorRef}
+            mode="yaml"
+            value={yamlContent}
+            readOnly={true}
+            showPrintMargin={false}
+            highlightActiveLine={false}
+            width="100%"
+            height="100%"
+            setOptions={{
+              useWorker: false,
+              showLineNumbers: true,
+              tabSize: 2,
+              wrap: false,
+              enableBasicAutocompletion: false,
+              enableLiveAutocompletion: false,
+              enableSnippets: false,
+              showFoldWidgets: true,
+            }}
+            enableSearchBox={true}
+          />
+        )}
+      </DialogContent>
+    </Dialog>
+  );
+};
+
+export default YamlViewer;
diff --git a/cmd/kueueviz/frontend/src/useWebSocket.js b/cmd/kueueviz/frontend/src/useWebSocket.js
index 3e6c7f48b..5334ae193 100644
--- a/cmd/kueueviz/frontend/src/useWebSocket.js
+++ b/cmd/kueueviz/frontend/src/useWebSocket.js
@@ -15,14 +15,12 @@ limitations under the License.
 */
 
 import { useEffect, useState } from 'react';
-import { env } from './env'
-
-const websocketURL = env.REACT_APP_WEBSOCKET_URL;
+import { buildWebSocketUrl } from './utils/urlHelper';
 
 const useWebSocket = (url) => {
   const [data, setData] = useState(null);
   const [error, setError] = useState(null);
-  const fullUrl = `${websocketURL}${url}`;
+  const fullUrl = buildWebSocketUrl(url);
   useEffect(() => {
     const ws = new WebSocket(fullUrl);
 
@@ -40,8 +38,6 @@ const useWebSocket = (url) => {
       console.error("WebSocket error:", err);
       // Log environment variables to the console
       console.log(`Backend URL: ${fullUrl}`);
-      console.log(`REACT_APP_WEBSOCKET_URL: ${env.REACT_APP_WEBSOCKET_URL}`);
-      console.log(`VITE_WEBSOCKET_URL: ${env.VITE_WEBSOCKET_URL}`);
 
       // Function to get all properties, including inherited ones
       const getAllProperties = (obj) => {
diff --git a/cmd/kueueviz/frontend/src/utils/urlHelper.js b/cmd/kueueviz/frontend/src/utils/urlHelper.js
new file mode 100644
index 000000000..13c7feb5e
--- /dev/null
+++ b/cmd/kueueviz/frontend/src/utils/urlHelper.js
@@ -0,0 +1,104 @@
+/*
+Copyright 2025 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.
+*/
+
+import { env } from "../env";
+
+/**
+ * Gets the backend WebSocket URL from environment variables
+ * @returns {string} The WebSocket URL
+ * @throws {Error} If no backend URL is configured
+ */
+export const getBackendWebSocketUrl = () => {
+  const backendUrl = env.REACT_APP_WEBSOCKET_URL || env.VITE_WEBSOCKET_URL;
+  if (!backendUrl) {
+    throw new Error(
+      "Backend URL is not configured. Please set REACT_APP_WEBSOCKET_URL or VITE_WEBSOCKET_URL."
+    );
+  }
+  return backendUrl;
+};
+
+/**
+ * Converts a WebSocket URL to HTTP/HTTPS URL
+ * @param {string} wsUrl - The WebSocket URL
+ * @returns {string} The HTTP/HTTPS URL
+ */
+export const convertWebSocketToHttp = (wsUrl) => {
+  return wsUrl.replace("ws://", "http://").replace("wss://", "https://");
+};
+
+/**
+ * Gets the backend HTTP URL for API calls
+ * @returns {string} The HTTP URL
+ * @throws {Error} If no backend URL is configured
+ */
+export const getBackendHttpUrl = () => {
+  const wsUrl = getBackendWebSocketUrl();
+  return convertWebSocketToHttp(wsUrl);
+};
+
+/**
+ * Builds a complete WebSocket URL with the given path
+ * @param {string} path - The WebSocket path (e.g., '/ws/workloads')
+ * @returns {string} The complete WebSocket URL
+ */
+export const buildWebSocketUrl = (path) => {
+  const baseUrl = getBackendWebSocketUrl();
+  return `${baseUrl}${path}`;
+};
+
+/**
+ * Builds a complete API URL with the given path and optional query parameters
+ * @param {string} path - The API path (e.g., '/api/workload/my-workload')
+ * @param {Object} params - Optional query parameters as key-value pairs
+ * @returns {string} The complete API URL
+ */
+export const buildApiUrl = (path, params = {}) => {
+  const baseUrl = getBackendHttpUrl();
+  const url = new URL(path, baseUrl);
+
+  Object.entries(params).forEach(([key, value]) => {
+    if (value !== null && value !== undefined) {
+      url.searchParams.append(key, value);
+    }
+  });
+
+  return url.toString();
+};
+
+/**
+ * Builds a resource API URL for YAML format
+ * @param {string} resourceType - The resource type (e.g., 'workload', 'clusterqueue')
+ * @param {string} resourceName - The resource name
+ * @param {Object} options - Optional parameters
+ * @param {string} options.namespace - The namespace (for namespaced resources)
+ * @param {string} options.output - The output format (currently only 'yaml' is supported)
+ * @returns {string} The complete resource API URL
+ */
+export const buildResourceUrl = (resourceType, resourceName, options = {}) => {
+  const { namespace, output } = options;
+  const path = `/api/${resourceType}/${resourceName}`;
+
+  const params = {};
+  if (namespace) {
+    params.namespace = namespace;
+  }
+  if (output) {
+    params.output = output;
+  }
+
+  return buildApiUrl(path, params);
+};



Test patch:
diff --git a/test/e2e/kueueviz/cypress/e2e/app.cy.js b/test/e2e/kueueviz/cypress/e2e/app.cy.js
index 9933e7cb5..54a9e4969 100644
--- a/test/e2e/kueueviz/cypress/e2e/app.cy.js
+++ b/test/e2e/kueueviz/cypress/e2e/app.cy.js
@@ -86,5 +86,26 @@ describe('Kueue Dashboard', () => {
     });
   })
 
+  it('should open and close YAML viewer modal on resource-flavors page', { defaultCommandTimeout: 15000 }, () => {
+    cy.visit('/resource-flavors')
+    
+    cy.get('table').should('exist')
+    
+    cy.contains('button', 'View YAML').first().click()
+    
+    cy.get('[role="dialog"]').should('be.visible')
+    
+    cy.get('[role="dialog"]').within(() => {
+      cy.get('h6').should('exist') 
+      cy.get('button').find('svg').should('exist')
+    })
+    
+    cy.get('[role="dialog"]').within(() => {
+      cy.get('button').find('svg').click()
+    })
+    
+    cy.get('[role="dialog"]').should('not.exist')
+  })
+
   // Add more test cases here as needed
 }) 
\ No newline at end of file