Repository: envoyproxy/gateway

Issue:
Add Console for Envoy Gateway Admin
*Description*:
>Describe the desired behavior, what scenario it enables and how it
would be used.

Similar to Envoy Proxy's Web UI for the Admin Server https://www.envoyproxy.io/docs/envoy/latest/start/quick-start/admin#envoy-admin-web-ui,it would be great to add a similar Web Ui for the Envoy Gateway admin server
https://github.com/envoyproxy/gateway/blob/main/internal/admin/server.go to get information around
* pprof
* resources
* status
* stats


[optional *Relevant Links*:]
>Any extra documentation required to understand the issue.


Hints from issue comments:
@Xunzhuo: ## Overview
![Image](https://github.com/user-attachments/assets/fbd97289-8aff-4faf-b308-5e170d4f6d76)

## Resources
![Image](https://github.com/user-attachments/assets/54860d28-1776-4e35-9ec2-3351712c868e)

## Stats
![Image](https://github.com/user-attachments/assets/9f0db2c3-b049-4c56-b623-41e33b7e592f)

## Profile
![Image](https://github.com/user-attachments/assets/5a34100a-2751-40a5-97a4-d4880266ed18)@Xunzhuo: Add a egctl command: `egctl x dashboard envoy-gateway` as a follow-up@zirain: @Xunzhuo we want this land this in v1.5, It can be improved through iterations.@Xunzhuo: @zirain sure do

Solution patch:
diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml
index 42cb3e522..eaa79acd0 100644
--- a/.github/workflows/license-scan.yml
+++ b/.github/workflows/license-scan.yml
@@ -18,7 +18,7 @@ jobs:
     - name: Checkout code
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
     - name: Run scanner
-      uses: google/osv-scanner-action/osv-scanner-action@40a8940a65eab1544a6af759e43d936201a131a2  # v2.0.3
+      uses: google/osv-scanner-action/osv-scanner-action@b00f71e051ddddc6e46a193c31c8c0bf283bf9e6  # v2.1.0
       continue-on-error: true  # remove this after https://github.com/google/deps.dev/issues/146 has been resolved
       with:
         scan-args: |-
diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml
index 0112df156..687d1d72e 100644
--- a/.github/workflows/osv-scanner.yml
+++ b/.github/workflows/osv-scanner.yml
@@ -19,7 +19,7 @@ permissions:
 jobs:
   scan-scheduled:
     if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@40a8940a65eab1544a6af759e43d936201a131a2"  # v2.0.3
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@b00f71e051ddddc6e46a193c31c8c0bf283bf9e6"  # v2.1.0
     with:
       scan-args: |-
         --recursive
@@ -32,7 +32,7 @@ jobs:
 
   scan-pr:
     if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
-    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@40a8940a65eab1544a6af759e43d936201a131a2"  # v2.0.3
+    uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@b00f71e051ddddc6e46a193c31c8c0bf283bf9e6"  # v2.1.0
     with:
       scan-args: |-
         --recursive
diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go
index 0fdf1b424..41f57e954 100644
--- a/api/v1alpha1/ext_proc_types.go
+++ b/api/v1alpha1/ext_proc_types.go
@@ -68,7 +68,7 @@ type ExtProcProcessingMode struct {
 // +kubebuilder:validation:XValidation:message="BackendRefs must be used, backendRef is not supported.",rule="!has(self.backendRef)"
 // +kubebuilder:validation:XValidation:message="BackendRefs only supports Service, ServiceImport, and Backend kind.",rule="has(self.backendRefs) ? self.backendRefs.all(f, f.kind == 'Service' || f.kind == 'ServiceImport' || f.kind == 'Backend') : true"
 // +kubebuilder:validation:XValidation:message="BackendRefs only supports Core, multicluster.x-k8s.io, and gateway.envoyproxy.io groups.",rule="has(self.backendRefs) ? (self.backendRefs.all(f, f.group == \"\" || f.group == 'multicluster.x-k8s.io' || f.group == 'gateway.envoyproxy.io')) : true"
-// +kubebuilder:validation:XValidation:message="If FullDuplexStreamed body processing mode is used, FailOpen must be false.",rule="!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body) && self.processingMode.request.body == 'FullDuplexStreamed') || (has(self.processingMode.response.body) && self.processingMode.response.body == 'FullDuplexStreamed')))"
+// +kubebuilder:validation:XValidation:message="If FullDuplexStreamed body processing mode is used, FailOpen must be false.",rule="!(has(self.failOpen) && self.failOpen == true && has(self.processingMode) && ((has(self.processingMode.request) && has(self.processingMode.request.body) && self.processingMode.request.body == 'FullDuplexStreamed') || (has(self.processingMode.response) && has(self.processingMode.response.body) && self.processingMode.response.body == 'FullDuplexStreamed')))"
 type ExtProc struct {
 	BackendCluster `json:",inline"`
 
diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
index 6d3fa8c17..b1b737330 100644
--- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
+++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
@@ -1102,10 +1102,11 @@ spec:
                       ''gateway.envoyproxy.io'')) : true'
                   - message: If FullDuplexStreamed body processing mode is used, FailOpen
                       must be false.
-                    rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body)
+                    rule: '!(has(self.failOpen) && self.failOpen == true && has(self.processingMode)
+                      && ((has(self.processingMode.request) && has(self.processingMode.request.body)
                       && self.processingMode.request.body == ''FullDuplexStreamed'')
-                      || (has(self.processingMode.response.body) && self.processingMode.response.body
-                      == ''FullDuplexStreamed'')))'
+                      || (has(self.processingMode.response) && has(self.processingMode.response.body)
+                      && self.processingMode.response.body == ''FullDuplexStreamed'')))'
                 maxItems: 16
                 type: array
               lua:
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
index 5bdf56a7f..bea703237 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml
@@ -1101,10 +1101,11 @@ spec:
                       ''gateway.envoyproxy.io'')) : true'
                   - message: If FullDuplexStreamed body processing mode is used, FailOpen
                       must be false.
-                    rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body)
+                    rule: '!(has(self.failOpen) && self.failOpen == true && has(self.processingMode)
+                      && ((has(self.processingMode.request) && has(self.processingMode.request.body)
                       && self.processingMode.request.body == ''FullDuplexStreamed'')
-                      || (has(self.processingMode.response.body) && self.processingMode.response.body
-                      == ''FullDuplexStreamed'')))'
+                      || (has(self.processingMode.response) && has(self.processingMode.response.body)
+                      && self.processingMode.response.body == ''FullDuplexStreamed'')))'
                 maxItems: 16
                 type: array
               lua:
