Commit ·
84fb786
1
Parent(s): 18e9b8c
Add dashboard reverse proxy at /dashboard
Browse files- Dockerfile +3 -2
- README.md +15 -0
- main.go +62 -3
Dockerfile
CHANGED
|
@@ -58,8 +58,9 @@ RUN useradd -m -u 1000 user && \
|
|
| 58 |
chown -R user:user /app && \
|
| 59 |
chown -R user:user /var/run/supervisor /var/log/supervisor
|
| 60 |
|
| 61 |
-
# 7860
|
| 62 |
-
|
|
|
|
| 63 |
|
| 64 |
# Run supervisor as root to manage both services (required for multi-process supervision)
|
| 65 |
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf", "-n"]
|
|
|
|
| 58 |
chown -R user:user /app && \
|
| 59 |
chown -R user:user /var/run/supervisor /var/log/supervisor
|
| 60 |
|
| 61 |
+
# EXPOSE 7860 only - this is the main OpenEnv API endpoint (reverse proxy + /dashboard)
|
| 62 |
+
# Port 7861 (dashboard) runs internally only and is accessed via /dashboard proxy
|
| 63 |
+
EXPOSE 7860
|
| 64 |
|
| 65 |
# Run supervisor as root to manage both services (required for multi-process supervision)
|
| 66 |
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf", "-n"]
|
README.md
CHANGED
|
@@ -21,6 +21,21 @@ license: mit
|
|
| 21 |
|
| 22 |
---
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
## Overview
|
| 25 |
|
| 26 |
GridMind-RL is a reinforcement learning environment for training and evaluating intelligent control policies in industrial building energy management. The environment simulates realistic HVAC control, thermal storage management, batch job scheduling, and demand response scenarios under stochastic electricity pricing and grid stress events.
|
|
|
|
| 21 |
|
| 22 |
---
|
| 23 |
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 🚀 Live Demo
|
| 27 |
+
|
| 28 |
+
| | URL |
|
| 29 |
+
|--|-----|
|
| 30 |
+
| **Environment API** | https://lo-kyu-gridmind.hf.space |
|
| 31 |
+
| **Live Dashboard** | https://lo-kyu-gridmind.hf.space/dashboard |
|
| 32 |
+
|
| 33 |
+
**Quick test:**
|
| 34 |
+
```bash
|
| 35 |
+
curl https://lo-kyu-gridmind.hf.space/health
|
| 36 |
+
curl https://lo-kyu-gridmind.hf.space/tasks
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
## Overview
|
| 40 |
|
| 41 |
GridMind-RL is a reinforcement learning environment for training and evaluating intelligent control policies in industrial building energy management. The environment simulates realistic HVAC control, thermal storage management, batch job scheduling, and demand response scenarios under stochastic electricity pricing and grid stress events.
|
main.go
CHANGED
|
@@ -8,9 +8,13 @@ import (
|
|
| 8 |
"fmt"
|
| 9 |
"log"
|
| 10 |
"math"
|
|
|
|
| 11 |
"net/http"
|
|
|
|
|
|
|
| 12 |
"os"
|
| 13 |
"strconv"
|
|
|
|
| 14 |
"sync"
|
| 15 |
"sync/atomic"
|
| 16 |
"time"
|
|
@@ -32,8 +36,8 @@ type Metrics struct {
|
|
| 32 |
rewardMin float64
|
| 33 |
rewardMax float64
|
| 34 |
// Histograms
|
| 35 |
-
actionBuckets
|
| 36 |
-
errorCount
|
| 37 |
}
|
| 38 |
|
| 39 |
var metrics = &Metrics{
|
|
@@ -143,6 +147,9 @@ func (s *Server) routes() *http.ServeMux {
|
|
| 143 |
mux.HandleFunc("/grade", s.handleGrade)
|
| 144 |
mux.HandleFunc("/tasks", s.handleTasks)
|
| 145 |
mux.HandleFunc("/metrics", s.handleMetrics)
|
|
|
|
|
|
|
|
|
|
| 146 |
return mux
|
| 147 |
}
|
| 148 |
|
|
@@ -333,6 +340,58 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request) {
|
|
| 333 |
fmt.Fprint(w, metrics.prometheus())
|
| 334 |
}
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
// ──────────────────────────────────────────────
|
| 337 |
// Entry point
|
| 338 |
// ──────────────────────────────────────────────
|
|
@@ -354,7 +413,7 @@ func main() {
|
|
| 354 |
srv.envMgr.Reset(env.ResetRequest{Seed: &seed, TaskID: 1, NumBuildings: 1})
|
| 355 |
|
| 356 |
log.Printf("GridMind-RL environment server starting on :%s", port)
|
| 357 |
-
log.Printf("Endpoints: GET /health /ping /state /replay /grade /tasks /metrics | POST /reset /step")
|
| 358 |
|
| 359 |
mux := withCORS(withLogging(srv.routes()))
|
| 360 |
if err := http.ListenAndServe(":"+port, mux); err != nil {
|
|
|
|
| 8 |
"fmt"
|
| 9 |
"log"
|
| 10 |
"math"
|
| 11 |
+
"net"
|
| 12 |
"net/http"
|
| 13 |
+
"net/http/httputil"
|
| 14 |
+
"net/url"
|
| 15 |
"os"
|
| 16 |
"strconv"
|
| 17 |
+
"strings"
|
| 18 |
"sync"
|
| 19 |
"sync/atomic"
|
| 20 |
"time"
|
|
|
|
| 36 |
rewardMin float64
|
| 37 |
rewardMax float64
|
| 38 |
// Histograms
|
| 39 |
+
actionBuckets map[string]int64 // hvac bucket counts
|
| 40 |
+
errorCount int64
|
| 41 |
}
|
| 42 |
|
| 43 |
var metrics = &Metrics{
|
|
|
|
| 147 |
mux.HandleFunc("/grade", s.handleGrade)
|
| 148 |
mux.HandleFunc("/tasks", s.handleTasks)
|
| 149 |
mux.HandleFunc("/metrics", s.handleMetrics)
|
| 150 |
+
// Reverse proxy for dashboard (runs on port 7861 internally)
|
| 151 |
+
mux.HandleFunc("/dashboard", s.handleDashboardProxy)
|
| 152 |
+
mux.HandleFunc("/dashboard/", s.handleDashboardProxy)
|
| 153 |
return mux
|
| 154 |
}
|
| 155 |
|
|
|
|
| 340 |
fmt.Fprint(w, metrics.prometheus())
|
| 341 |
}
|
| 342 |
|
| 343 |
+
// ── /dashboard (Reverse Proxy) ────────────────────────────────────────────
|
| 344 |
+
|
| 345 |
+
func (s *Server) handleDashboardProxy(w http.ResponseWriter, r *http.Request) {
|
| 346 |
+
// Target URL for the dashboard service (running on localhost:7861)
|
| 347 |
+
target, err := url.Parse("http://localhost:7861")
|
| 348 |
+
if err != nil {
|
| 349 |
+
http.Error(w, "proxy configuration error", http.StatusInternalServerError)
|
| 350 |
+
return
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
// Create a custom director to modify the request
|
| 354 |
+
director := func(req *http.Request) {
|
| 355 |
+
// Strip /dashboard prefix
|
| 356 |
+
path := req.URL.Path
|
| 357 |
+
if strings.HasPrefix(path, "/dashboard") {
|
| 358 |
+
path = strings.TrimPrefix(path, "/dashboard")
|
| 359 |
+
if path == "" {
|
| 360 |
+
path = "/"
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
// Set up the new request
|
| 365 |
+
req.URL.Scheme = target.Scheme
|
| 366 |
+
req.URL.Host = target.Host
|
| 367 |
+
if target.Path != "" {
|
| 368 |
+
req.URL.Path = target.Path + path
|
| 369 |
+
} else {
|
| 370 |
+
req.URL.Path = path
|
| 371 |
+
}
|
| 372 |
+
req.RequestURI = ""
|
| 373 |
+
|
| 374 |
+
// Preserve original host header for dashboard API calls
|
| 375 |
+
if req.Header.Get("X-Forwarded-Host") == "" {
|
| 376 |
+
req.Header.Set("X-Forwarded-For", getClientIP(r))
|
| 377 |
+
req.Header.Set("X-Forwarded-Proto", "https")
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
// Use ReverseProxy with custom director
|
| 382 |
+
proxy := &httputil.ReverseProxy{Director: director}
|
| 383 |
+
proxy.ServeHTTP(w, r)
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
// Helper: extract client IP from request
|
| 387 |
+
func getClientIP(r *http.Request) string {
|
| 388 |
+
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
| 389 |
+
if err != nil {
|
| 390 |
+
return r.RemoteAddr
|
| 391 |
+
}
|
| 392 |
+
return ip
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
// ──────────────────────────────────────────────
|
| 396 |
// Entry point
|
| 397 |
// ──────────────────────────────────────────────
|
|
|
|
| 413 |
srv.envMgr.Reset(env.ResetRequest{Seed: &seed, TaskID: 1, NumBuildings: 1})
|
| 414 |
|
| 415 |
log.Printf("GridMind-RL environment server starting on :%s", port)
|
| 416 |
+
log.Printf("Endpoints: GET /health /ping /state /replay /grade /tasks /metrics /dashboard | POST /reset /step")
|
| 417 |
|
| 418 |
mux := withCORS(withLogging(srv.routes()))
|
| 419 |
if err := http.ListenAndServe(":"+port, mux); err != nil {
|