@@ -49,6 +49,15 @@ func newReverseProxy(targetBase, stripPrefix string, forwardAuth bool) (*httputi
4949 if ! forwardAuth {
5050 r .Header .Del ("Authorization" )
5151 }
52+
53+ // Log the proxied request for debugging
54+ log .Printf ("Proxying: %s %s -> %s://%s%s" , r .Method , stripPrefix , targetURL .Scheme , targetURL .Host , p )
55+ }
56+
57+ // Add error handler for proxy failures
58+ proxy .ErrorHandler = func (w http.ResponseWriter , r * http.Request , err error ) {
59+ log .Printf ("Proxy error for %s: %v" , r .URL .Path , err )
60+ http .Error (w , fmt .Sprintf ("Bad Gateway: %v" , err ), http .StatusBadGateway )
5261 }
5362
5463 // Sanitize response headers for iframe embedding
@@ -87,8 +96,18 @@ func newReverseProxy(targetBase, stripPrefix string, forwardAuth bool) (*httputi
8796func staticFileServer (staticDir string ) http.Handler {
8897 fs := http .FileServer (http .Dir (staticDir ))
8998 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
90- // Serve index.html for root and for unknown routes (SPA)
99+ // Never serve index.html for API or embedded proxy routes
100+ // These should be handled by their respective handlers
91101 p := r .URL .Path
102+ if strings .HasPrefix (p , "/api/" ) || strings .HasPrefix (p , "/embedded/" ) ||
103+ strings .HasPrefix (p , "/metrics/" ) || strings .HasPrefix (p , "/public/" ) ||
104+ strings .HasPrefix (p , "/avatar/" ) {
105+ // These paths should have been handled by other handlers
106+ // If we reach here, it means the proxy failed or route not found
107+ http .Error (w , "Service not available" , http .StatusBadGateway )
108+ return
109+ }
110+
92111 full := path .Join (staticDir , path .Clean (p ))
93112
94113 // Check if file exists
@@ -143,33 +162,67 @@ func main() {
143162 w .Write ([]byte (`{"status":"healthy","service":"semantic-router-dashboard"}` ))
144163 })
145164
146- // Static frontend
147- mux . Handle ( "/" , staticFileServer ( * staticDir )) // Router API proxy (forward Authorization)
165+ // Router API proxy (forward Authorization) - MUST be registered before Grafana
166+ var routerAPIProxy * httputil. ReverseProxy
148167 if * routerAPI != "" {
149168 rp , err := newReverseProxy (* routerAPI , "/api/router" , true )
150169 if err != nil {
151170 log .Fatalf ("router API proxy error: %v" , err )
152171 }
153- mux . Handle ( "/api/router" , rp )
172+ routerAPIProxy = rp
154173 mux .Handle ("/api/router/" , rp )
174+ log .Printf ("Router API proxy configured: %s" , * routerAPI )
155175 }
156176
157- // Router metrics passthrough (no rewrite, simple redirect/proxy)
158- mux .HandleFunc ("/metrics/router" , func (w http.ResponseWriter , r * http.Request ) {
159- // Simple 302 redirect for now to let Prometheus UI open directly
160- http .Redirect (w , r , * routerMetrics , http .StatusTemporaryRedirect )
161- })
162-
163- // Grafana proxy
177+ // Grafana proxy and static assets
178+ var grafanaStaticProxy * httputil.ReverseProxy
164179 if * grafanaURL != "" {
165180 gp , err := newReverseProxy (* grafanaURL , "/embedded/grafana" , false )
166181 if err != nil {
167182 log .Fatalf ("grafana proxy error: %v" , err )
168183 }
169- mux .Handle ("/embedded/grafana" , gp )
170184 mux .Handle ("/embedded/grafana/" , gp )
185+
186+ // Proxy for Grafana static assets (no prefix stripping)
187+ grafanaStaticProxy , _ = newReverseProxy (* grafanaURL , "" , false )
188+ mux .Handle ("/public/" , grafanaStaticProxy )
189+ mux .Handle ("/avatar/" , grafanaStaticProxy )
190+
191+ log .Printf ("Grafana proxy configured: %s" , * grafanaURL )
192+ log .Printf ("Grafana static assets proxied: /public/, /avatar/" )
193+ } else {
194+ mux .HandleFunc ("/embedded/grafana/" , func (w http.ResponseWriter , r * http.Request ) {
195+ w .Header ().Set ("Content-Type" , "application/json" )
196+ w .WriteHeader (http .StatusServiceUnavailable )
197+ w .Write ([]byte (`{"error":"Grafana not configured","message":"TARGET_GRAFANA_URL environment variable is not set"}` ))
198+ })
199+ log .Printf ("Warning: Grafana URL not configured" )
171200 }
172201
202+ // Smart /api/ router: route to Router API or Grafana API based on path
203+ mux .HandleFunc ("/api/" , func (w http.ResponseWriter , r * http.Request ) {
204+ // If path starts with /api/router/, use Router API proxy
205+ if strings .HasPrefix (r .URL .Path , "/api/router/" ) && routerAPIProxy != nil {
206+ routerAPIProxy .ServeHTTP (w , r )
207+ return
208+ }
209+ // Otherwise, if Grafana is configured, proxy to Grafana API
210+ if grafanaStaticProxy != nil {
211+ grafanaStaticProxy .ServeHTTP (w , r )
212+ return
213+ }
214+ // No handler available
215+ http .Error (w , "Service not available" , http .StatusBadGateway )
216+ })
217+
218+ // Router metrics passthrough
219+ mux .HandleFunc ("/metrics/router" , func (w http.ResponseWriter , r * http.Request ) {
220+ http .Redirect (w , r , * routerMetrics , http .StatusTemporaryRedirect )
221+ })
222+
223+ // Static frontend - MUST be registered last
224+ mux .Handle ("/" , staticFileServer (* staticDir ))
225+
173226 // Prometheus proxy (optional)
174227 if * promURL != "" {
175228 pp , err := newReverseProxy (* promURL , "/embedded/prometheus" , false )
@@ -178,6 +231,14 @@ func main() {
178231 }
179232 mux .Handle ("/embedded/prometheus" , pp )
180233 mux .Handle ("/embedded/prometheus/" , pp )
234+ log .Printf ("Prometheus proxy configured: %s" , * promURL )
235+ } else {
236+ mux .HandleFunc ("/embedded/prometheus/" , func (w http.ResponseWriter , r * http.Request ) {
237+ w .Header ().Set ("Content-Type" , "application/json" )
238+ w .WriteHeader (http .StatusServiceUnavailable )
239+ w .Write ([]byte (`{"error":"Prometheus not configured","message":"TARGET_PROMETHEUS_URL environment variable is not set"}` ))
240+ })
241+ log .Printf ("Warning: Prometheus URL not configured" )
181242 }
182243
183244 // Open WebUI proxy (optional)
@@ -188,6 +249,14 @@ func main() {
188249 }
189250 mux .Handle ("/embedded/openwebui" , op )
190251 mux .Handle ("/embedded/openwebui/" , op )
252+ log .Printf ("Open WebUI proxy configured: %s" , * openwebuiURL )
253+ } else {
254+ mux .HandleFunc ("/embedded/openwebui/" , func (w http.ResponseWriter , r * http.Request ) {
255+ w .Header ().Set ("Content-Type" , "application/json" )
256+ w .WriteHeader (http .StatusServiceUnavailable )
257+ w .Write ([]byte (`{"error":"Open WebUI not configured","message":"TARGET_OPENWEBUI_URL environment variable is not set or empty"}` ))
258+ })
259+ log .Printf ("Info: Open WebUI not configured (optional)" )
191260 }
192261
193262 addr := ":" + * port
0 commit comments