diff --git a/examples/admin-console-config.yaml b/examples/admin-console-config.yaml
new file mode 100644
index 000000000..7fda3fc39
--- /dev/null
+++ b/examples/admin-console-config.yaml
@@ -0,0 +1,155 @@
+# Example EnvoyGateway configuration with Admin Console enabled
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyGateway
+metadata:
+  name: envoy-gateway-config
+  namespace: envoy-gateway-system
+spec:
+  # Gateway API configuration
+  gateway:
+    controllerName: gateway.envoyproxy.io/gatewayclass-controller
+
+  # Provider configuration
+  provider:
+    type: Kubernetes
+    kubernetes:
+      watch:
+        type: Namespaces
+        namespaces:
+        - default
+        - app-namespace
+
+  # Admin server configuration
+  admin:
+    # Web console is always enabled and provides access to:
+    # - Server status and component information
+    # - Configuration dump and EnvoyGateway settings
+    # - Performance metrics and statistics
+
+    # Enable pprof endpoints for debugging (default: false)
+    # Only enable in development/debugging scenarios
+    enablePprof: true
+
+    # Enable config dump in logs (default: false)
+    enableDumpConfig: false
+
+    # Admin server address configuration
+    address:
+      # Host to bind the admin server (default: 127.0.0.1)
+      # Use 127.0.0.1 for localhost-only access (recommended for security)
+      # Use 0.0.0.0 to allow external access (use with caution)
+      host: "127.0.0.1"
+
+      # Port for the admin server (default: 19000)
+      port: 19000
+
+  # Logging configuration
+  logging:
+    level:
+      default: info
+
+  # Telemetry configuration
+  telemetry:
+    metrics:
+      prometheus:
+        disable: false
+      sinks:
+      - type: OpenTelemetry
+        openTelemetry:
+          host: otel-collector.monitoring.svc.cluster.local
+          port: 4317
+          protocol: grpc
+
+---
+# Example: Production configuration with console disabled
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyGateway
+metadata:
+  name: envoy-gateway-production
+  namespace: envoy-gateway-system
+spec:
+  gateway:
+    controllerName: gateway.envoyproxy.io/gatewayclass-controller
+
+  provider:
+    type: Kubernetes
+    kubernetes:
+      watch:
+        type: Namespaces
+        namespaces:
+        - production
+        - staging
+
+  # Production admin configuration - more restrictive
+  admin:
+    # Disable pprof in production
+    enablePprof: false
+
+    # Disable config dump in logs
+    enableDumpConfig: false
+
+    address:
+      # Localhost only for security
+      host: "127.0.0.1"
+      port: 19000
+
+  logging:
+    level:
+      default: warn
+      # Enable debug logging for specific components if needed
+      # gateway-api: debug
+      # xds-translator: debug
+
+---
+# Example: Development configuration with all debugging features
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyGateway
+metadata:
+  name: envoy-gateway-development
+  namespace: envoy-gateway-system
+spec:
+  gateway:
+    controllerName: gateway.envoyproxy.io/gatewayclass-controller
+
+  provider:
+    type: Kubernetes
+    kubernetes:
+      watch:
+        type: Namespaces
+        namespaces:
+        - default
+        - development
+        - testing
+
+  # Development admin configuration - all features enabled
+  admin:
+    # Enable pprof for debugging
+    enablePprof: true
+
+    # Enable config dump for troubleshooting
+    enableDumpConfig: true
+
+    address:
+      # Allow external access for development (use with caution)
+      host: "0.0.0.0"
+      port: 19000
+
+  logging:
+    level:
+      default: debug
+      # Detailed logging for all components
+      gateway-api: debug
+      xds-translator: debug
+      xds-server: debug
+      infrastructure: debug
+
+  telemetry:
+    metrics:
+      prometheus:
+        disable: false
+      sinks:
+      - type: OpenTelemetry
+        openTelemetry:
+          host: jaeger-collector.observability.svc.cluster.local
+          port: 4317
+          protocol: grpc
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index d2bff4d7a..9b72dae0a 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -34,7 +34,7 @@ require (
 	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/spf13/cobra v1.9.1 // indirect
-	github.com/spf13/pflag v1.0.6 // indirect
+	github.com/spf13/pflag v1.0.7 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
 	golang.org/x/mod v0.25.0 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index c76aafe2b..8deddcd82 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -79,8 +79,9 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
diff --git a/examples/preserve-case-backend/go.mod b/examples/preserve-case-backend/go.mod
index 816cdc0a0..2e6b968d8 100644
--- a/examples/preserve-case-backend/go.mod
+++ b/examples/preserve-case-backend/go.mod
@@ -2,7 +2,7 @@ module github.com/envoyproxy/gateway-preserve-case-backend
 
 go 1.24.4
 
-require github.com/valyala/fasthttp v1.63.0
+require github.com/valyala/fasthttp v1.64.0
 
 require (
 	github.com/andybalholm/brotli v1.2.0 // indirect
diff --git a/examples/preserve-case-backend/go.sum b/examples/preserve-case-backend/go.sum
index 277646d01..3d992599b 100644
--- a/examples/preserve-case-backend/go.sum
+++ b/examples/preserve-case-backend/go.sum
@@ -4,7 +4,7 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns=
-github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM=
+github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
+github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
 github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
diff --git a/go.mod b/go.mod
index 967084f8e..922760859 100644
--- a/go.mod
+++ b/go.mod
@@ -10,10 +10,10 @@ require (
 	github.com/avast/retry-go v3.0.0+incompatible
 	github.com/cenkalti/backoff/v4 v4.3.0
 	github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f
-	github.com/containers/image/v5 v5.35.0
+	github.com/containers/image/v5 v5.36.0
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 	github.com/docker/cli v28.3.2+incompatible
-	github.com/docker/docker v28.2.2+incompatible
+	github.com/docker/docker v28.3.2+incompatible
 	github.com/dominikbraun/graph v0.23.0
 	github.com/envoyproxy/go-control-plane v0.13.5-0.20250622153809-434b6986176d
 	github.com/envoyproxy/go-control-plane/contrib v1.32.5-0.20250430092421-68a532e11403
@@ -43,7 +43,7 @@ require (
 	github.com/replicatedhq/troubleshoot v0.120.3
 	github.com/shopspring/decimal v1.4.0
 	github.com/spf13/cobra v1.9.1
-	github.com/spf13/pflag v1.0.6
+	github.com/spf13/pflag v1.0.7
 	github.com/stretchr/testify v1.10.0
 	github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7
 	github.com/tetratelabs/func-e v1.1.5
@@ -104,7 +104,7 @@ require (
 	cel.dev/expr v0.24.0 // indirect
 	connectrpc.com/connect v1.18.1 // indirect
 	connectrpc.com/otelconnect v0.7.2 // indirect
-	dario.cat/mergo v1.0.1 // indirect
+	dario.cat/mergo v1.0.2 // indirect
 	filippo.io/edwards25519 v1.1.0 // indirect
 	fortio.org/cli v1.10.0 // indirect
 	fortio.org/dflag v1.8.1 // indirect
@@ -130,7 +130,7 @@ require (
 	github.com/Masterminds/sprig/v3 v3.3.0 // indirect
 	github.com/Masterminds/squirrel v1.5.4 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
-	github.com/Microsoft/hcsshim v0.12.9 // indirect
+	github.com/Microsoft/hcsshim v0.13.0 // indirect
 	github.com/NYTimes/gziphandler v1.1.1 // indirect
 	github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
 	github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
@@ -183,7 +183,7 @@ require (
 	github.com/containerd/typeurl/v2 v2.2.3 // indirect
 	github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
 	github.com/containers/ocicrypt v1.2.1 // indirect
-	github.com/containers/storage v1.58.0 // indirect
+	github.com/containers/storage v1.59.0 // indirect
 	github.com/coreos/go-semver v0.3.1 // indirect
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
@@ -367,7 +367,6 @@ require (
 	github.com/opencontainers/image-spec v1.1.1 // indirect
 	github.com/opencontainers/runtime-spec v1.2.1 // indirect
 	github.com/opencontainers/selinux v1.12.0 // indirect
-	github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
@@ -394,7 +393,7 @@ require (
 	github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
 	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
-	github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
+	github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
 	github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
 	github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
 	github.com/securego/gosec/v2 v2.22.2 // indirect
@@ -419,7 +418,7 @@ require (
 	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
 	github.com/sylabs/sif/v2 v2.21.1 // indirect
-	github.com/tchap/go-patricia/v2 v2.3.2 // indirect
+	github.com/tchap/go-patricia/v2 v2.3.3 // indirect
 	github.com/tdakkota/asciicheck v0.4.1 // indirect
 	github.com/tetafro/godot v1.5.0 // indirect
 	github.com/tetratelabs/wazero v1.9.0 // indirect
@@ -482,7 +481,7 @@ require (
 	golang.org/x/sys v0.34.0 // indirect
 	golang.org/x/term v0.33.0 // indirect
 	golang.org/x/text v0.27.0 // indirect
-	golang.org/x/time v0.10.0 // indirect
+	golang.org/x/time v0.11.0 // indirect
 	golang.org/x/tools v0.34.0 // indirect
 	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
diff --git a/go.sum b/go.sum
index 2f93dff1b..9e54f0493 100644
--- a/go.sum
+++ b/go.sum
@@ -37,8 +37,8 @@ connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
 connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
 connectrpc.com/otelconnect v0.7.2 h1:WlnwFzaW64dN06JXU+hREPUGeEzpz3Acz2ACOmN8cMI=
 connectrpc.com/otelconnect v0.7.2/go.mod h1:JS7XUKfuJs2adhCnXhNHPHLz6oAaZniCJdSF00OZSew=
-dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
-dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 fortio.org/assert v1.2.1 h1:48I39urpeDj65RP1KguF7akCjILNeu6vICiYMEysR7Q=
@@ -61,8 +61,6 @@ fortio.org/struct2env v0.4.2 h1:Xh7HlS9vf2ZdRvRfmoGIasNDO8t6z36M713utVODRCo=
 fortio.org/struct2env v0.4.2/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
 fortio.org/version v1.0.4 h1:FWUMpJ+hVTNc4RhvvOJzb0xesrlRmG/a+D6bjbQ4+5U=
 fortio.org/version v1.0.4/go.mod h1:2JQp9Ax+tm6QKiGuzR5nJY63kFeANcgrZ0osoQFDVm0=
-github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
-github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
 github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
 github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI=
 github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE=
@@ -124,8 +122,8 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8
 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
-github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
+github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
+github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
@@ -266,14 +264,14 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc
 github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
 github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
 github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
-github.com/containers/image/v5 v5.35.0 h1:T1OeyWp3GjObt47bchwD9cqiaAm/u4O4R9hIWdrdrP8=
-github.com/containers/image/v5 v5.35.0/go.mod h1:8vTsgb+1gKcBL7cnjyNOInhJQfTUQjJoO2WWkKDoebM=
+github.com/containers/image/v5 v5.36.0 h1:Zh+xFcLjRmicnOT5AFPHH/xj+e3s9ojDN/9X2Kx1+Jo=
+github.com/containers/image/v5 v5.36.0/go.mod h1:VZ6cyDHbxZoOt4dklUJ+WNEH9FrgSgfH3qUBYKFlcT0=
 github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
 github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
 github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
 github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
-github.com/containers/storage v1.58.0 h1:Q7SyyCCjqgT3wYNgRNIL8o/wUS92heIj2/cc8Sewvcc=
-github.com/containers/storage v1.58.0/go.mod h1:w7Jl6oG+OpeLGLzlLyOZPkmUso40kjpzgrHUk5tyBlo=
+github.com/containers/storage v1.59.0 h1:r2pYSTzQpJTROZbjJQ54Z0GT+rUC6+wHzlSY8yPjsXk=
+github.com/containers/storage v1.59.0/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -330,8 +328,8 @@ github.com/docker/cli v28.3.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvM
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
-github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
+github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
 github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -877,8 +875,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
-github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
+github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
 github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
@@ -993,8 +991,6 @@ github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU
 github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
 github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
-github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f h1:/UDgs8FGMqwnHagNDPGOlts35QkhAZ8by3DR7nMih7M=
-github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
 github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
 github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
 github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
@@ -1102,8 +1098,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
 github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
 github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
 github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
-github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
-github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
 github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
 github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
 github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ=
@@ -1168,8 +1164,9 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
@@ -1205,8 +1202,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI=
 github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI=
-github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
-github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
+github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
+github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
 github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
 github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
 github.com/telepresenceio/telepresence/rpc/v2 v2.6.8 h1:q5V85LBT9bA/c4YPa/kMvJGyKZDgBPJTftlAMqJx7j4=
@@ -1319,8 +1316,8 @@ go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE=
 go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
-go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
+go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
+go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
@@ -1377,8 +1374,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
 go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo=
 go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE=
 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=
@@ -1590,8 +1587,8 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
-golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/admin/console/api.go b/internal/admin/console/api.go
new file mode 100644
index 000000000..b68409e22
--- /dev/null
+++ b/internal/admin/console/api.go
@@ -0,0 +1,561 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"encoding/json"
+	"net/http"
+	"runtime"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+	"github.com/envoyproxy/gateway/internal/cmd/version"
+)
+
+// SystemInfo represents basic system information
+type SystemInfo struct {
+	Version   string    `json:"version"`
+	StartTime time.Time `json:"startTime"`
+	Uptime    string    `json:"uptime"`
+	GoVersion string    `json:"goVersion"`
+	Platform  string    `json:"platform"`
+	Timestamp time.Time `json:"timestamp"`
+}
+
+// ServerInfo represents server status information
+type ServerInfo struct {
+	State              string            `json:"state"`
+	Version            string            `json:"version"`
+	Uptime             string            `json:"uptime"`
+	Components         []ComponentStatus `json:"components"`
+	EnvoyGatewayConfig interface{}       `json:"envoyGatewayConfig"`
+	LastUpdated        time.Time         `json:"lastUpdated"`
+}
+
+// ComponentStatus represents the status of a system component
+type ComponentStatus struct {
+	Name    string `json:"name"`
+	Status  string `json:"status"`
+	Message string `json:"message"`
+}
+
+// ConfigDumpInfo represents configuration dump information
+type ConfigDumpInfo struct {
+	// Gateway API Core Resources
+	Gateways     []ResourceSummary `json:"gateways"`
+	HTTPRoutes   []ResourceSummary `json:"httpRoutes"`
+	GRPCRoutes   []ResourceSummary `json:"grpcRoutes"`
+	TLSRoutes    []ResourceSummary `json:"tlsRoutes"`
+	TCPRoutes    []ResourceSummary `json:"tcpRoutes"`
+	UDPRoutes    []ResourceSummary `json:"udpRoutes"`
+	GatewayClass []ResourceSummary `json:"gatewayClass"`
+
+	// Envoy Gateway Policies
+	ClientTrafficPolicies  []ResourceSummary `json:"clientTrafficPolicies"`
+	BackendTrafficPolicies []ResourceSummary `json:"backendTrafficPolicies"`
+	BackendTLSPolicies     []ResourceSummary `json:"backendTLSPolicies"`
+	SecurityPolicies       []ResourceSummary `json:"securityPolicies"`
+	EnvoyPatchPolicies     []ResourceSummary `json:"envoyPatchPolicies"`
+	EnvoyExtensionPolicies []ResourceSummary `json:"envoyExtensionPolicies"`
+
+	// Kubernetes Resources
+	Services       []ResourceSummary `json:"services"`
+	Secrets        []ResourceSummary `json:"secrets"`
+	ConfigMaps     []ResourceSummary `json:"configMaps"`
+	Namespaces     []ResourceSummary `json:"namespaces"`
+	EndpointSlices []ResourceSummary `json:"endpointSlices"`
+
+	// Other Resources
+	ReferenceGrants  []ResourceSummary `json:"referenceGrants"`
+	HTTPRouteFilters []ResourceSummary `json:"httpRouteFilters"`
+	EnvoyProxies     []ResourceSummary `json:"envoyProxies"`
+	Backends         []ResourceSummary `json:"backends"`
+	ServiceImports   []ResourceSummary `json:"serviceImports"`
+
+	LastUpdated time.Time `json:"lastUpdated"`
+}
+
+// ResourceSummary represents a simplified summary of a Kubernetes resource
+type ResourceSummary struct {
+	Name      string `json:"name"`
+	Namespace string `json:"namespace"`
+}
+
+var startTime = time.Now()
+
+// handleAPIInfo returns basic system information
+func (h *Handler) handleAPIInfo(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	uptime := time.Since(startTime)
+	versionInfo := version.Get()
+	info := SystemInfo{
+		Version:   versionInfo.EnvoyGatewayVersion,
+		StartTime: startTime,
+		Uptime:    uptime.String(),
+		GoVersion: runtime.Version(),
+		Platform:  runtime.GOOS + "/" + runtime.GOARCH,
+		Timestamp: time.Now(),
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(info); err != nil {
+		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		return
+	}
+}
+
+// handleAPIServerInfo returns server status information
+func (h *Handler) handleAPIServerInfo(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	uptime := time.Since(startTime)
+	versionInfo := version.Get()
+
+	// Since Admin Server starts last, if we can respond, all components are healthy
+	// This reflects the actual startup order in server.go
+	components := []ComponentStatus{
+		{
+			Name:    "Provider Service",
+			Status:  "Running",
+			Message: "Fetches resources from configured provider and publishes them",
+		},
+		{
+			Name:    "GatewayAPI Translator",
+			Status:  "Running",
+			Message: "Translates provider resources to xDS IR and infra IR",
+		},
+		{
+			Name:    "XDS Translator",
+			Status:  "Running",
+			Message: "Translates xDS IR into xDS resources and computes policy statuses",
+		},
+		{
+			Name:    "Infrastructure Manager",
+			Status:  "Running",
+			Message: "Translates infra IR into Envoy Proxy infrastructure resources",
+		},
+	}
+
+	info := ServerInfo{
+		State:              "Running",
+		Version:            versionInfo.EnvoyGatewayVersion,
+		Uptime:             uptime.String(),
+		Components:         components,
+		EnvoyGatewayConfig: h.cfg.EnvoyGateway,
+		LastUpdated:        time.Now(),
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(info); err != nil {
+		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		return
+	}
+}
+
+// handleAPIConfigDump returns configuration dump information
+func (h *Handler) handleAPIConfigDump(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	// Load provider resources from watchable
+	configDump := h.loadConfigDump()
+
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(configDump); err != nil {
+		http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		return
+	}
+}
+
+// loadConfigDump loads configuration data from provider resources
+func (h *Handler) loadConfigDump() ConfigDumpInfo {
+	configDump := ConfigDumpInfo{
+		// Gateway API Core Resources
+		Gateways:     []ResourceSummary{},
+		HTTPRoutes:   []ResourceSummary{},
+		GRPCRoutes:   []ResourceSummary{},
+		TLSRoutes:    []ResourceSummary{},
+		TCPRoutes:    []ResourceSummary{},
+		UDPRoutes:    []ResourceSummary{},
+		GatewayClass: []ResourceSummary{},
+
+		// Envoy Gateway Policies
+		ClientTrafficPolicies:  []ResourceSummary{},
+		BackendTrafficPolicies: []ResourceSummary{},
+		BackendTLSPolicies:     []ResourceSummary{},
+		SecurityPolicies:       []ResourceSummary{},
+		EnvoyPatchPolicies:     []ResourceSummary{},
+		EnvoyExtensionPolicies: []ResourceSummary{},
+
+		// Kubernetes Resources
+		Services:       []ResourceSummary{},
+		Secrets:        []ResourceSummary{},
+		ConfigMaps:     []ResourceSummary{},
+		Namespaces:     []ResourceSummary{},
+		EndpointSlices: []ResourceSummary{},
+
+		// Other Resources
+		ReferenceGrants:  []ResourceSummary{},
+		HTTPRouteFilters: []ResourceSummary{},
+		EnvoyProxies:     []ResourceSummary{},
+		Backends:         []ResourceSummary{},
+		ServiceImports:   []ResourceSummary{},
+
+		LastUpdated: time.Now(),
+	}
+
+	if h.providerResources != nil {
+		// Load controller resources directly from the provider resources
+		controllerResources := h.providerResources.GatewayAPIResources.LoadAll()
+
+		for _, resources := range controllerResources {
+			if resources == nil {
+				continue
+			}
+
+			for _, res := range *resources {
+				if res == nil {
+					continue
+				}
+
+				// Process GatewayClass
+				if res.GatewayClass != nil {
+					configDump.GatewayClass = append(configDump.GatewayClass, ResourceSummary{
+						Name:      res.GatewayClass.Name,
+						Namespace: "", // GatewayClass is cluster-scoped
+					})
+				}
+
+				// Process Gateways
+				for _, gateway := range res.Gateways {
+					if gateway != nil {
+						configDump.Gateways = append(configDump.Gateways, ResourceSummary{
+							Name:      gateway.Name,
+							Namespace: gateway.Namespace,
+						})
+					}
+				}
+
+				// Process HTTPRoutes
+				for _, httpRoute := range res.HTTPRoutes {
+					if httpRoute != nil {
+						configDump.HTTPRoutes = append(configDump.HTTPRoutes, ResourceSummary{
+							Name:      httpRoute.Name,
+							Namespace: httpRoute.Namespace,
+						})
+					}
+				}
+
+				// Process GRPCRoutes
+				for _, grpcRoute := range res.GRPCRoutes {
+					if grpcRoute != nil {
+						configDump.GRPCRoutes = append(configDump.GRPCRoutes, ResourceSummary{
+							Name:      grpcRoute.Name,
+							Namespace: grpcRoute.Namespace,
+						})
+					}
+				}
+
+				// Process TLSRoutes
+				for _, tlsRoute := range res.TLSRoutes {
+					if tlsRoute != nil {
+						configDump.TLSRoutes = append(configDump.TLSRoutes, ResourceSummary{
+							Name:      tlsRoute.Name,
+							Namespace: tlsRoute.Namespace,
+						})
+					}
+				}
+
+				// Process TCPRoutes
+				for _, tcpRoute := range res.TCPRoutes {
+					if tcpRoute != nil {
+						configDump.TCPRoutes = append(configDump.TCPRoutes, ResourceSummary{
+							Name:      tcpRoute.Name,
+							Namespace: tcpRoute.Namespace,
+						})
+					}
+				}
+
+				// Process UDPRoutes
+				for _, udpRoute := range res.UDPRoutes {
+					if udpRoute != nil {
+						configDump.UDPRoutes = append(configDump.UDPRoutes, ResourceSummary{
+							Name:      udpRoute.Name,
+							Namespace: udpRoute.Namespace,
+						})
+					}
+				}
+
+				// Process Envoy Gateway Policies
+				for _, policy := range res.ClientTrafficPolicies {
+					if policy != nil {
+						configDump.ClientTrafficPolicies = append(configDump.ClientTrafficPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				for _, policy := range res.BackendTrafficPolicies {
+					if policy != nil {
+						configDump.BackendTrafficPolicies = append(configDump.BackendTrafficPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				for _, policy := range res.BackendTLSPolicies {
+					if policy != nil {
+						configDump.BackendTLSPolicies = append(configDump.BackendTLSPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				for _, policy := range res.SecurityPolicies {
+					if policy != nil {
+						configDump.SecurityPolicies = append(configDump.SecurityPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				for _, policy := range res.EnvoyPatchPolicies {
+					if policy != nil {
+						configDump.EnvoyPatchPolicies = append(configDump.EnvoyPatchPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				for _, policy := range res.EnvoyExtensionPolicies {
+					if policy != nil {
+						configDump.EnvoyExtensionPolicies = append(configDump.EnvoyExtensionPolicies, ResourceSummary{
+							Name:      policy.Name,
+							Namespace: policy.Namespace,
+						})
+					}
+				}
+
+				// Process Kubernetes Resources
+				for _, service := range res.Services {
+					if service != nil {
+						configDump.Services = append(configDump.Services, ResourceSummary{
+							Name:      service.Name,
+							Namespace: service.Namespace,
+						})
+					}
+				}
+
+				for _, secret := range res.Secrets {
+					if secret != nil {
+						configDump.Secrets = append(configDump.Secrets, ResourceSummary{
+							Name:      secret.Name,
+							Namespace: secret.Namespace,
+						})
+					}
+				}
+
+				for _, configMap := range res.ConfigMaps {
+					if configMap != nil {
+						configDump.ConfigMaps = append(configDump.ConfigMaps, ResourceSummary{
+							Name:      configMap.Name,
+							Namespace: configMap.Namespace,
+						})
+					}
+				}
+
+				for _, namespace := range res.Namespaces {
+					if namespace != nil {
+						configDump.Namespaces = append(configDump.Namespaces, ResourceSummary{
+							Name:      namespace.Name,
+							Namespace: "", // Namespaces are cluster-scoped
+						})
+					}
+				}
+
+				for _, endpointSlice := range res.EndpointSlices {
+					if endpointSlice != nil {
+						configDump.EndpointSlices = append(configDump.EndpointSlices, ResourceSummary{
+							Name:      endpointSlice.Name,
+							Namespace: endpointSlice.Namespace,
+						})
+					}
+				}
+
+				// Process Other Resources
+				for _, refGrant := range res.ReferenceGrants {
+					if refGrant != nil {
+						configDump.ReferenceGrants = append(configDump.ReferenceGrants, ResourceSummary{
+							Name:      refGrant.Name,
+							Namespace: refGrant.Namespace,
+						})
+					}
+				}
+
+				for _, filter := range res.HTTPRouteFilters {
+					if filter != nil {
+						configDump.HTTPRouteFilters = append(configDump.HTTPRouteFilters, ResourceSummary{
+							Name:      filter.Name,
+							Namespace: filter.Namespace,
+						})
+					}
+				}
+
+				// Process EnvoyProxy for GatewayClass
+				if res.EnvoyProxyForGatewayClass != nil {
+					configDump.EnvoyProxies = append(configDump.EnvoyProxies, ResourceSummary{
+						Name:      res.EnvoyProxyForGatewayClass.Name,
+						Namespace: res.EnvoyProxyForGatewayClass.Namespace,
+					})
+				}
+
+				// Process EnvoyProxies for Gateways
+				for _, proxy := range res.EnvoyProxiesForGateways {
+					if proxy != nil {
+						configDump.EnvoyProxies = append(configDump.EnvoyProxies, ResourceSummary{
+							Name:      proxy.Name,
+							Namespace: proxy.Namespace,
+						})
+					}
+				}
+
+				for _, backend := range res.Backends {
+					if backend != nil {
+						configDump.Backends = append(configDump.Backends, ResourceSummary{
+							Name:      backend.Name,
+							Namespace: backend.Namespace,
+						})
+					}
+				}
+
+				for _, serviceImport := range res.ServiceImports {
+					if serviceImport != nil {
+						configDump.ServiceImports = append(configDump.ServiceImports, ResourceSummary{
+							Name:      serviceImport.Name,
+							Namespace: serviceImport.Namespace,
+						})
+					}
+				}
+			}
+		}
+	}
+
+	return configDump
+}
+
+// handleAPIConfigDumpAll handles requests for complete provider resources dump
+func (h *Handler) handleAPIConfigDumpAll(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+	if h.providerResources == nil {
+		// Return empty structure if no provider resources
+		emptyResponse := map[string]interface{}{
+			"message":   "No provider resources available",
+			"timestamp": time.Now(),
+		}
+		if err := json.NewEncoder(w).Encode(emptyResponse); err != nil {
+			http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+		}
+		return
+	}
+
+	// Get the actual resources using GetResources() method
+	resources := h.providerResources.GetResources()
+
+	// Create a structured response with the actual resource data
+	response := map[string]interface{}{
+		"resources":  resources,
+		"timestamp":  time.Now(),
+		"totalCount": len(resources),
+	}
+
+	if err := json.NewEncoder(w).Encode(response); err != nil {
+		http.Error(w, "Failed to encode provider resources", http.StatusInternalServerError)
+		return
+	}
+}
+
+// handleAPIMetrics handles requests for metrics using the Prometheus registry
+func (h *Handler) handleAPIMetrics(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	// Create a Prometheus handler using the controller-runtime registry
+	// This includes all Envoy Gateway control plane metrics
+	handler := createCombinedMetricsHandler()
+
+	// Serve the metrics using the Prometheus handler
+	handler.ServeHTTP(w, r)
+}
+
+// Helper functions to extract status information from Gateway API resources
+
+func getGatewayClassStatus(gc *gwapiv1.GatewayClass) string {
+	if len(gc.Status.Conditions) == 0 {
+		return "Unknown"
+	}
+
+	for _, condition := range gc.Status.Conditions {
+		if condition.Type == string(gwapiv1.GatewayClassConditionStatusAccepted) {
+			if condition.Status == metav1.ConditionTrue {
+				return "Accepted"
+			}
+			return "Not Accepted"
+		}
+	}
+	return "Unknown"
+}
+
+func getGatewayStatus(gw *gwapiv1.Gateway) string {
+	if len(gw.Status.Conditions) == 0 {
+		return "Unknown"
+	}
+
+	// Check for Programmed condition first
+	for _, condition := range gw.Status.Conditions {
+		if condition.Type == string(gwapiv1.GatewayConditionProgrammed) {
+			if condition.Status == metav1.ConditionTrue {
+				return "Programmed"
+			}
+			return "Not Programmed"
+		}
+	}
+
+	// Check for Accepted condition
+	for _, condition := range gw.Status.Conditions {
+		if condition.Type == string(gwapiv1.GatewayConditionAccepted) {
+			if condition.Status == metav1.ConditionTrue {
+				return "Accepted"
+			}
+			return "Not Accepted"
+		}
+	}
+
+	return "Unknown"
+}
diff --git a/internal/admin/console/embed.go b/internal/admin/console/embed.go
new file mode 100644
index 000000000..8323f02d1
--- /dev/null
+++ b/internal/admin/console/embed.go
@@ -0,0 +1,18 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"embed"
+)
+
+// Embed static files and templates
+//
+//go:embed static
+var staticFiles embed.FS
+
+//go:embed templates
+var templateFiles embed.FS
diff --git a/internal/admin/console/handlers.go b/internal/admin/console/handlers.go
new file mode 100644
index 000000000..e665f5c4c
--- /dev/null
+++ b/internal/admin/console/handlers.go
@@ -0,0 +1,228 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"fmt"
+	"html/template"
+	"io/fs"
+	"net/http"
+	"time"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/message"
+)
+
+// Handler manages the console web interface
+type Handler struct {
+	cfg               *config.Server
+	templates         map[string]*template.Template
+	providerResources *message.ProviderResources
+}
+
+// NewHandler creates a new console handler
+func NewHandler(cfg *config.Server, providerResources *message.ProviderResources) *Handler {
+	return &Handler{
+		cfg:               cfg,
+		templates:         make(map[string]*template.Template),
+		providerResources: providerResources,
+	}
+}
+
+// RegisterRoutes registers all console routes with the provided mux
+func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
+	// Web UI endpoints
+	mux.HandleFunc("/", h.handleIndex)
+	mux.HandleFunc("/pprof", h.handlePprof)
+	mux.HandleFunc("/server_info", h.handleServerInfo)
+	mux.HandleFunc("/config_dump", h.handleConfigDump)
+	mux.HandleFunc("/stats", h.handleStats)
+
+	// API endpoints
+	mux.HandleFunc("/api/info", h.handleAPIInfo)
+	mux.HandleFunc("/api/server_info", h.handleAPIServerInfo)
+	mux.HandleFunc("/api/config_dump", h.handleAPIConfigDump)
+	mux.HandleFunc("/api/config_dump_all", h.handleAPIConfigDumpAll)
+	mux.HandleFunc("/api/metrics", h.handleAPIMetrics)
+
+	// Static files
+	staticFS, err := fs.Sub(staticFiles, "static")
+	if err == nil {
+		mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
+	}
+}
+
+// handleIndex serves the main console page
+func (h *Handler) handleIndex(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	data := struct {
+		Title     string
+		Timestamp time.Time
+	}{
+		Title:     "Envoy Gateway Admin Console",
+		Timestamp: time.Now(),
+	}
+
+	if err := h.renderTemplate(w, "index.html", data); err != nil {
+		// Content-Type is already set by renderTemplate
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte("Internal server error"))
+		return
+	}
+}
+
+// handlePprof serves the pprof page
+func (h *Handler) handlePprof(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	enablePprof := h.cfg.EnvoyGateway.GetEnvoyGatewayAdmin().EnablePprof
+	data := struct {
+		Title       string
+		EnablePprof bool
+	}{
+		Title:       "Performance Profiling",
+		EnablePprof: enablePprof,
+	}
+
+	if err := h.renderTemplate(w, "pprof.html", data); err != nil {
+		// Content-Type is already set by renderTemplate
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte("Internal server error"))
+		return
+	}
+}
+
+// handleServerInfo serves the server info page
+func (h *Handler) handleServerInfo(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	data := struct {
+		Title string
+	}{
+		Title: "Server Information",
+	}
+
+	if err := h.renderTemplate(w, "server_info.html", data); err != nil {
+		// Content-Type is already set by renderTemplate
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte("Internal server error"))
+		return
+	}
+}
+
+// handleConfigDump serves the config dump page
+func (h *Handler) handleConfigDump(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	data := struct {
+		Title string
+	}{
+		Title: "Configuration Dump",
+	}
+
+	if err := h.renderTemplate(w, "config_dump.html", data); err != nil {
+		// Content-Type is already set by renderTemplate
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte("Internal server error"))
+		return
+	}
+}
+
+// handleStats serves the stats page
+func (h *Handler) handleStats(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+		return
+	}
+
+	// Get metrics server address
+	metricsAddress := fmt.Sprintf("%s:%d", egv1a1.GatewayMetricsHost, egv1a1.GatewayMetricsPort)
+
+	data := struct {
+		Title          string
+		MetricsAddress string
+	}{
+		Title:          "Statistics",
+		MetricsAddress: metricsAddress,
+	}
+
+	if err := h.renderTemplate(w, "stats.html", data); err != nil {
+		// Content-Type is already set by renderTemplate
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = w.Write([]byte("Internal server error"))
+		return
+	}
+}
+
+// renderTemplate renders a template with the given data
+func (h *Handler) renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) error {
+	if len(h.templates) == 0 {
+		if err := h.loadTemplates(); err != nil {
+			// Set Content-Type before returning error
+			w.Header().Set("Content-Type", "text/html; charset=utf-8")
+			return err
+		}
+	}
+
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+	// Get the specific template
+	t, exists := h.templates[tmpl]
+	if !exists {
+		return fmt.Errorf("template %s not found", tmpl)
+	}
+
+	return t.ExecuteTemplate(w, "base.html", data)
+}
+
+// loadTemplates loads all HTML templates
+func (h *Handler) loadTemplates() error {
+	// Template mappings: template name -> content block name
+	templateMappings := map[string]string{
+		"index.html":       "index-content",
+		"server_info.html": "server-info-content",
+		"config_dump.html": "config-dump-content",
+		"stats.html":       "stats-content",
+		"pprof.html":       "pprof-content",
+	}
+
+	// Create individual templates for each page
+	for templateName, contentBlock := range templateMappings {
+		// Create a new template instance for each page
+		tmpl := template.New(templateName)
+
+		// Parse all templates together
+		tmpl, err := tmpl.ParseFS(templateFiles, "templates/base.html", "templates/"+templateName)
+		if err != nil {
+			return fmt.Errorf("failed to parse templates for %s: %w", templateName, err)
+		}
+
+		// Create a wrapper template that calls the correct content block
+		wrapperTemplate := fmt.Sprintf(`{{define "content"}}{{template "%s" .}}{{end}}`, contentBlock)
+		tmpl, err = tmpl.Parse(wrapperTemplate)
+		if err != nil {
+			return fmt.Errorf("failed to parse wrapper for %s: %w", templateName, err)
+		}
+
+		h.templates[templateName] = tmpl
+	}
+
+	return nil
+}
diff --git a/internal/admin/console/metrics.go b/internal/admin/console/metrics.go
new file mode 100644
index 000000000..8c74fda06
--- /dev/null
+++ b/internal/admin/console/metrics.go
@@ -0,0 +1,33 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"net/http"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics"
+)
+
+// createCombinedMetricsHandler creates a handler that uses the controller-runtime registry
+func createCombinedMetricsHandler() http.Handler {
+	// Check if the controller-runtime registry has any metrics
+	// In test environments, it might be empty
+	if hasMetrics(metricsserver.Registry) {
+		// Use the controller-runtime registry which includes all Envoy Gateway metrics
+		return promhttp.HandlerFor(metricsserver.Registry, promhttp.HandlerOpts{})
+	}
+
+	// Fallback to default gatherer for test environments
+	return promhttp.Handler()
+}
+
+// hasMetrics checks if a gatherer has any metrics
+func hasMetrics(gatherer prometheus.Gatherer) bool {
+	families, err := gatherer.Gather()
+	return err == nil && len(families) > 0
+}
diff --git a/internal/admin/console/static/css/admin.css b/internal/admin/console/static/css/admin.css
new file mode 100644
index 000000000..3c6a54c03
--- /dev/null
+++ b/internal/admin/console/static/css/admin.css
@@ -0,0 +1,641 @@
+/* Envoy Gateway Admin Console Styles */
+
+/* Reset and base styles */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+html, body {
+    height: 100%;
+}
+
+body {
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+    line-height: 1.6;
+    color: #333;
+    background-color: #f5f5f5;
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+}
+
+/* Header */
+.header {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    padding: 1rem 0;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.header-content {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 0 2rem;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.logo {
+    font-size: 1.5rem;
+    font-weight: bold;
+}
+
+.nav {
+    display: flex;
+    gap: 2rem;
+}
+
+.nav a {
+    color: white;
+    text-decoration: none;
+    padding: 0.5rem 1rem;
+    border-radius: 4px;
+    transition: background-color 0.3s;
+}
+
+.nav a:hover,
+.nav a.active {
+    background-color: rgba(255, 255, 255, 0.2);
+}
+
+/* Main content */
+.container {
+    max-width: 1200px;
+    margin: 2rem auto;
+    padding: 0 2rem;
+    flex: 1; /* This makes the container expand to fill available space */
+}
+
+.card {
+    background: white;
+    border-radius: 8px;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+    margin-bottom: 2rem;
+    overflow: hidden;
+}
+
+.card-header {
+    background: #f8f9fa;
+    padding: 1rem 1.5rem;
+    border-bottom: 1px solid #e9ecef;
+}
+
+.card-title {
+    font-size: 1.25rem;
+    font-weight: 600;
+    color: #495057;
+}
+
+.card-body {
+    padding: 1.5rem;
+}
+
+/* Grid layout */
+.grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+    gap: 2rem;
+    margin-bottom: 2rem;
+}
+
+/* Dashboard 2x2 grid layout */
+.dashboard-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 2rem;
+    margin-bottom: 2rem;
+}
+
+/* Config Summary 6x4 grid layout */
+.config-summary-grid {
+    display: grid;
+    grid-template-columns: repeat(6, 1fr);
+    gap: 1.5rem;
+    margin-bottom: 2rem;
+}
+
+/* Config Resources 4x6 grid layout */
+.config-resources-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 1.5rem;
+    margin-bottom: 2rem;
+}
+
+/* Quick Links styles */
+.quick-links-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 2rem;
+}
+
+.quick-links-section h3 {
+    margin-bottom: 1rem;
+    color: #495057;
+    font-size: 1.1rem;
+    font-weight: 600;
+}
+
+.quick-links-list {
+    display: flex;
+    flex-direction: column;
+    gap: 0.75rem;
+}
+
+.quick-link {
+    display: flex;
+    align-items: center;
+    padding: 0.75rem 1rem;
+    background-color: #f8f9fa;
+    border: 1px solid #e9ecef;
+    border-radius: 0.5rem;
+    text-decoration: none;
+    color: #495057;
+    transition: all 0.2s ease;
+}
+
+.quick-link:hover {
+    background-color: #e9ecef;
+    border-color: #007bff;
+    color: #007bff;
+    text-decoration: none;
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.quick-link-icon {
+    font-size: 1.2rem;
+    margin-right: 0.75rem;
+    min-width: 1.5rem;
+}
+
+.quick-link-text {
+    font-weight: 500;
+}
+
+/* Resource item styles */
+.resource-item {
+    padding: 1rem;
+    border: 1px solid #e9ecef;
+    border-radius: 0.5rem;
+    background-color: #fff;
+    margin-bottom: 0.5rem;
+}
+
+.resource-header {
+    display: flex;
+    flex-direction: column;
+    gap: 0.5rem;
+}
+
+.resource-name {
+    font-weight: 600;
+    font-size: 1rem;
+    color: #495057;
+}
+
+.resource-namespace {
+    font-size: 0.875rem;
+    color: #6c757d;
+    background-color: #f8f9fa;
+    padding: 0.25rem 0.5rem;
+    border-radius: 0.25rem;
+    display: inline-block;
+    width: fit-content;
+}
+
+.resource-scope {
+    font-size: 0.875rem;
+    color: #6c757d;
+    background-color: #e3f2fd;
+    padding: 0.25rem 0.5rem;
+    border-radius: 0.25rem;
+    display: inline-block;
+    width: fit-content;
+}
+
+/* Status indicators */
+.status {
+    display: inline-block;
+    padding: 0.25rem 0.75rem;
+    border-radius: 12px;
+    font-size: 0.875rem;
+    font-weight: 500;
+}
+
+.status.running {
+    background-color: #d4edda;
+    color: #155724;
+}
+
+.status.warning {
+    background-color: #fff3cd;
+    color: #856404;
+}
+
+.status.error {
+    background-color: #f8d7da;
+    color: #721c24;
+}
+
+.status.accepted {
+    background-color: #d4edda;
+    color: #155724;
+}
+
+.status.programmed {
+    background-color: #d4edda;
+    color: #155724;
+}
+
+.status.pending {
+    background-color: #fff3cd;
+    color: #856404;
+}
+
+.status.invalid {
+    background-color: #f8d7da;
+    color: #721c24;
+}
+
+/* Resource items */
+.resource-item {
+    border: 1px solid #e9ecef;
+    border-radius: 8px;
+    padding: 1rem;
+    margin-bottom: 1rem;
+    background-color: #fff;
+}
+
+.resource-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 0.5rem;
+}
+
+.resource-details {
+    color: #6c757d;
+    font-size: 0.875rem;
+}
+
+/* Config summary */
+.summary-item {
+    text-align: center;
+    padding: 1rem;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+}
+
+.summary-item h3 {
+    font-size: 2rem;
+    margin: 0 0 0.5rem 0;
+    color: #007bff;
+}
+
+.summary-item p {
+    margin: 0;
+    color: #6c757d;
+}
+
+.text-muted {
+    color: #6c757d;
+    font-style: italic;
+}
+
+/* Namespace and cluster badges */
+.namespace-badge {
+    background-color: #e9ecef;
+    color: #495057;
+    padding: 0.25rem 0.5rem;
+    border-radius: 4px;
+    font-size: 0.75rem;
+    font-weight: 500;
+}
+
+.cluster-scoped {
+    background-color: #fff3cd;
+    color: #856404;
+    padding: 0.25rem 0.5rem;
+    border-radius: 4px;
+    font-size: 0.75rem;
+    font-weight: 500;
+}
+
+/* Button styles */
+.btn-primary {
+    background-color: #007bff;
+    border-color: #007bff;
+    color: white;
+    text-decoration: none;
+    padding: 0.375rem 0.75rem;
+    border-radius: 0.25rem;
+    display: inline-block;
+    font-weight: 400;
+    text-align: center;
+    vertical-align: middle;
+    border: 1px solid transparent;
+    font-size: 1rem;
+    line-height: 1.5;
+}
+
+.btn-primary:hover {
+    background-color: #0056b3;
+    border-color: #004085;
+    color: white;
+    text-decoration: none;
+}
+
+.btn-secondary {
+    background-color: #6c757d;
+    border-color: #6c757d;
+    color: white;
+    text-decoration: none;
+    padding: 0.375rem 0.75rem;
+    border-radius: 0.25rem;
+    display: inline-block;
+    font-weight: 400;
+    text-align: center;
+    vertical-align: middle;
+    border: 1px solid transparent;
+    font-size: 1rem;
+    line-height: 1.5;
+    margin-left: 0.5rem;
+}
+
+.btn-secondary:hover {
+    background-color: #545b62;
+    border-color: #4e555b;
+    color: white;
+    text-decoration: none;
+}
+
+/* Tables */
+.table {
+    width: 100%;
+    border-collapse: collapse;
+    margin-top: 1rem;
+}
+
+.table th,
+.table td {
+    padding: 0.75rem;
+    text-align: left;
+    border-bottom: 1px solid #e9ecef;
+}
+
+.table th {
+    background-color: #f8f9fa;
+    font-weight: 600;
+    color: #495057;
+}
+
+.table tbody tr:hover {
+    background-color: #f8f9fa;
+}
+
+/* Buttons */
+.btn {
+    display: inline-block;
+    padding: 0.5rem 1rem;
+    background-color: #007bff;
+    color: white;
+    text-decoration: none;
+    border-radius: 4px;
+    border: none;
+    cursor: pointer;
+    transition: background-color 0.3s;
+}
+
+.btn:hover {
+    background-color: #0056b3;
+}
+
+.btn-secondary {
+    background-color: #6c757d;
+}
+
+.btn-secondary:hover {
+    background-color: #545b62;
+}
+
+/* Info boxes */
+.info-box {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 1rem;
+    background-color: #e7f3ff;
+    border-left: 4px solid #007bff;
+    margin-bottom: 1rem;
+}
+
+.info-box.warning {
+    background-color: #fff8e1;
+    border-left-color: #ff9800;
+}
+
+.info-box.error {
+    background-color: #ffebee;
+    border-left-color: #f44336;
+}
+
+/* Code blocks */
+.code {
+    background-color: #f8f9fa;
+    border: 1px solid #e9ecef;
+    border-radius: 4px;
+    padding: 1rem;
+    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+    font-size: 0.875rem;
+    overflow-x: auto;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    line-height: 1.4;
+}
+
+/* JSON code blocks with syntax highlighting */
+.json-code {
+    background-color: #f8f9fa;
+    border: 1px solid #e9ecef;
+    border-radius: 6px;
+    padding: 1.25rem;
+    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
+    font-size: 0.875rem;
+    overflow-x: auto;
+    white-space: pre;
+    line-height: 1.5;
+    max-height: 500px;
+    overflow-y: auto;
+    margin-top: 0.75rem;
+}
+
+/* JSON syntax highlighting */
+.json-key {
+    color: #0066cc;
+    font-weight: 600;
+}
+
+.json-string {
+    color: #008000;
+}
+
+.json-number {
+    color: #ff6600;
+}
+
+.json-boolean {
+    color: #cc0066;
+    font-weight: 600;
+}
+
+.json-null {
+    color: #999999;
+    font-style: italic;
+}
+
+/* Config details toggle */
+.config-details {
+    margin-top: 1rem;
+    border-top: 1px solid #e9ecef;
+    padding-top: 1rem;
+}
+
+.config-details.collapsed {
+    display: none;
+}
+
+.config-details.expanded {
+    display: block;
+    animation: slideDown 0.3s ease-out;
+}
+
+@keyframes slideDown {
+    from {
+        opacity: 0;
+        max-height: 0;
+    }
+    to {
+        opacity: 1;
+        max-height: 500px;
+    }
+}
+
+/* Success button state */
+.btn-success {
+    background-color: #28a745 !important;
+    border-color: #28a745 !important;
+    color: white !important;
+}
+
+/* Button hover effects */
+.btn:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    transition: all 0.2s ease;
+}
+
+/* Copy button specific styling */
+.btn.copy-btn {
+    transition: all 0.3s ease;
+}
+
+.btn.copy-btn:active {
+    transform: translateY(0);
+}
+
+/* Loading spinner */
+.loading {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    border: 3px solid #f3f3f3;
+    border-top: 3px solid #007bff;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+    .header-content {
+        flex-direction: column;
+        gap: 1rem;
+    }
+    
+    .nav {
+        flex-wrap: wrap;
+        justify-content: center;
+    }
+    
+    .container {
+        padding: 0 1rem;
+    }
+    
+    .grid {
+        grid-template-columns: 1fr;
+    }
+
+    .dashboard-grid {
+        grid-template-columns: 1fr;
+    }
+
+    .config-summary-grid {
+        grid-template-columns: repeat(3, 1fr);
+    }
+
+    .config-resources-grid {
+        grid-template-columns: repeat(2, 1fr);
+    }
+}
+
+/* Medium screens */
+@media (max-width: 1024px) {
+    .config-summary-grid {
+        grid-template-columns: repeat(4, 1fr);
+    }
+
+    .config-resources-grid {
+        grid-template-columns: repeat(3, 1fr);
+    }
+}
+
+/* Small screens */
+@media (max-width: 768px) {
+    .config-summary-grid {
+        grid-template-columns: repeat(2, 1fr);
+    }
+
+    .config-resources-grid {
+        grid-template-columns: repeat(1, 1fr);
+    }
+
+    .quick-links-grid {
+        grid-template-columns: 1fr;
+    }
+}
+
+/* Footer */
+.footer {
+    background-color: #343a40;
+    color: #adb5bd;
+    text-align: center;
+    padding: 2rem 0;
+    margin-top: auto; /* This pushes the footer to the bottom */
+}
+
+.footer a {
+    color: #007bff;
+    text-decoration: none;
+}
+
+.footer a:hover {
+    text-decoration: underline;
+}
diff --git a/internal/admin/console/static/js/admin.js b/internal/admin/console/static/js/admin.js
new file mode 100644
index 000000000..4bce3bb10
--- /dev/null
+++ b/internal/admin/console/static/js/admin.js
@@ -0,0 +1,677 @@
+// Envoy Gateway Admin Console JavaScript
+
+// Global app object
+window.EnvoyGatewayAdmin = {
+    // Configuration
+    config: {
+        refreshInterval: 30000, // 30 seconds
+        apiTimeout: 10000 // 10 seconds
+    },
+    
+    // State
+    state: {
+        autoRefresh: false,
+        currentPage: null
+    },
+    
+    // Initialize the application
+    init: function() {
+        this.setupNavigation();
+        this.setupAutoRefresh();
+        this.detectCurrentPage();
+        this.loadPageData();
+    },
+    
+    // Setup navigation highlighting
+    setupNavigation: function() {
+        const currentPath = window.location.pathname;
+        const navLinks = document.querySelectorAll('.nav a');
+        
+        navLinks.forEach(link => {
+            if (link.getAttribute('href') === currentPath) {
+                link.classList.add('active');
+            }
+        });
+    },
+    
+    // Setup auto-refresh functionality
+    setupAutoRefresh: function() {
+        const refreshToggle = document.getElementById('auto-refresh');
+        if (refreshToggle) {
+            refreshToggle.addEventListener('change', (e) => {
+                this.state.autoRefresh = e.target.checked;
+                if (this.state.autoRefresh) {
+                    this.startAutoRefresh();
+                } else {
+                    this.stopAutoRefresh();
+                }
+            });
+        }
+    },
+    
+    // Detect current page
+    detectCurrentPage: function() {
+        const path = window.location.pathname;
+        if (path === '/' || path === '/index') {
+            this.state.currentPage = 'index';
+        } else if (path === '/server_info') {
+            this.state.currentPage = 'server_info';
+        } else if (path === '/config_dump') {
+            this.state.currentPage = 'config_dump';
+        } else if (path === '/stats') {
+            this.state.currentPage = 'stats';
+        } else if (path === '/pprof') {
+            this.state.currentPage = 'pprof';
+        }
+    },
+    
+    // Load page-specific data
+    loadPageData: function() {
+        switch (this.state.currentPage) {
+            case 'index':
+                this.loadSystemInfo();
+                break;
+            case 'server_info':
+                this.loadServerInfo();
+                break;
+            case 'config_dump':
+                this.loadConfigDump();
+                break;
+        }
+    },
+    
+    // API call helper
+    apiCall: function(endpoint, callback) {
+        const controller = new AbortController();
+        const timeoutId = setTimeout(() => controller.abort(), this.config.apiTimeout);
+        
+        fetch(endpoint, {
+            signal: controller.signal,
+            headers: {
+                'Accept': 'application/json'
+            }
+        })
+        .then(response => {
+            clearTimeout(timeoutId);
+            if (!response.ok) {
+                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+            }
+            return response.json();
+        })
+        .then(data => callback(null, data))
+        .catch(error => {
+            clearTimeout(timeoutId);
+            callback(error, null);
+        });
+    },
+    
+    // Load system information
+    loadSystemInfo: function() {
+        this.showLoading('system-info');
+        this.apiCall('/api/info', (error, data) => {
+            this.hideLoading('system-info');
+            if (error) {
+                this.showError('system-info', 'Failed to load system information: ' + error.message);
+                return;
+            }
+            this.updateSystemInfo(data);
+        });
+    },
+    
+    // Update system information display
+    updateSystemInfo: function(data) {
+        const container = document.getElementById('system-info');
+        if (!container) return;
+
+        container.innerHTML = `
+            <div class="info-box">
+                <div>
+                    <strong>Version:</strong> ${data.version}<br>
+                    <strong>Uptime:</strong> ${data.uptime}<br>
+                    <strong>Go Version:</strong> ${data.goVersion}<br>
+                    <strong>Platform:</strong> ${data.platform}
+                </div>
+                <div>
+                    <small>Last updated: ${new Date(data.timestamp).toLocaleString()}</small>
+                </div>
+            </div>
+        `;
+    },
+
+
+
+    // Update EnvoyGateway configuration display
+    updateEnvoyGatewayConfig: function(data) {
+        const container = document.getElementById('envoy-gateway-config');
+        if (!container) return;
+
+        container.innerHTML = `
+            <div class="info-box">
+                <div>
+                    <strong>API Version:</strong> ${data.apiVersion || 'N/A'}<br>
+                    <strong>Kind:</strong> ${data.kind || 'N/A'}<br>
+                    <strong>Controller Name:</strong> ${data.gateway?.controllerName || 'N/A'}<br>
+                    <strong>Provider Type:</strong> ${data.provider?.type || 'N/A'}
+                </div>
+                <div>
+                    <button class="btn btn-secondary" onclick="EnvoyGatewayAdmin.toggleConfigDetails()" id="config-toggle-btn">
+                        Show Details
+                    </button>
+                    <button class="btn btn-secondary" onclick="EnvoyGatewayAdmin.copyConfigToClipboard(event)" style="margin-left: 0.5rem;">
+                        Copy JSON
+                    </button>
+                </div>
+            </div>
+            <div id="config-details" class="config-details collapsed">
+                <div class="json-code" id="config-json">${this.formatJSON(data)}</div>
+            </div>
+        `;
+    },
+
+    // Toggle configuration details
+    toggleConfigDetails: function() {
+        const details = document.getElementById('config-details');
+        const button = document.getElementById('config-toggle-btn');
+
+        if (details && button) {
+            if (details.classList.contains('collapsed')) {
+                details.classList.remove('collapsed');
+                details.classList.add('expanded');
+                button.textContent = 'Hide Details';
+            } else {
+                details.classList.remove('expanded');
+                details.classList.add('collapsed');
+                button.textContent = 'Show Details';
+            }
+        }
+    },
+
+    // Copy configuration to clipboard
+    copyConfigToClipboard: function(event) {
+        const configElement = document.getElementById('config-json');
+        if (configElement && event && event.target) {
+            const text = configElement.textContent;
+            const button = event.target;
+
+            if (navigator.clipboard && navigator.clipboard.writeText) {
+                navigator.clipboard.writeText(text).then(() => {
+                    this.showCopySuccess(button);
+                }).catch(err => {
+                    console.error('Failed to copy to clipboard:', err);
+                    this.fallbackCopyToClipboard(text, button);
+                });
+            } else {
+                this.fallbackCopyToClipboard(text, button);
+            }
+        }
+    },
+
+    // Show copy success feedback
+    showCopySuccess: function(button) {
+        const originalText = button.textContent;
+        button.textContent = 'Copied!';
+        button.classList.add('btn-success');
+        setTimeout(() => {
+            button.textContent = originalText;
+            button.classList.remove('btn-success');
+        }, 2000);
+    },
+
+    // Fallback copy method for older browsers
+    fallbackCopyToClipboard: function(text, button) {
+        const textArea = document.createElement('textarea');
+        textArea.value = text;
+        textArea.style.position = 'fixed';
+        textArea.style.left = '-999999px';
+        textArea.style.top = '-999999px';
+        document.body.appendChild(textArea);
+        textArea.focus();
+        textArea.select();
+
+        try {
+            const successful = document.execCommand('copy');
+            if (successful && button) {
+                this.showCopySuccess(button);
+            }
+        } catch (err) {
+            console.error('Fallback copy failed:', err);
+        }
+
+        document.body.removeChild(textArea);
+    },
+
+    // Format JSON with syntax highlighting
+    formatJSON: function(obj) {
+        const jsonString = JSON.stringify(obj, null, 2);
+        return this.syntaxHighlight(jsonString);
+    },
+
+    // Add syntax highlighting to JSON
+    syntaxHighlight: function(json) {
+        json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+        return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
+            let cls = 'json-number';
+            if (/^"/.test(match)) {
+                if (/:$/.test(match)) {
+                    cls = 'json-key';
+                } else {
+                    cls = 'json-string';
+                }
+            } else if (/true|false/.test(match)) {
+                cls = 'json-boolean';
+            } else if (/null/.test(match)) {
+                cls = 'json-null';
+            }
+            return '<span class="' + cls + '">' + match + '</span>';
+        });
+    },
+    
+    // Load server information
+    loadServerInfo: function() {
+        this.showLoading('server-info');
+        this.apiCall('/api/server_info', (error, data) => {
+            this.hideLoading('server-info');
+            if (error) {
+                this.showError('server-info', 'Failed to load server information: ' + error.message);
+                return;
+            }
+            this.updateServerInfo(data);
+        });
+    },
+    
+    // Update server information display
+    updateServerInfo: function(data) {
+        const container = document.getElementById('server-info');
+        if (!container) return;
+
+        let componentsHtml = '';
+        data.components.forEach(component => {
+            const statusClass = component.status.toLowerCase() === 'running' ? 'running' : 'error';
+            componentsHtml += `
+                <tr>
+                    <td>${component.name}</td>
+                    <td><span class="status ${statusClass}">${component.status}</span></td>
+                    <td>${component.message}</td>
+                </tr>
+            `;
+        });
+
+        container.innerHTML = `
+            <div class="info-box">
+                <div>
+                    <strong>State:</strong> <span class="status running">${data.state}</span><br>
+                    <strong>Version:</strong> ${data.version}<br>
+                    <strong>Uptime:</strong> ${data.uptime}
+                </div>
+                <div>
+                    <small>Last updated: ${new Date(data.lastUpdated).toLocaleString()}</small>
+                </div>
+            </div>
+            <table class="table">
+                <thead>
+                    <tr>
+                        <th>Component</th>
+                        <th>Status</th>
+                        <th>Message</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    ${componentsHtml}
+                </tbody>
+            </table>
+        `;
+
+        // Update EnvoyGateway configuration if we're on server_info page
+        if (data.envoyGatewayConfig) {
+            this.updateEnvoyGatewayConfig(data.envoyGatewayConfig);
+        }
+    },
+    
+    // Load configuration dump
+    loadConfigDump: function() {
+        this.showLoading('config-dump');
+        this.apiCall('/api/config_dump', (error, data) => {
+            this.hideLoading('config-dump');
+            if (error) {
+                this.showError('config-dump', 'Failed to load configuration dump: ' + error.message);
+                return;
+            }
+            this.updateConfigDump(data);
+        });
+    },
+    
+    // Update configuration dump display
+    updateConfigDump: function(data) {
+        const container = document.getElementById('config-dump');
+        if (container) {
+            container.innerHTML = `
+                <div class="info-box">
+                    <div>
+                        <strong>Gateways:</strong> ${data.gateways ? data.gateways.length : 0}<br>
+                        <strong>HTTP Routes:</strong> ${data.httpRoutes ? data.httpRoutes.length : 0}<br>
+                        <strong>Gateway Classes:</strong> ${data.gatewayClass ? data.gatewayClass.length : 0}
+                    </div>
+                    <div>
+                        <small>Last updated: ${new Date(data.lastUpdated).toLocaleString()}</small>
+                    </div>
+                </div>
+                <div class="code">
+                    ${JSON.stringify(data, null, 2)}
+                </div>
+            `;
+        }
+
+        // Update individual resource lists
+        this.updateGatewaysList(data.gateways || []);
+        this.updateHTTPRoutesList(data.httpRoutes || []);
+        this.updateGRPCRoutesList(data.grpcRoutes || []);
+        this.updateTLSRoutesList(data.tlsRoutes || []);
+        this.updateTCPRoutesList(data.tcpRoutes || []);
+        this.updateUDPRoutesList(data.udpRoutes || []);
+        this.updateGatewayClassesList(data.gatewayClass || []);
+
+        // Update Envoy Gateway Policies
+        this.updateResourcesList('clienttrafficpolicies-list', data.clientTrafficPolicies || [], 'Client Traffic Policies');
+        this.updateResourcesList('backendtrafficpolicies-list', data.backendTrafficPolicies || [], 'Backend Traffic Policies');
+        this.updateResourcesList('backendtlspolicies-list', data.backendTLSPolicies || [], 'Backend TLS Policies');
+        this.updateResourcesList('securitypolicies-list', data.securityPolicies || [], 'Security Policies');
+        this.updateResourcesList('envoypatchpolicies-list', data.envoyPatchPolicies || [], 'Envoy Patch Policies');
+        this.updateResourcesList('envoyextensionpolicies-list', data.envoyExtensionPolicies || [], 'Envoy Extension Policies');
+
+        // Update Kubernetes Resources
+        this.updateResourcesList('services-list', data.services || [], 'Services');
+        this.updateResourcesList('secrets-list', data.secrets || [], 'Secrets');
+        this.updateResourcesList('configmaps-list', data.configMaps || [], 'ConfigMaps');
+        this.updateResourcesList('namespaces-list', data.namespaces || [], 'Namespaces');
+        this.updateResourcesList('endpointslices-list', data.endpointSlices || [], 'Endpoint Slices');
+
+        // Update Other Resources
+        this.updateResourcesList('referencegrants-list', data.referenceGrants || [], 'Reference Grants');
+        this.updateResourcesList('httproutefilters-list', data.httpRouteFilters || [], 'HTTP Route Filters');
+        this.updateResourcesList('envoyproxies-list', data.envoyProxies || [], 'Envoy Proxies');
+        this.updateResourcesList('backends-list', data.backends || [], 'Backends');
+        this.updateResourcesList('serviceimports-list', data.serviceImports || [], 'Service Imports');
+
+        // Update config summary
+        this.updateConfigSummary(data);
+    },
+
+    // Update gateways list
+    updateGatewaysList: function(gateways) {
+        const container = document.getElementById('gateways-list');
+        if (!container) return;
+
+        if (gateways.length === 0) {
+            container.innerHTML = '<p class="text-muted">No gateways found</p>';
+            return;
+        }
+
+        const gatewaysHtml = gateways.map(gw => `
+            <div class="resource-item">
+                <div class="resource-header">
+                    <div class="resource-name">${gw.name}</div>
+                    <div class="resource-namespace">${gw.namespace}</div>
+                </div>
+            </div>
+        `).join('');
+
+        container.innerHTML = gatewaysHtml;
+    },
+
+    // Update HTTP routes list
+    updateHTTPRoutesList: function(httpRoutes) {
+        this.updateRoutesList('httproutes-list', httpRoutes, 'HTTP routes');
+    },
+
+    // Update GRPC routes list
+    updateGRPCRoutesList: function(grpcRoutes) {
+        this.updateRoutesList('grpcroutes-list', grpcRoutes, 'GRPC routes');
+    },
+
+    // Update TLS routes list
+    updateTLSRoutesList: function(tlsRoutes) {
+        this.updateRoutesList('tlsroutes-list', tlsRoutes, 'TLS routes');
+    },
+
+    // Update TCP routes list
+    updateTCPRoutesList: function(tcpRoutes) {
+        this.updateRoutesList('tcproutes-list', tcpRoutes, 'TCP routes');
+    },
+
+    // Update UDP routes list
+    updateUDPRoutesList: function(udpRoutes) {
+        this.updateRoutesList('udproutes-list', udpRoutes, 'UDP routes');
+    },
+
+    // Generic method to update any routes list
+    updateRoutesList: function(containerId, routes, routeType) {
+        const container = document.getElementById(containerId);
+        if (!container) return;
+
+        if (routes.length === 0) {
+            container.innerHTML = `<p class="text-muted">No ${routeType} found</p>`;
+            return;
+        }
+
+        const routesHtml = routes.map(route => `
+            <div class="resource-item">
+                <div class="resource-header">
+                    <div class="resource-name">${route.name}</div>
+                    <div class="resource-namespace">${route.namespace}</div>
+                </div>
+            </div>
+        `).join('');
+
+        container.innerHTML = routesHtml;
+    },
+
+    // Update gateway classes list
+    updateGatewayClassesList: function(gatewayClasses) {
+        const container = document.getElementById('gatewayclasses-list');
+        if (!container) return;
+
+        if (gatewayClasses.length === 0) {
+            container.innerHTML = '<p class="text-muted">No gateway classes found</p>';
+            return;
+        }
+
+        const classesHtml = gatewayClasses.map(gc => `
+            <div class="resource-item">
+                <div class="resource-header">
+                    <div class="resource-name">${gc.name}</div>
+                    <div class="resource-scope">Cluster-scoped</div>
+                </div>
+            </div>
+        `).join('');
+
+        container.innerHTML = classesHtml;
+    },
+
+    // Generic method to update any resource list
+    updateResourcesList: function(containerId, resources, resourceType) {
+        const container = document.getElementById(containerId);
+        if (!container) return;
+
+        if (resources.length === 0) {
+            container.innerHTML = `<p class="text-muted">No ${resourceType.toLowerCase()} found</p>`;
+            return;
+        }
+
+        const resourcesHtml = resources.map(resource => `
+            <div class="resource-item">
+                <div class="resource-header">
+                    <div class="resource-name">${resource.name}</div>
+                    <div class="resource-namespace">${resource.namespace || 'Cluster-scoped'}</div>
+                </div>
+            </div>
+        `).join('');
+
+        container.innerHTML = resourcesHtml;
+    },
+
+    // Update config summary
+    updateConfigSummary: function(data) {
+        const container = document.getElementById('config-summary');
+        if (!container) return;
+
+        const summary = `
+            <div class="config-summary-grid">
+                <!-- Gateway API Core Resources -->
+                <div class="summary-item">
+                    <h3>${data.gateways ? data.gateways.length : 0}</h3>
+                    <p>Gateways</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.httpRoutes ? data.httpRoutes.length : 0}</h3>
+                    <p>HTTP Routes</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.grpcRoutes ? data.grpcRoutes.length : 0}</h3>
+                    <p>GRPC Routes</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.tlsRoutes ? data.tlsRoutes.length : 0}</h3>
+                    <p>TLS Routes</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.tcpRoutes ? data.tcpRoutes.length : 0}</h3>
+                    <p>TCP Routes</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.udpRoutes ? data.udpRoutes.length : 0}</h3>
+                    <p>UDP Routes</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.gatewayClass ? data.gatewayClass.length : 0}</h3>
+                    <p>Gateway Classes</p>
+                </div>
+
+                <!-- Envoy Gateway Policies -->
+                <div class="summary-item">
+                    <h3>${data.clientTrafficPolicies ? data.clientTrafficPolicies.length : 0}</h3>
+                    <p>Client Traffic Policies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.backendTrafficPolicies ? data.backendTrafficPolicies.length : 0}</h3>
+                    <p>Backend Traffic Policies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.backendTLSPolicies ? data.backendTLSPolicies.length : 0}</h3>
+                    <p>Backend TLS Policies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.securityPolicies ? data.securityPolicies.length : 0}</h3>
+                    <p>Security Policies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.envoyPatchPolicies ? data.envoyPatchPolicies.length : 0}</h3>
+                    <p>Envoy Patch Policies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.envoyExtensionPolicies ? data.envoyExtensionPolicies.length : 0}</h3>
+                    <p>Envoy Extension Policies</p>
+                </div>
+
+                <!-- Kubernetes Resources -->
+                <div class="summary-item">
+                    <h3>${data.services ? data.services.length : 0}</h3>
+                    <p>Services</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.secrets ? data.secrets.length : 0}</h3>
+                    <p>Secrets</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.configMaps ? data.configMaps.length : 0}</h3>
+                    <p>ConfigMaps</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.namespaces ? data.namespaces.length : 0}</h3>
+                    <p>Namespaces</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.endpointSlices ? data.endpointSlices.length : 0}</h3>
+                    <p>Endpoint Slices</p>
+                </div>
+
+                <!-- Other Resources -->
+                <div class="summary-item">
+                    <h3>${data.referenceGrants ? data.referenceGrants.length : 0}</h3>
+                    <p>Reference Grants</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.httpRouteFilters ? data.httpRouteFilters.length : 0}</h3>
+                    <p>HTTP Route Filters</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.envoyProxies ? data.envoyProxies.length : 0}</h3>
+                    <p>Envoy Proxies</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.backends ? data.backends.length : 0}</h3>
+                    <p>Backends</p>
+                </div>
+                <div class="summary-item">
+                    <h3>${data.serviceImports ? data.serviceImports.length : 0}</h3>
+                    <p>Service Imports</p>
+                </div>
+            </div>
+        `;
+
+        container.innerHTML = summary;
+    },
+    
+    // Show loading indicator
+    showLoading: function(containerId) {
+        const container = document.getElementById(containerId);
+        if (container) {
+            container.innerHTML = '<div class="loading"></div> Loading...';
+        }
+    },
+    
+    // Hide loading indicator
+    hideLoading: function(containerId) {
+        // Loading will be replaced by content
+    },
+    
+    // Show error message
+    showError: function(containerId, message) {
+        const container = document.getElementById(containerId);
+        if (container) {
+            container.innerHTML = `<div class="info-box error">${message}</div>`;
+        }
+    },
+    
+    // Start auto-refresh
+    startAutoRefresh: function() {
+        if (this.refreshTimer) {
+            clearInterval(this.refreshTimer);
+        }
+        this.refreshTimer = setInterval(() => {
+            this.loadPageData();
+        }, this.config.refreshInterval);
+    },
+    
+    // Stop auto-refresh
+    stopAutoRefresh: function() {
+        if (this.refreshTimer) {
+            clearInterval(this.refreshTimer);
+            this.refreshTimer = null;
+        }
+    },
+    
+    // Manual refresh
+    refresh: function() {
+        this.loadPageData();
+    }
+};
+
+// Initialize when DOM is loaded
+document.addEventListener('DOMContentLoaded', function() {
+    window.EnvoyGatewayAdmin.init();
+});
+
+// Cleanup on page unload
+window.addEventListener('beforeunload', function() {
+    window.EnvoyGatewayAdmin.stopAutoRefresh();
+});
diff --git a/internal/admin/console/templates/base.html b/internal/admin/console/templates/base.html
new file mode 100644
index 000000000..ce3487ced
--- /dev/null
+++ b/internal/admin/console/templates/base.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{{.Title}} - Envoy Gateway Admin</title>
+    <link rel="stylesheet" href="/static/css/admin.css">
+    <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚀</text></svg>">
+</head>
+<body>
+    <header class="header">
+        <div class="header-content">
+            <div class="logo">
+                🚀 Envoy Gateway Admin
+            </div>
+            <nav class="nav">
+                <a href="/">Dashboard</a>
+                <a href="/server_info">Server Info</a>
+                <a href="/config_dump">Config Dump</a>
+                <a href="/stats">Stats</a>
+                <a href="/pprof">Profiling</a>
+            </nav>
+        </div>
+    </header>
+
+    <main class="container">
+        {{template "content" .}}
+    </main>
+
+    <footer class="footer">
+        <p>&copy; 2025 Envoy Gateway. Built with ❤️ by the Envoy Gateway community.</p>
+        <p>
+            <a href="https://gateway.envoyproxy.io/" target="_blank">Documentation</a> |
+            <a href="https://github.com/envoyproxy/gateway" target="_blank">GitHub</a>
+        </p>
+    </footer>
+
+    <script src="/static/js/admin.js"></script>
+</body>
+</html>
diff --git a/internal/admin/console/templates/config_dump.html b/internal/admin/console/templates/config_dump.html
new file mode 100644
index 000000000..1895b012d
--- /dev/null
+++ b/internal/admin/console/templates/config_dump.html
@@ -0,0 +1,323 @@
+{{template "base.html" .}}
+
+{{define "config-dump-content"}}
+<div class="card">
+    <div class="card-header">
+        <h1 class="card-title">{{.Title}}</h1>
+        <div style="display: flex; align-items: center; gap: 1rem;">
+            <a href="/api/config_dump_all" class="btn btn-primary" target="_blank">
+                🔍 View Complete Config (JSON)
+            </a>
+            <label>
+                <input type="checkbox" id="auto-refresh"> Auto-refresh (30s)
+            </label>
+            <button class="btn btn-secondary" onclick="EnvoyGatewayAdmin.refresh()">
+                Refresh
+            </button>
+        </div>
+    </div>
+    <div class="card-body">
+        <p>This page displays the current configuration state of Envoy Gateway, including all Gateway API resources and their status.</p>
+    </div>
+</div>
+
+<div class="card">
+    <div class="card-header">
+        <h2 class="card-title">📊 Configuration Summary</h2>
+    </div>
+    <div class="card-body">
+        <p>This section provides a high-level overview of the current configuration state:</p>
+        <div id="config-summary">
+            <div class="loading"></div> Loading configuration summary...
+        </div>
+    </div>
+</div>
+
+<div class="config-resources-grid">
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🚪 Gateways</h2>
+        </div>
+        <div class="card-body">
+            <p>Gateway resources define the entry points for traffic into the cluster. They specify which ports and protocols are exposed.</p>
+            <div id="gateways-list">
+                <div class="loading"></div> Loading gateways...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🛣️ HTTP Routes</h2>
+        </div>
+        <div class="card-body">
+            <p>HTTPRoute resources define how HTTP requests are routed to backend services.</p>
+            <div id="httproutes-list">
+                <div class="loading"></div> Loading HTTP routes...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔧 GRPC Routes</h2>
+        </div>
+        <div class="card-body">
+            <p>GRPCRoute resources define how gRPC requests are routed to backend services.</p>
+            <div id="grpcroutes-list">
+                <div class="loading"></div> Loading GRPC routes...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔒 TLS Routes</h2>
+        </div>
+        <div class="card-body">
+            <p>TLSRoute resources define how TLS connections are routed to backend services.</p>
+            <div id="tlsroutes-list">
+                <div class="loading"></div> Loading TLS routes...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔌 TCP Routes</h2>
+        </div>
+        <div class="card-body">
+            <p>TCPRoute resources define how TCP connections are routed to backend services.</p>
+            <div id="tcproutes-list">
+                <div class="loading"></div> Loading TCP routes...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">📡 UDP Routes</h2>
+        </div>
+        <div class="card-body">
+            <p>UDPRoute resources define how UDP traffic is routed to backend services.</p>
+            <div id="udproutes-list">
+                <div class="loading"></div> Loading UDP routes...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🏷️ Gateway Classes</h2>
+        </div>
+        <div class="card-body">
+            <p>GatewayClass resources define the type of Gateway that can be deployed.</p>
+            <div id="gatewayclasses-list">
+                <div class="loading"></div> Loading gateway classes...
+            </div>
+        </div>
+    </div>
+
+    <!-- Envoy Gateway Policies -->
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🚦 Client Traffic Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>ClientTrafficPolicy resources define traffic management policies for client connections.</p>
+            <div id="clienttrafficpolicies-list">
+                <div class="loading"></div> Loading client traffic policies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔄 Backend Traffic Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>BackendTrafficPolicy resources define traffic management policies for backend connections.</p>
+            <div id="backendtrafficpolicies-list">
+                <div class="loading"></div> Loading backend traffic policies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔐 Backend TLS Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>BackendTLSPolicy resources define TLS configuration for backend connections.</p>
+            <div id="backendtlspolicies-list">
+                <div class="loading"></div> Loading backend TLS policies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🛡️ Security Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>SecurityPolicy resources define security configurations and authentication policies.</p>
+            <div id="securitypolicies-list">
+                <div class="loading"></div> Loading security policies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔧 Envoy Patch Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>EnvoyPatchPolicy resources define custom patches to Envoy configuration.</p>
+            <div id="envoypatchpolicies-list">
+                <div class="loading"></div> Loading Envoy patch policies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔌 Envoy Extension Policies</h2>
+        </div>
+        <div class="card-body">
+            <p>EnvoyExtensionPolicy resources define external processing and extension configurations.</p>
+            <div id="envoyextensionpolicies-list">
+                <div class="loading"></div> Loading Envoy extension policies...
+            </div>
+        </div>
+    </div>
+
+    <!-- Kubernetes Resources -->
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🌐 Services</h2>
+        </div>
+        <div class="card-body">
+            <p>Service resources define network services and their endpoints.</p>
+            <div id="services-list">
+                <div class="loading"></div> Loading services...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔑 Secrets</h2>
+        </div>
+        <div class="card-body">
+            <p>Secret resources contain sensitive data such as certificates and keys.</p>
+            <div id="secrets-list">
+                <div class="loading"></div> Loading secrets...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">📋 ConfigMaps</h2>
+        </div>
+        <div class="card-body">
+            <p>ConfigMap resources contain configuration data for applications.</p>
+            <div id="configmaps-list">
+                <div class="loading"></div> Loading config maps...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">📁 Namespaces</h2>
+        </div>
+        <div class="card-body">
+            <p>Namespace resources provide scope for names and resource isolation.</p>
+            <div id="namespaces-list">
+                <div class="loading"></div> Loading namespaces...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🎯 Endpoint Slices</h2>
+        </div>
+        <div class="card-body">
+            <p>EndpointSlice resources define network endpoints for services.</p>
+            <div id="endpointslices-list">
+                <div class="loading"></div> Loading endpoint slices...
+            </div>
+        </div>
+    </div>
+
+    <!-- Other Resources -->
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔗 Reference Grants</h2>
+        </div>
+        <div class="card-body">
+            <p>ReferenceGrant resources allow cross-namespace references in Gateway API.</p>
+            <div id="referencegrants-list">
+                <div class="loading"></div> Loading reference grants...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔍 HTTP Route Filters</h2>
+        </div>
+        <div class="card-body">
+            <p>HTTPRouteFilter resources define custom filters for HTTP routes.</p>
+            <div id="httproutefilters-list">
+                <div class="loading"></div> Loading HTTP route filters...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">⚙️ Envoy Proxies</h2>
+        </div>
+        <div class="card-body">
+            <p>EnvoyProxy resources define Envoy proxy configuration and deployment settings.</p>
+            <div id="envoyproxies-list">
+                <div class="loading"></div> Loading Envoy proxies...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔙 Backends</h2>
+        </div>
+        <div class="card-body">
+            <p>Backend resources define backend service configurations.</p>
+            <div id="backends-list">
+                <div class="loading"></div> Loading backends...
+            </div>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">📤 Service Imports</h2>
+        </div>
+        <div class="card-body">
+            <p>ServiceImport resources define imported services from other clusters.</p>
+            <div id="serviceimports-list">
+                <div class="loading"></div> Loading service imports...
+            </div>
+        </div>
+    </div>
+
+</div>
+
+
+
+<script>
+
+</script>
+{{end}}
diff --git a/internal/admin/console/templates/index.html b/internal/admin/console/templates/index.html
new file mode 100644
index 000000000..24ed4ff48
--- /dev/null
+++ b/internal/admin/console/templates/index.html
@@ -0,0 +1,110 @@
+{{template "base.html" .}}
+
+{{define "index-content"}}
+<div class="card">
+    <div class="card-header">
+        <h1 class="card-title">{{.Title}}</h1>
+        <div style="display: flex; align-items: center; gap: 1rem;">
+            <label>
+                <input type="checkbox" id="auto-refresh"> Auto-refresh (30s)
+            </label>
+            <button class="btn btn-secondary" onclick="EnvoyGatewayAdmin.refresh()">
+                Refresh
+            </button>
+        </div>
+    </div>
+    <div class="card-body">
+        <p>Welcome to the Envoy Gateway Admin Console. This interface provides access to various administrative functions and monitoring capabilities.</p>
+        
+        <div id="system-info">
+            <div class="loading"></div> Loading system information...
+        </div>
+    </div>
+</div>
+
+<div class="dashboard-grid">
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🖥️ Server Information</h2>
+        </div>
+        <div class="card-body">
+            <p>View detailed information about the Envoy Gateway server status, components, and runtime information.</p>
+            <a href="/server_info" class="btn">View Server Info</a>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">⚙️ Configuration Dump</h2>
+        </div>
+        <div class="card-body">
+            <p>Access the current configuration state including Gateways, HTTPRoutes, and other resources.</p>
+            <a href="/config_dump" class="btn">View Config Dump</a>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">📊 Statistics</h2>
+        </div>
+        <div class="card-body">
+            <p>Monitor performance metrics and statistics from the Envoy Gateway control plane.</p>
+            <a href="/stats" class="btn">View Statistics</a>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h2 class="card-title">🔍 Performance Profiling</h2>
+        </div>
+        <div class="card-body">
+            <p>Access pprof endpoints for performance analysis and debugging.</p>
+            <a href="/pprof" class="btn">View Profiling</a>
+        </div>
+    </div>
+</div>
+
+<div class="card">
+    <div class="card-header">
+        <h2 class="card-title">📚 Quick Links</h2>
+    </div>
+    <div class="card-body">
+        <div class="quick-links-grid">
+            <div class="quick-links-section">
+                <h3>📖 Documentation</h3>
+                <div class="quick-links-list">
+                    <a href="https://gateway.envoyproxy.io/" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">📚</span>
+                        <span class="quick-link-text">Envoy Gateway Docs</span>
+                    </a>
+                    <a href="https://gateway.envoyproxy.io/latest/api/" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">🔧</span>
+                        <span class="quick-link-text">API Reference</span>
+                    </a>
+                    <a href="https://gateway.envoyproxy.io/latest/tasks/" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">📝</span>
+                        <span class="quick-link-text">Tasks & Tutorials</span>
+                    </a>
+                </div>
+            </div>
+            <div class="quick-links-section">
+                <h3>👥 Community</h3>
+                <div class="quick-links-list">
+                    <a href="https://github.com/envoyproxy/gateway" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">🐙</span>
+                        <span class="quick-link-text">GitHub Repository</span>
+                    </a>
+                    <a href="https://github.com/envoyproxy/gateway/issues" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">🐛</span>
+                        <span class="quick-link-text">Issue Tracker</span>
+                    </a>
+                    <a href="https://envoyproxy.slack.com/" target="_blank" class="quick-link">
+                        <span class="quick-link-icon">💬</span>
+                        <span class="quick-link-text">Slack Community</span>
+                    </a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{end}}
diff --git a/internal/admin/console/templates/pprof.html b/internal/admin/console/templates/pprof.html
new file mode 100644
index 000000000..66c919066
--- /dev/null
+++ b/internal/admin/console/templates/pprof.html
@@ -0,0 +1,118 @@
+{{template "base.html" .}}
+
+{{define "pprof-content"}}
+<div class="card">
+    <div class="card-header">
+        <h1 class="card-title">{{.Title}}</h1>
+    </div>
+    <div class="card-body">
+        {{if .EnablePprof}}
+        <p>Performance profiling endpoints are available for debugging and performance analysis.</p>
+        
+        <div class="info-box">
+            <div>
+                <strong>Status:</strong> <span class="status running">Enabled</span>
+            </div>
+        </div>
+
+        <div class="dashboard-grid">
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">🔍 CPU Profile</h2>
+                </div>
+                <div class="card-body">
+                    <p>CPU profiling shows where the program spends its time while actively consuming CPU cycles.</p>
+                    <a href="/debug/pprof/profile" class="btn" target="_blank">Download CPU Profile</a>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">🧠 Memory Heap</h2>
+                </div>
+                <div class="card-body">
+                    <p>Heap profiling reports memory allocation samples for monitoring current and historical memory usage.</p>
+                    <a href="/debug/pprof/heap" class="btn" target="_blank">Download Heap Profile</a>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">🔄 Goroutines</h2>
+                </div>
+                <div class="card-body">
+                    <p>Stack traces of all current goroutines.</p>
+                    <a href="/debug/pprof/goroutine" class="btn" target="_blank">View Goroutines</a>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">🔒 Mutex</h2>
+                </div>
+                <div class="card-body">
+                    <p>Stack traces of holders of contended mutexes.</p>
+                    <a href="/debug/pprof/mutex" class="btn" target="_blank">View Mutex Profile</a>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">🚫 Block</h2>
+                </div>
+                <div class="card-body">
+                    <p>Stack traces that led to blocking on synchronization primitives.</p>
+                    <a href="/debug/pprof/block" class="btn" target="_blank">View Block Profile</a>
+                </div>
+            </div>
+
+            <div class="card">
+                <div class="card-header">
+                    <h2 class="card-title">📊 All Profiles</h2>
+                </div>
+                <div class="card-body">
+                    <p>Index page with links to all available profiles.</p>
+                    <a href="/debug/pprof/" class="btn" target="_blank">View All Profiles</a>
+                </div>
+            </div>
+        </div>
+
+        <div class="card">
+            <div class="card-header">
+                <h2 class="card-title">💡 Usage Tips</h2>
+            </div>
+            <div class="card-body">
+                <ul>
+                    <li><strong>CPU Profile:</strong> Use <code>go tool pprof &lt;profile-url&gt;</code> to analyze CPU usage</li>
+                    <li><strong>Memory Heap:</strong> Identify memory leaks and high memory usage patterns</li>
+                    <li><strong>Goroutines:</strong> Debug goroutine leaks and concurrency issues</li>
+                    <li><strong>Mutex/Block:</strong> Find contention points in your application</li>
+                </ul>
+                
+                <div class="code">
+# Example: Analyze CPU profile
+go tool pprof http://localhost:19000/debug/pprof/profile
+
+# Example: Analyze heap profile  
+go tool pprof http://localhost:19000/debug/pprof/heap
+
+# Example: View goroutines in browser
+go tool pprof -http=:8080 http://localhost:19000/debug/pprof/goroutine
+                </div>
+            </div>
+        </div>
+        {{else}}
+        <div class="info-box warning">
+            <div>
+                <strong>Status:</strong> <span class="status warning">Disabled</span>
+            </div>
+            <div>
+                pprof endpoints are currently disabled. Enable them in the Envoy Gateway configuration.
+            </div>
+        </div>
+
+
+        {{end}}
+    </div>
+</div>
+{{end}}
diff --git a/internal/admin/console/templates/server_info.html b/internal/admin/console/templates/server_info.html
new file mode 100644
index 000000000..fbaccd9ad
--- /dev/null
+++ b/internal/admin/console/templates/server_info.html
@@ -0,0 +1,43 @@
+{{template "base.html" .}}
+
+{{define "server-info-content"}}
+<div class="card">
+    <div class="card-header">
+        <h1 class="card-title">{{.Title}}</h1>
+        <div style="display: flex; align-items: center; gap: 1rem;">
+            <label>
+                <input type="checkbox" id="auto-refresh"> Auto-refresh (30s)
+            </label>
+            <button class="btn btn-secondary" onclick="EnvoyGatewayAdmin.refresh()">
+                Refresh
+            </button>
+        </div>
+    </div>
+    <div class="card-body">
+        <p>This page displays detailed information about the Envoy Gateway server status and components.</p>
+
+        <div id="server-info">
+            <div class="loading"></div> Loading server information...
+        </div>
+    </div>
+</div>
+
+
+
+
+
+
+<div class="card">
+    <div class="card-header">
+        <h2 class="card-title">⚙️ EnvoyGateway Configuration</h2>
+    </div>
+    <div class="card-body">
+        <p>Current EnvoyGateway configuration loaded from the config file or defaults.</p>
+
+        <div id="envoy-gateway-config">
+            <div class="loading"></div> Loading EnvoyGateway configuration...
+        </div>
+    </div>
+</div>
+
+{{end}}
diff --git a/internal/admin/console/templates/stats.html b/internal/admin/console/templates/stats.html
new file mode 100644
index 000000000..42e1ea336
--- /dev/null
+++ b/internal/admin/console/templates/stats.html
@@ -0,0 +1,102 @@
+{{template "base.html" .}}
+
+{{define "stats-content"}}
+<div class="card">
+    <div class="card-header">
+        <h1 class="card-title">{{.Title}}</h1>
+    </div>
+    <div class="card-body">
+        <p>This page provides access to Envoy Gateway statistics and metrics. Metrics are exposed in Prometheus format for monitoring and alerting.</p>
+
+        <div class="info-box">
+            <div>
+                <strong>Metrics Endpoint:</strong><br>
+                <a href="/api/metrics" target="_blank">/api/metrics</a> (Available on Admin Port)
+            </div>
+            <div>
+                <a href="/api/metrics" class="btn btn-primary" target="_blank">
+                    📊 View Metrics
+                </a>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="card">
+    <div class="card-header">
+        <h2 class="card-title">📈 Metrics Categories</h2>
+    </div>
+    <div class="card-body">
+        <p>Envoy Gateway exports metrics in six main categories to monitor different aspects of the control plane:</p>
+    </div>
+</div>
+
+<div class="dashboard-grid">
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">🔄 Watching Components</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Monitor the internal event-driven architecture of Envoy Gateway components.</p>
+
+            <p><em>Components: Gateway API Translator, Infrastructure Manager, xDS Server, xDS Translator, Global RateLimit</em></p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">📝 Status Updater</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Track status updates for Gateway API resources to ensure proper resource state management.</p>
+
+            <p><em>Resources: GatewayClass, Gateway, HTTPRoute, GRPCRoute, TLSRoute, etc.</em></p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">🌐 xDS Server</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Monitor the xDS (Discovery Service) server that provides configuration to Envoy proxies.</p>
+
+            <p><em>Critical for proxy configuration delivery and connection health</em></p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">🏗️ Infrastructure Manager</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Monitor Kubernetes resource operations performed by Envoy Gateway.</p>
+
+            <p><em>Tracks Deployments, Services, ConfigMaps, and other Kubernetes resources</em></p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">🔧 Wasm</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Monitor WebAssembly extension management and remote fetch operations.</p>
+
+            <p><em>Essential for custom extension performance and availability</em></p>
+        </div>
+    </div>
+
+    <div class="card">
+        <div class="card-header">
+            <h3 class="card-title">🗺️ Topology Injector</h3>
+        </div>
+        <div class="card-body">
+            <p><strong>Core Function:</strong> Monitor the webhook that injects node topology information into EnvoyProxy pods.</p>
+
+            <p><em>Critical for multi-zone deployments and traffic locality optimization</em></p>
+        </div>
+    </div>
+</div>
+
+{{end}}
diff --git a/internal/admin/server.go b/internal/admin/server.go
index 3bb301acc..9da741596 100644
--- a/internal/admin/server.go
+++ b/internal/admin/server.go
@@ -13,17 +13,26 @@ import (
 
 	"github.com/davecgh/go-spew/spew"
 
+	"github.com/envoyproxy/gateway/internal/admin/console"
 	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/message"
 )
 
+type Config struct {
+	Server            config.Server
+	ProviderResources *message.ProviderResources
+}
+
 type Runner struct {
-	cfg    *config.Server
-	server *http.Server
+	cfg               *config.Server
+	server            *http.Server
+	providerResources *message.ProviderResources
 }
 
-func New(cfg *config.Server) *Runner {
+func New(cfg *Config) *Runner {
 	return &Runner{
-		cfg: cfg,
+		cfg:               &cfg.Server,
+		providerResources: cfg.ProviderResources,
 	}
 }
 
@@ -53,10 +62,15 @@ func (r *Runner) Close() error {
 func (r *Runner) start() error {
 	handlers := http.NewServeMux()
 	address := r.cfg.EnvoyGateway.GetEnvoyGatewayAdminAddress()
-	enablePprof := r.cfg.EnvoyGateway.GetEnvoyGatewayAdmin().EnablePprof
+	adminConfig := r.cfg.EnvoyGateway.GetEnvoyGatewayAdmin()
+	enablePprof := adminConfig.EnablePprof
 
 	adminLogger := r.cfg.Logger.WithName("admin")
-	adminLogger.Info("starting admin server", "address", address, "enablePprof", enablePprof)
+	adminLogger.Info("starting admin server", "address", address, "enablePprof", enablePprof, "enableConsole", true)
+
+	// Register console handlers (always enabled)
+	consoleHandler := console.NewHandler(r.cfg, r.providerResources)
+	consoleHandler.RegisterRoutes(handlers)
 
 	if enablePprof {
 		// Serve pprof endpoints to aid in live debugging.
diff --git a/internal/cmd/egctl/dashboard.go b/internal/cmd/egctl/dashboard.go
index 349662dbe..67994c449 100644
--- a/internal/cmd/egctl/dashboard.go
+++ b/internal/cmd/egctl/dashboard.go
@@ -18,6 +18,7 @@ func newDashboardCommand() *cobra.Command {
 	}
 
 	c.AddCommand(newEnvoyDashboardCmd())
+	c.AddCommand(newEnvoyGatewayDashboardCmd())
 
 	return c
 }
diff --git a/internal/cmd/egctl/dashboard_envoy_gateway.go b/internal/cmd/egctl/dashboard_envoy_gateway.go
new file mode 100644
index 000000000..be6bb6ece
--- /dev/null
+++ b/internal/cmd/egctl/dashboard_envoy_gateway.go
@@ -0,0 +1,105 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package egctl
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/types"
+
+	kube "github.com/envoyproxy/gateway/internal/kubernetes"
+)
+
+const (
+	// envoyGatewayAdminPort is the port for the Envoy Gateway admin server
+	envoyGatewayAdminPort = 19000
+)
+
+func newEnvoyGatewayDashboardCmd() *cobra.Command {
+	var namespace string
+	var listenPort int
+
+	dashboardCmd := &cobra.Command{
+		Use:   "envoy-gateway",
+		Short: "Open Envoy Gateway admin console in your browser",
+		Long:  `Open Envoy Gateway admin console in your browser.`,
+		Example: `  # Open Envoy Gateway admin console in your browser.
+  egctl x dashboard envoy-gateway
+
+  # Open Envoy Gateway admin console with a specific namespace
+  egctl x dashboard envoy-gateway -n custom-namespace
+
+  # Open Envoy Gateway admin console with a specific port
+  egctl x dashboard envoy-gateway --port 8080
+`,
+		Aliases: []string{"eg"},
+		RunE: func(c *cobra.Command, args []string) error {
+			if listenPort > 65535 || listenPort < 0 {
+				return fmt.Errorf("invalid port number range")
+			}
+
+			kubeClient, err := getCLIClient()
+			if err != nil {
+				return err
+			}
+
+			// Find the Envoy Gateway deployment pod
+			labelSelectors := []string{"control-plane=envoy-gateway"}
+			pl, err := kubeClient.PodsForSelector(namespace, labelSelectors...)
+			if err != nil {
+				return fmt.Errorf("not able to locate Envoy Gateway pod: %w", err)
+			}
+			if len(pl.Items) < 1 {
+				return fmt.Errorf("no Envoy Gateway pods found in namespace %s", namespace)
+			}
+
+			podName := pl.Items[0].Name
+			fmt.Fprintf(c.OutOrStdout(), "Found Envoy Gateway pod: %s\n", podName)
+
+			return forwardToEnvoyGatewayAdmin(podName, namespace, listenPort, kubeClient, c.OutOrStdout())
+		},
+	}
+
+	dashboardCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "envoy-gateway-system", "Namespace where Envoy Gateway is installed.")
+	dashboardCmd.PersistentFlags().IntVarP(&listenPort, "port", "p", 0, "Local port to listen to.")
+
+	return dashboardCmd
+}
+
+// forwardToEnvoyGatewayAdmin forwards a local port to the Envoy Gateway admin port
+func forwardToEnvoyGatewayAdmin(podName, namespace string, listenPort int, client kube.CLIClient, writer io.Writer) error {
+	meta := types.NamespacedName{
+		Namespace: namespace,
+		Name:      podName,
+	}
+
+	fmt.Fprintf(writer, "Setting up port-forward to Envoy Gateway admin server...\n")
+
+	fw, err := kube.NewLocalPortForwarder(client, meta, listenPort, envoyGatewayAdminPort)
+	if err != nil {
+		return fmt.Errorf("could not build port forwarder for Envoy Gateway: %w", err)
+	}
+
+	if err = fw.Start(); err != nil {
+		fw.Stop()
+		return fmt.Errorf("could not start port forwarder for Envoy Gateway: %w", err)
+	}
+
+	ClosePortForwarderOnInterrupt(fw)
+
+	url := fmt.Sprintf("http://%s", fw.Address())
+	fmt.Fprintf(writer, "Envoy Gateway admin console URL: %s\n", url)
+	fmt.Fprintf(writer, "Opening browser to access the Envoy Gateway admin console...\n")
+	fmt.Fprintf(writer, "Press Ctrl+C to quit\n")
+
+	openBrowser(url, writer)
+
+	fw.WaitForStop()
+
+	return nil
+}
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 618b221ff..6ba9f77b9 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -215,7 +215,10 @@ func startRunners(ctx context.Context, cfg *config.Server) (err error) {
 		{
 			// Start the Admin Server
 			// It provides admin endpoints including pprof for debugging.
-			runner: admin.New(cfg),
+			runner: admin.New(&admin.Config{
+				Server:            *cfg,
+				ProviderResources: channels.pResources,
+			}),
 		},
 		{
 			// Start the Metrics Server
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index c2e52bb98..631de3247 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -212,20 +212,28 @@ func (t *Translator) processURLRewriteFilter(
 		return
 	}
 
-	newURLRewrite := &ir.URLRewrite{}
-
 	if rewrite.Hostname != nil {
 		if err := t.validateHostname(string(*rewrite.Hostname)); err != nil {
 			updateRouteStatusForFilter(filterContext, err.Error())
 			return
 		}
 		redirectHost := string(*rewrite.Hostname)
-		newURLRewrite.Host = &ir.HTTPHostModifier{
-			Name: &redirectHost,
+		if filterContext.URLRewrite == nil {
+			filterContext.URLRewrite = &ir.URLRewrite{
+				Host: &ir.HTTPHostModifier{
+					Name: &redirectHost,
+				},
+			}
+		} else if filterContext.URLRewrite.Host == nil {
+			filterContext.URLRewrite.Host = &ir.HTTPHostModifier{
+				Name: &redirectHost,
+			}
 		}
 	}
 
 	if rewrite.Path != nil {
+		var pathModifier *ir.ExtendedHTTPPathModifier
+
 		switch rewrite.Path.Type {
 		case gwapiv1.FullPathHTTPPathModifier:
 			if rewrite.Path.ReplacePrefixMatch != nil {
@@ -241,7 +249,7 @@ func (t *Translator) processURLRewriteFilter(
 				return
 			}
 			if rewrite.Path.ReplaceFullPath != nil {
-				newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
+				pathModifier = &ir.ExtendedHTTPPathModifier{
 					HTTPPathModifier: ir.HTTPPathModifier{
 						FullReplace: rewrite.Path.ReplaceFullPath,
 					},
@@ -261,7 +269,7 @@ func (t *Translator) processURLRewriteFilter(
 				return
 			}
 			if rewrite.Path.ReplacePrefixMatch != nil {
-				newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
+				pathModifier = &ir.ExtendedHTTPPathModifier{
 					HTTPPathModifier: ir.HTTPPathModifier{
 						PrefixMatchReplace: rewrite.Path.ReplacePrefixMatch,
 					},
@@ -275,9 +283,14 @@ func (t *Translator) processURLRewriteFilter(
 					rewrite.Path.Type))
 			return
 		}
+		if filterContext.URLRewrite == nil {
+			filterContext.URLRewrite = &ir.URLRewrite{
+				Path: pathModifier,
+			}
+		} else if filterContext.URLRewrite.Path == nil {
+			filterContext.URLRewrite.Path = pathModifier
+		}
 	}
-
-	filterContext.URLRewrite = newURLRewrite
 }
 
 func (t *Translator) processRedirectFilter(
diff --git a/site/content/en/v1.4/tasks/operations/airgap-deployment.md b/site/content/en/v1.4/tasks/operations/airgap-deployment.md
new file mode 100644
index 000000000..8b72c776d
--- /dev/null
+++ b/site/content/en/v1.4/tasks/operations/airgap-deployment.md
@@ -0,0 +1,160 @@
+---
+title: 'Deploy Envoy Gateway in Air-Gapped Environments'
+---
+
+Deploying the Envoy Gateway in an air-gapped environment using a Helm chart
+requires careful configuration of the `values.yaml` file as well as adjustments
+when deploying a Gateway resource.
+
+You will need to specify custom image repositories for the following components
+in the Helm chart. This can be done on a global level or image level.
+
+- Gateway
+- Ratelimit
+
+## Gateway – `values.yaml` Configuration
+
+Example done in image level:
+
+```yaml
+deployment:
+  envoyGateway:
+    image:
+      repository: custom-cr.internal.io/envoyproxy/gateway
+      tag: v1.4.1
+```
+
+It's also possible to define the registry on a global level:
+
+```yaml
+# Global settings
+global:
+  # If set, these take highest precedence and change both envoyGateway and ratelimit's container registry and pull secrets.
+  # -- Global override for image registry
+  imageRegistry: 'custom-cr.internal.io'
+```
+
+## Ratelimit - `values.yaml` Configuration
+
+Example done on global level:
+
+```yaml
+global:
+  images:
+    ratelimit:
+      image: custom-cr.internal.io/envoyproxy/ratelimit:master
+```
+
+Furthermore for private registries you might need to define imagePullSecrets.
+
+On global level:
+
+```yaml
+global:
+  imagePullSecrets:
+    - my-private-registry-secret
+```
+
+or per image
+
+```yaml
+global:
+  images:
+    ratelimit:
+      pullSecrets:
+        - name: my-private-registry-secret
+```
+
+## Gateway Requires a Custom EnvoyProxy Reference
+
+Either the Gateway or GatewayClass must reference a custom EnvoyProxy resource
+that explicitly specifies the location of the distroless Envoy container image.
+Without this, the image will be pulled implicitly from Docker Hub.
+
+```yaml
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: custom-envoy-proxy
+  namespace: default
+spec:
+  provider:
+    type: Kubernetes
+    kubernetes:
+      envoyDeployment:
+        container:
+          image: custom-cr.internal.io/envoyproxy/envoy:distroless-v1.34.1
+```
+
+Then, reference the `custom-envoy-proxy` in your Gateway manifest
+
+```yaml
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: envoy-gateway
+  namespace: default
+  annotations:
+    cert-manager.io/cluster-issuer: cluster-wide-ca-issuer
+    cert-manager.io/duration: 8760h
+    cert-manager.io/renew-before: 360h
+    cert-manager.io/usages: server auth, client auth
+spec:
+  gatewayClassName: envoy-gateway-class
+  infrastructure:
+    parametersRef:
+      group: gateway.envoyproxy.io
+      kind: EnvoyProxy
+      name: custom-envoy-proxy
+  listeners:
+    - hostname: example.com
+      name: https
+      port: 443
+      protocol: HTTPS
+      tls:
+        certificateRefs:
+          - name: example-tls
+        mode: Terminate
+```
+
+or reference it in your GatewayClass, so that each new Gateway uses the
+EnvoyProxy automatically:
+
+```yaml
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: envoy-gateway-class
+spec:
+  controllerName: gateway.envoyproxy.io/gatewayclass-controller
+  parametersRef:
+    group: gateway.envoyproxy.io
+    kind: EnvoyProxy
+    name: custom-envoy-proxy
+    namespace: default
+```
+
+## Default LoadBalancer Service Type
+
+By default, Envoy uses a Service of type `LoadBalancer`. Depending on your
+Kubernetes environment, you might need to add custom annotations. For example,
+when deploying in Azure, you can configure the service as follows:
+
+```yaml
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: custom-envoy-proxy
+  namespace: default
+spec:
+  provider:
+    type: Kubernetes
+    kubernetes:
+      envoyService:
+        type: LoadBalancer
+        annotations:
+          service.beta.kubernetes.io/azure-load-balancer-internal: 'true'
+      envoyDeployment:
+        container:
+          image: custom-cr.internal.io/envoyproxy/envoy:distroless-v1.34.1
+```
diff --git a/site/data/ecosystem.yaml b/site/data/ecosystem.yaml
index 0a7e04b09..018273e58 100644
--- a/site/data/ecosystem.yaml
+++ b/site/data/ecosystem.yaml
@@ -23,3 +23,7 @@ adopters:
     url: "https://k0rdent.io/"
     logo: /logos/k0rdent.svg
     description: "k0rdent integrates with Envoy Gateway to offer advanced, high-performance traffic management within Kubernetes. k0rdent simplifies Envoy configuration, enabling users to easily define routing, security, and observability policies. This integration provides a robust, scalable, and secure ingress solution for applications deployed within the k0rdent-managed cluster."
+  - name: "KEDA"
+    logo: "/logos/keda.svg"
+    url: "https://keda.sh"
+    description: "KEDA integrates with Envoy Gateway through http-add-on for autoscaling workloads based on real-time HTTP traffic metrics. This integration allows users to leverage Envoy Gateway's capabilities for scaling behavior shaping desired traffic patterns."
diff --git a/site/static/logos/keda.svg b/site/static/logos/keda.svg
new file mode 100644
index 000000000..2dba5f8fd
--- /dev/null
+++ b/site/static/logos/keda.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Warstwa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 906.2 906.2" style="enable-background:new 0 0 906.2 906.2;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#326DE6;}
+	.st1{fill:#FFFFFF;}
+</style>
+<title>keda-word</title>
+<g id="Warstwa_1-2">
+	<g id="Wordmark">
+		<path id="KEDA" class="st0" d="M207.5,492.8l-47.2-84.5L146,422.8v70h-37V308h37v59.7l54.1-59.7H250l-64.7,72.3l66.3,112.5H207.5z
+			 M412.9,308v37H289.7v-37H412.9z M289.7,416.2v-37h102.1v37H289.7z M289.7,493.1v-37h122.5v37H289.7z M450.9,308h52.3
+			c24.5-0.2,48,9.6,65.2,26.9c8.5,8.4,15.3,18.5,19.8,29.6c9.9,23,9.9,49.1,0,72.1c-4.5,11.1-11.2,21-19.8,29.3
+			c-17.3,17.3-40.7,27.1-65.2,27.2H466v-0.3h-15.1V308z M487.9,456.1h15.3c30.7-0.4,55.2-25.6,54.8-56.3
+			c-0.4-30.1-24.7-54.4-54.8-54.8h-15.3V456.1z M698.9,308h10.8l87.4,185.1h-41l-52.8-112.4l-57.3,112.4h-41.5L698.9,308z"/>
+
+			<image style="overflow:visible;opacity:0.95;enable-background:new    ;" width="126" height="231" xlink:href="A04350A68EF731DF.png"  transform="matrix(0.9921 0 0 0.9921 625.9449 369.0308)">
+		</image>
+		<polygon class="st1" points="723.6,485.3 733.6,467.4 694.8,467.4 706.4,387.1 705.8,386 644.6,497 644.6,497 642.7,500.3
+			682.1,499.9 671.6,578.9 671.9,580 719.3,493 719.3,493 719.3,493 		"/>
+		<path id="Combined-Shape" class="st0" d="M719.3,493.1l14.1-25.6h-38.7l12.1-83.3l-60.1,108.9h-13.3l69.8-131l69.8,131H719.3z"/>
+	</g>
+</g>
+</svg>



Test patch:
diff --git a/internal/admin/console/api_test.go b/internal/admin/console/api_test.go
new file mode 100644
index 000000000..e677184b0
--- /dev/null
+++ b/internal/admin/console/api_test.go
@@ -0,0 +1,416 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/telepresenceio/watchable"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
+	"github.com/envoyproxy/gateway/internal/logging"
+	"github.com/envoyproxy/gateway/internal/message"
+)
+
+func TestHandleAPIInfo(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/api/info", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleAPIInfo(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
+
+	var info SystemInfo
+	err := json.Unmarshal(w.Body.Bytes(), &info)
+	require.NoError(t, err)
+
+	// Version might be empty in test environment
+	assert.NotEmpty(t, info.GoVersion)
+	assert.NotEmpty(t, info.Platform)
+	assert.NotEmpty(t, info.Uptime)
+	assert.False(t, info.StartTime.IsZero())
+	assert.False(t, info.Timestamp.IsZero())
+}
+
+func TestHandleAPIInfoMethodNotAllowed(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodPost, "/api/info", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleAPIInfo(w, req)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
+}
+
+func TestHandleAPIServerInfo(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/api/server_info", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleAPIServerInfo(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
+
+	var info ServerInfo
+	err := json.Unmarshal(w.Body.Bytes(), &info)
+	require.NoError(t, err)
+
+	assert.Equal(t, "Running", info.State)
+	// Version might be empty in test environment
+	assert.NotEmpty(t, info.Uptime)
+	assert.Len(t, info.Components, 4) // Core components only
+	assert.False(t, info.LastUpdated.IsZero())
+	assert.NotNil(t, info.EnvoyGatewayConfig) // Verify EnvoyGateway config is included
+
+	// Check component details
+	for _, component := range info.Components {
+		assert.NotEmpty(t, component.Name)
+		assert.Equal(t, "Running", component.Status)
+		assert.NotEmpty(t, component.Message)
+	}
+
+	// Verify EnvoyGateway configuration structure
+	configData, ok := info.EnvoyGatewayConfig.(map[string]interface{})
+	if ok {
+		// Check if it has the expected structure (this will be a map when JSON unmarshaled)
+		assert.Contains(t, configData, "kind")
+		assert.Contains(t, configData, "apiVersion")
+	}
+}
+
+func TestHandleAPIConfigDump(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	// Create a mock provider resources
+	providerResources := &message.ProviderResources{}
+
+	handler := NewHandler(cfg, providerResources)
+
+	req := httptest.NewRequest(http.MethodGet, "/api/config_dump", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleAPIConfigDump(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
+
+	var configDump ConfigDumpInfo
+	err := json.Unmarshal(w.Body.Bytes(), &configDump)
+	require.NoError(t, err)
+
+	assert.NotNil(t, configDump.Gateways)
+	assert.NotNil(t, configDump.HTTPRoutes)
+	assert.NotNil(t, configDump.GatewayClass)
+	assert.False(t, configDump.LastUpdated.IsZero())
+}
+
+func TestLoadConfigDumpWithData(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	// Create a simple test without the complex watchable structure
+	// This test focuses on the basic functionality
+	providerRes := &message.ProviderResources{}
+	// Initialize empty watchable map
+	providerRes.GatewayAPIResources = watchable.Map[string, *resource.ControllerResources]{}
+
+	// Skip storing to avoid watchable copy issues
+	// providerResources.Store("test", providerRes)
+
+	handler := NewHandler(cfg, providerRes)
+
+	configDump := handler.loadConfigDump()
+
+	// Verify the response structure with empty data
+	// Gateway API Core Resources
+	assert.Empty(t, configDump.GatewayClass)
+	assert.Empty(t, configDump.Gateways)
+	assert.Empty(t, configDump.HTTPRoutes)
+	assert.Empty(t, configDump.GRPCRoutes)
+	assert.Empty(t, configDump.TLSRoutes)
+	assert.Empty(t, configDump.TCPRoutes)
+	assert.Empty(t, configDump.UDPRoutes)
+
+	// Envoy Gateway Policies
+	assert.Empty(t, configDump.ClientTrafficPolicies)
+	assert.Empty(t, configDump.BackendTrafficPolicies)
+	assert.Empty(t, configDump.BackendTLSPolicies)
+	assert.Empty(t, configDump.SecurityPolicies)
+	assert.Empty(t, configDump.EnvoyPatchPolicies)
+	assert.Empty(t, configDump.EnvoyExtensionPolicies)
+
+	// Kubernetes Resources
+	assert.Empty(t, configDump.Services)
+	assert.Empty(t, configDump.Secrets)
+	assert.Empty(t, configDump.ConfigMaps)
+	assert.Empty(t, configDump.Namespaces)
+	assert.Empty(t, configDump.EndpointSlices)
+
+	// Other Resources
+	assert.Empty(t, configDump.ReferenceGrants)
+	assert.Empty(t, configDump.HTTPRouteFilters)
+	assert.Empty(t, configDump.EnvoyProxies)
+	assert.Empty(t, configDump.Backends)
+	assert.Empty(t, configDump.ServiceImports)
+
+	// Verify all fields are properly initialized (not nil)
+	assert.NotNil(t, configDump.GatewayClass)
+	assert.NotNil(t, configDump.Gateways)
+	assert.NotNil(t, configDump.HTTPRoutes)
+	assert.NotNil(t, configDump.GRPCRoutes)
+	assert.NotNil(t, configDump.TLSRoutes)
+	assert.NotNil(t, configDump.TCPRoutes)
+	assert.NotNil(t, configDump.UDPRoutes)
+	assert.NotNil(t, configDump.ClientTrafficPolicies)
+	assert.NotNil(t, configDump.BackendTrafficPolicies)
+	assert.NotNil(t, configDump.BackendTLSPolicies)
+	assert.NotNil(t, configDump.SecurityPolicies)
+	assert.NotNil(t, configDump.EnvoyPatchPolicies)
+	assert.NotNil(t, configDump.EnvoyExtensionPolicies)
+	assert.NotNil(t, configDump.Services)
+	assert.NotNil(t, configDump.Secrets)
+	assert.NotNil(t, configDump.ConfigMaps)
+	assert.NotNil(t, configDump.Namespaces)
+	assert.NotNil(t, configDump.EndpointSlices)
+	assert.NotNil(t, configDump.ReferenceGrants)
+	assert.NotNil(t, configDump.HTTPRouteFilters)
+	assert.NotNil(t, configDump.EnvoyProxies)
+	assert.NotNil(t, configDump.Backends)
+	assert.NotNil(t, configDump.ServiceImports)
+
+	assert.False(t, configDump.LastUpdated.IsZero())
+}
+
+func TestGetGatewayClassStatus(t *testing.T) {
+	tests := []struct {
+		name       string
+		conditions []metav1.Condition
+		expected   string
+	}{
+		{
+			name:       "no conditions",
+			conditions: nil,
+			expected:   "Unknown",
+		},
+		{
+			name: "accepted",
+			conditions: []metav1.Condition{
+				{
+					Type:   string(gwapiv1.GatewayClassConditionStatusAccepted),
+					Status: metav1.ConditionTrue,
+				},
+			},
+			expected: "Accepted",
+		},
+		{
+			name: "not accepted",
+			conditions: []metav1.Condition{
+				{
+					Type:   string(gwapiv1.GatewayClassConditionStatusAccepted),
+					Status: metav1.ConditionFalse,
+				},
+			},
+			expected: "Not Accepted",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gc := &gwapiv1.GatewayClass{
+				Status: gwapiv1.GatewayClassStatus{
+					Conditions: tt.conditions,
+				},
+			}
+
+			result := getGatewayClassStatus(gc)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestGetGatewayStatus(t *testing.T) {
+	tests := []struct {
+		name       string
+		conditions []metav1.Condition
+		expected   string
+	}{
+		{
+			name:       "no conditions",
+			conditions: nil,
+			expected:   "Unknown",
+		},
+		{
+			name: "programmed",
+			conditions: []metav1.Condition{
+				{
+					Type:   string(gwapiv1.GatewayConditionProgrammed),
+					Status: metav1.ConditionTrue,
+				},
+			},
+			expected: "Programmed",
+		},
+		{
+			name: "accepted",
+			conditions: []metav1.Condition{
+				{
+					Type:   string(gwapiv1.GatewayConditionAccepted),
+					Status: metav1.ConditionTrue,
+				},
+			},
+			expected: "Accepted",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gw := &gwapiv1.Gateway{
+				Status: gwapiv1.GatewayStatus{
+					Conditions: tt.conditions,
+				},
+			}
+
+			result := getGatewayStatus(gw)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestHandleAPIConfigDumpAll(t *testing.T) {
+	cfg := &config.Server{
+		Logger: logging.NewLogger(os.Stdout, egv1a1.DefaultEnvoyGatewayLogging()),
+	}
+
+	providerRes := &message.ProviderResources{}
+	handler := NewHandler(cfg, providerRes)
+
+	req := httptest.NewRequest(http.MethodGet, "/api/config_dump_all", nil)
+	resp := httptest.NewRecorder()
+
+	handler.handleAPIConfigDumpAll(resp, req)
+
+	assert.Equal(t, http.StatusOK, resp.Code)
+	assert.Equal(t, "application/json; charset=utf-8", resp.Header().Get("Content-Type"))
+
+	// Should not have download headers
+	assert.Empty(t, resp.Header().Get("Content-Disposition"))
+
+	// Should return JSON response with structured format
+	var result map[string]interface{}
+	err := json.Unmarshal(resp.Body.Bytes(), &result)
+	require.NoError(t, err)
+
+	// Verify response structure
+	assert.Contains(t, result, "resources")
+	assert.Contains(t, result, "timestamp")
+	assert.Contains(t, result, "totalCount")
+
+	// Verify resources field exists (could be nil for empty case)
+	resources := result["resources"]
+	if resources != nil {
+		resourcesArray, ok := resources.([]interface{})
+		assert.True(t, ok)
+		assert.Empty(t, resourcesArray) // Empty for test case
+	} else {
+		// resources is nil, which is expected for empty provider resources
+		assert.Nil(t, resources)
+	}
+
+	// Verify totalCount matches resources length
+	totalCount, ok := result["totalCount"].(float64)
+	assert.True(t, ok)
+	assert.Equal(t, float64(0), totalCount)
+}
+
+func TestHandleAPIConfigDumpAllMethodNotAllowed(t *testing.T) {
+	cfg := &config.Server{
+		Logger: logging.NewLogger(os.Stdout, egv1a1.DefaultEnvoyGatewayLogging()),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodPost, "/api/config_dump_all", nil)
+	resp := httptest.NewRecorder()
+
+	handler.handleAPIConfigDumpAll(resp, req)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, resp.Code)
+}
+
+func TestHandleAPIMetrics(t *testing.T) {
+	cfg := &config.Server{
+		Logger: logging.NewLogger(os.Stdout, egv1a1.DefaultEnvoyGatewayLogging()),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/api/metrics", nil)
+	resp := httptest.NewRecorder()
+
+	handler.handleAPIMetrics(resp, req)
+
+	// Now we're using the Prometheus registry directly, so we should get a successful response
+	assert.Equal(t, http.StatusOK, resp.Code)
+	assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain; version=0.0.4; charset=utf-8")
+
+	// Verify we get actual Prometheus metrics
+	body := resp.Body.String()
+	assert.Contains(t, body, "# HELP")
+	assert.Contains(t, body, "# TYPE")
+	// Should contain Go runtime metrics
+	assert.Contains(t, body, "go_goroutines")
+	assert.Contains(t, body, "process_cpu_seconds_total")
+}
+
+func TestHandleAPIMetricsMethodNotAllowed(t *testing.T) {
+	cfg := &config.Server{
+		Logger: logging.NewLogger(os.Stdout, egv1a1.DefaultEnvoyGatewayLogging()),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodPost, "/api/metrics", nil)
+	resp := httptest.NewRecorder()
+
+	handler.handleAPIMetrics(resp, req)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, resp.Code)
+}
diff --git a/internal/admin/console/handlers_test.go b/internal/admin/console/handlers_test.go
new file mode 100644
index 000000000..9d1b8f1af
--- /dev/null
+++ b/internal/admin/console/handlers_test.go
@@ -0,0 +1,211 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/logging"
+	"github.com/envoyproxy/gateway/internal/message"
+)
+
+func TestNewHandler(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	providerResources := &message.ProviderResources{}
+
+	handler := NewHandler(cfg, providerResources)
+
+	assert.NotNil(t, handler)
+	assert.Equal(t, cfg, handler.cfg)
+	assert.Equal(t, providerResources, handler.providerResources)
+}
+
+func TestHandleIndex(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleIndex(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
+	assert.Contains(t, w.Body.String(), "Envoy Gateway Admin Console")
+}
+
+func TestHandleIndexMethodNotAllowed(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, nil)
+
+	req := httptest.NewRequest(http.MethodPost, "/", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleIndex(w, req)
+
+	assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
+}
+
+func TestHandlePprof(t *testing.T) {
+	tests := []struct {
+		name        string
+		enablePprof bool
+		expectBody  string
+	}{
+		{
+			name:        "pprof enabled",
+			enablePprof: true,
+			expectBody:  "Performance Profiling",
+		},
+		{
+			name:        "pprof disabled",
+			enablePprof: false,
+			expectBody:  "Performance Profiling",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cfg := &config.Server{
+				EnvoyGateway: &egv1a1.EnvoyGateway{
+					EnvoyGatewaySpec: egv1a1.EnvoyGatewaySpec{
+						Admin: &egv1a1.EnvoyGatewayAdmin{
+							EnablePprof: tt.enablePprof,
+						},
+					},
+				},
+				Logger: logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+			}
+
+			handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+			req := httptest.NewRequest(http.MethodGet, "/pprof", nil)
+			w := httptest.NewRecorder()
+
+			handler.handlePprof(w, req)
+
+			assert.Equal(t, http.StatusOK, w.Code)
+			assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
+			assert.Contains(t, w.Body.String(), tt.expectBody)
+		})
+	}
+}
+
+func TestHandleServerInfo(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/server_info", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleServerInfo(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
+	assert.Contains(t, w.Body.String(), "Server Information")
+}
+
+func TestHandleConfigDump(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/config_dump", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleConfigDump(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
+	assert.Contains(t, w.Body.String(), "Configuration Dump")
+}
+
+func TestHandleStats(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+
+	req := httptest.NewRequest(http.MethodGet, "/stats", nil)
+	w := httptest.NewRecorder()
+
+	handler.handleStats(w, req)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
+	assert.Contains(t, w.Body.String(), "Statistics")
+}
+
+func TestRegisterRoutes(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+	mux := http.NewServeMux()
+
+	// This should not panic
+	require.NotPanics(t, func() {
+		handler.RegisterRoutes(mux)
+	})
+
+	// Test that routes are registered by making requests
+	testCases := []struct {
+		path           string
+		expectedStatus int
+	}{
+		{"/", http.StatusOK},
+		{"/pprof", http.StatusOK},
+		{"/server_info", http.StatusOK},
+		{"/config_dump", http.StatusOK},
+		{"/stats", http.StatusOK},
+		{"/api/info", http.StatusOK},
+		{"/api/server_info", http.StatusOK},
+		{"/api/config_dump", http.StatusOK},
+		{"/api/config_dump_all", http.StatusOK},
+		{"/api/metrics", http.StatusOK}, // Now uses Prometheus registry directly
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.path, func(t *testing.T) {
+			req := httptest.NewRequest(http.MethodGet, tc.path, nil)
+			w := httptest.NewRecorder()
+
+			mux.ServeHTTP(w, req)
+
+			assert.Equal(t, tc.expectedStatus, w.Code)
+		})
+	}
+}
diff --git a/internal/admin/console/static_test.go b/internal/admin/console/static_test.go
new file mode 100644
index 000000000..958851f2f
--- /dev/null
+++ b/internal/admin/console/static_test.go
@@ -0,0 +1,64 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/envoygateway/config"
+	"github.com/envoyproxy/gateway/internal/logging"
+	"github.com/envoyproxy/gateway/internal/message"
+)
+
+func TestStaticFiles(t *testing.T) {
+	cfg := &config.Server{
+		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
+		Logger:       logging.DefaultLogger(nil, egv1a1.LogLevelInfo),
+	}
+
+	handler := NewHandler(cfg, (*message.ProviderResources)(nil))
+	mux := http.NewServeMux()
+	handler.RegisterRoutes(mux)
+
+	// Test CSS file
+	t.Run("CSS file", func(t *testing.T) {
+		req := httptest.NewRequest(http.MethodGet, "/static/css/admin.css", nil)
+		w := httptest.NewRecorder()
+
+		mux.ServeHTTP(w, req)
+
+		assert.Equal(t, http.StatusOK, w.Code)
+		assert.Contains(t, w.Header().Get("Content-Type"), "text/css")
+		assert.Contains(t, w.Body.String(), "Envoy Gateway Admin Console Styles")
+	})
+
+	// Test JS file
+	t.Run("JS file", func(t *testing.T) {
+		req := httptest.NewRequest(http.MethodGet, "/static/js/admin.js", nil)
+		w := httptest.NewRecorder()
+
+		mux.ServeHTTP(w, req)
+
+		assert.Equal(t, http.StatusOK, w.Code)
+		assert.Contains(t, w.Header().Get("Content-Type"), "javascript")
+		assert.Contains(t, w.Body.String(), "EnvoyGatewayAdmin")
+	})
+
+	// Test non-existent file
+	t.Run("Non-existent file", func(t *testing.T) {
+		req := httptest.NewRequest(http.MethodGet, "/static/nonexistent.txt", nil)
+		w := httptest.NewRecorder()
+
+		mux.ServeHTTP(w, req)
+
+		assert.Equal(t, http.StatusNotFound, w.Code)
+	})
+}
diff --git a/internal/admin/console/template_test.go b/internal/admin/console/template_test.go
new file mode 100644
index 000000000..46a2ee2ad
--- /dev/null
+++ b/internal/admin/console/template_test.go
@@ -0,0 +1,70 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package console
+
+import (
+	"bytes"
+	"fmt"
+	"html/template"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestTemplateLoading(t *testing.T) {
+	// Test each template using the same method as the handler
+	testCases := []struct {
+		templateName string
+		contentBlock string
+		expectedText string
+	}{
+		{"index.html", "index-content", "Welcome to the Envoy Gateway Admin Console"},
+		{"server_info.html", "server-info-content", "displays detailed information about the Envoy Gateway server"},
+		{"config_dump.html", "config-dump-content", "current configuration state"},
+		{"stats.html", "stats-content", "Envoy Gateway statistics"},
+		{"pprof.html", "pprof-content", "Performance profiling"},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.templateName, func(t *testing.T) {
+			// Create a new template instance for each page (same as handler)
+			tmpl := template.New(tc.templateName)
+
+			// Parse base template and specific page template
+			tmpl, err := tmpl.ParseFS(templateFiles, "templates/base.html", "templates/"+tc.templateName)
+			require.NoError(t, err)
+
+			// Create a wrapper template that calls the correct content block
+			wrapperTemplate := fmt.Sprintf(`{{define "content"}}{{template "%s" .}}{{end}}`, tc.contentBlock)
+			tmpl, err = tmpl.Parse(wrapperTemplate)
+			require.NoError(t, err)
+
+			var buf bytes.Buffer
+			data := struct {
+				Title          string
+				MetricsAddress string
+				EnablePprof    bool
+			}{
+				Title:          "Test",
+				MetricsAddress: "localhost:19001",
+				EnablePprof:    true,
+			}
+
+			// Execute the base template (same as handler)
+			err = tmpl.ExecuteTemplate(&buf, "base.html", data)
+			require.NoError(t, err)
+
+			content := buf.String()
+			assert.Contains(t, content, tc.expectedText, "Template %s should contain expected text", tc.templateName)
+
+			// Verify the template produces valid HTML structure
+			assert.Contains(t, content, "<html", "Template should contain HTML structure")
+			assert.Contains(t, content, "</html>", "Template should be properly closed")
+			assert.Contains(t, content, "Envoy Gateway", "Template should contain Envoy Gateway branding")
+		})
+	}
+}
diff --git a/internal/admin/server_test.go b/internal/admin/server_test.go
index 325af7e7c..7bf13e4cc 100644
--- a/internal/admin/server_test.go
+++ b/internal/admin/server_test.go
@@ -25,7 +25,9 @@ func TestInitAdminServer(t *testing.T) {
 	}
 
 	svrConfig.Logger = logging.NewLogger(os.Stdout, egv1a1.DefaultEnvoyGatewayLogging())
-	runner := New(svrConfig)
+	runner := New(&Config{
+		Server: *svrConfig,
+	})
 	err := runner.Start(context.Background())
 	require.NoError(t, err)
 
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml
index 70d124e47..ca3284339 100644
--- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml
@@ -370,6 +370,12 @@ xdsIR:
         urlRewrite:
           host:
             name: rewrite.com
+          path:
+            fullReplace: null
+            prefixMatchReplace: null
+            regexMatchReplace:
+              pattern: .*
+              substitution: foo
       - destination:
           metadata:
             kind: HTTPRoute
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
index 15a17b99c..d28bfb180 100644
--- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
@@ -333,6 +333,8 @@ xdsIR:
           name: ""
           prefix: /header-host-and-path
         urlRewrite:
+          host:
+            header: my-host
           path:
             fullReplace: null
             prefixMatchReplace: /rewrite
diff --git a/test/cel-validation/envoyextensionpolicy_test.go b/test/cel-validation/envoyextensionpolicy_test.go
index 977dc3684..7202634be 100644
--- a/test/cel-validation/envoyextensionpolicy_test.go
+++ b/test/cel-validation/envoyextensionpolicy_test.go
@@ -447,6 +447,112 @@ func TestEnvoyExtensionPolicyTarget(t *testing.T) {
 				"spec.extProc[0].processingMode.request.body: Unsupported value: \"not-a-body-mode\": supported values: \"Streamed\", \"Buffered\", \"BufferedPartial\"",
 			},
 		},
+		{
+			desc: "valid ExtProc with request attributes and failOpen true",
+			mutate: func(sp *egv1a1.EnvoyExtensionPolicy) {
+				sp.Spec = egv1a1.EnvoyExtensionPolicySpec{
+					ExtProc: []egv1a1.ExtProc{
+						{
+							BackendCluster: egv1a1.BackendCluster{
+								BackendRefs: []egv1a1.BackendRef{
+									{
+										BackendObjectReference: gwapiv1.BackendObjectReference{
+											Name: "grpc-proc-service",
+											Port: ptr.To(gwapiv1.PortNumber(80)),
+										},
+									},
+								},
+							},
+							ProcessingMode: &egv1a1.ExtProcProcessingMode{
+								Request: &egv1a1.ProcessingModeOptions{
+									Attributes: []string{"request.headers"},
+								},
+							},
+							FailOpen: ptr.To(true),
+						},
+					},
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: "gateway.networking.k8s.io",
+								Kind:  "Gateway",
+								Name:  "eg",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "valid ExtProc with response attributes and failOpen true",
+			mutate: func(sp *egv1a1.EnvoyExtensionPolicy) {
+				sp.Spec = egv1a1.EnvoyExtensionPolicySpec{
+					ExtProc: []egv1a1.ExtProc{
+						{
+							BackendCluster: egv1a1.BackendCluster{
+								BackendRefs: []egv1a1.BackendRef{
+									{
+										BackendObjectReference: gwapiv1.BackendObjectReference{
+											Name: "grpc-proc-service",
+											Port: ptr.To(gwapiv1.PortNumber(80)),
+										},
+									},
+								},
+							},
+							ProcessingMode: &egv1a1.ExtProcProcessingMode{
+								Response: &egv1a1.ProcessingModeOptions{
+									Attributes: []string{"response.headers"},
+								},
+							},
+							FailOpen: ptr.To(true),
+						},
+					},
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: "gateway.networking.k8s.io",
+								Kind:  "Gateway",
+								Name:  "eg",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "valid ExtProc with failOpen true",
+			mutate: func(sp *egv1a1.EnvoyExtensionPolicy) {
+				sp.Spec = egv1a1.EnvoyExtensionPolicySpec{
+					ExtProc: []egv1a1.ExtProc{
+						{
+							BackendCluster: egv1a1.BackendCluster{
+								BackendRefs: []egv1a1.BackendRef{
+									{
+										BackendObjectReference: gwapiv1.BackendObjectReference{
+											Name: "grpc-proc-service",
+											Port: ptr.To(gwapiv1.PortNumber(80)),
+										},
+									},
+								},
+							},
+							FailOpen: ptr.To(true),
+						},
+					},
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: "gateway.networking.k8s.io",
+								Kind:  "Gateway",
+								Name:  "eg",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
 		{
 			desc: "valid ExtProc without FullDuplexStreamed body and failOpen true",
 			mutate: func(sp *egv1a1.EnvoyExtensionPolicy) {
diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml
index bd8436a2a..5f3999a69 100644
--- a/test/helm/gateway-crds-helm/all.out.yaml
+++ b/test/helm/gateway-crds-helm/all.out.yaml
@@ -22489,10 +22489,11 @@ spec:
                       ''gateway.envoyproxy.io'')) : true'
                   - message: If FullDuplexStreamed body processing mode is used, FailOpen
                       must be false.
-                    rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body)
+                    rule: '!(has(self.failOpen) && self.failOpen == true && has(self.processingMode)
+                      && ((has(self.processingMode.request) && has(self.processingMode.request.body)
                       && self.processingMode.request.body == ''FullDuplexStreamed'')
-                      || (has(self.processingMode.response.body) && self.processingMode.response.body
-                      == ''FullDuplexStreamed'')))'
+                      || (has(self.processingMode.response) && has(self.processingMode.response.body)
+                      && self.processingMode.response.body == ''FullDuplexStreamed'')))'
                 maxItems: 16
                 type: array
               lua:
diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
index fb55ef8da..23fa70706 100644
--- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
+++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@@ -5177,10 +5177,11 @@ spec:
                       ''gateway.envoyproxy.io'')) : true'
                   - message: If FullDuplexStreamed body processing mode is used, FailOpen
                       must be false.
-                    rule: '!(has(self.failOpen) && self.failOpen == true && ((has(self.processingMode.request.body)
+                    rule: '!(has(self.failOpen) && self.failOpen == true && has(self.processingMode)
+                      && ((has(self.processingMode.request) && has(self.processingMode.request.body)
                       && self.processingMode.request.body == ''FullDuplexStreamed'')
-                      || (has(self.processingMode.response.body) && self.processingMode.response.body
-                      == ''FullDuplexStreamed'')))'
+                      || (has(self.processingMode.response) && has(self.processingMode.response.body)
+                      && self.processingMode.response.body == ''FullDuplexStreamed'')))'
                 maxItems: 16
                 type: array
               lua: