TomatitoToho's picture
Add cloudflared tunnel support
8e57c53 verified
Raw
History Blame Contribute Delete
8.08 kB
"""
Embodied Navigation Benchmark β€” Monitoring Dashboard
Serves both a Gradio UI and REST API endpoints for agent discovery.
"""
import gradio as gr
import json
import os
import time
import threading
import requests as req_lib
import psutil
import subprocess
from fastapi import FastAPI
from fastapi.responses import JSONResponse, HTMLResponse
import uvicorn
# ── State ─────────────────────────────────────────────────────────────────────
_state = {
"engine_online": False,
"tunnel_url": "",
"uptime": 0,
"start_time": time.time(),
"engine_logs": [],
"observations": 0,
}
def get_tunnel_url():
# Method 1: Read tunnel URL from file (written by start.sh)
try:
if os.path.exists("/tmp/tunnel_url.txt"):
with open("/tmp/tunnel_url.txt") as f:
url = f.read().strip()
if url:
_state["tunnel_url"] = url
return url
except:
pass
# Method 2: Query ngrok API
try:
resp = req_lib.get("http://localhost:4040/api/tunnels", timeout=3)
data = resp.json()
for t in data.get("tunnels", []):
url = t.get("public_url", "")
if url.startswith("tcp://"):
_state["tunnel_url"] = url
return url
except:
pass
# Method 3: Check cloudflared log
try:
if os.path.exists("/tmp/cloudflared.log"):
with open("/tmp/cloudflared.log") as f:
for line in f:
if "trycloudflare.com" in line:
import re
match = re.search(r'https://[a-z0-9-]+\.trycloudflare\.com', line)
if match:
url = f"cloudflared:{match.group()}"
_state["tunnel_url"] = url
return url
except:
pass
return _state.get("tunnel_url", "")
def get_engine_status():
try:
log_path = "/app/simulation/logs/latest.log"
if os.path.exists(log_path):
with open(log_path, "r") as f:
lines = f.readlines()
_state["engine_logs"] = lines[-50:] if len(lines) > 50 else lines
for line in lines[-20:]:
if "Done (" in line:
_state["engine_online"] = True
except:
pass
try:
result = subprocess.run(["pgrep", "-f", "engine.jar"], capture_output=True, text=True)
if result.returncode == 0:
_state["engine_online"] = True
except:
pass
return _state["engine_online"]
def parse_address(url):
if not url:
return "N/A", "N/A"
if url.startswith("tcp://"):
addr = url.replace("tcp://", "")
parts = addr.rsplit(":", 1)
if len(parts) == 2:
return parts[0], parts[1]
return addr, "25565"
if url.startswith("cloudflared:"):
return url.replace("cloudflared:", ""), "cloudflared"
if url.startswith("direct:"):
return url.replace("direct:", ""), "direct"
return url, "unknown"
def background_checker():
while True:
get_engine_status()
get_tunnel_url()
_state["uptime"] = int(time.time() - _state["start_time"])
_state["observations"] += 1
time.sleep(10)
_thread = threading.Thread(target=background_checker, daemon=True)
_thread.start()
# ── FastAPI app with API routes FIRST ─────────────────────────────────────────
app = FastAPI()
@app.get("/health")
async def health():
get_engine_status()
return {"status": "ok", "engine": _state.get("engine_online", False)}
@app.get("/api/ngrok")
async def api_ngrok():
get_tunnel_url()
url = _state.get("tunnel_url", "")
return {"url": url, "tcp": url}
@app.get("/api/status")
async def api_status():
get_engine_status()
return {
"mc_server": "online" if _state.get("engine_online") else "offline",
"ngrok_url": _state.get("tunnel_url", ""),
"uptime": _state.get("uptime", 0),
}
# ── Gradio Interface ──────────────────────────────────────────────────────────
def refresh_dashboard():
get_engine_status()
get_tunnel_url()
status = "Running" if _state["engine_online"] else "Starting..."
uptime_h = _state["uptime"] // 3600
uptime_m = (_state["uptime"] % 3600) // 60
uptime_s = _state["uptime"] % 60
tunnel_url = _state.get("tunnel_url", "Not available")
try:
mem = psutil.virtual_memory()
cpu = psutil.cpu_percent(interval=0.5)
resources = f"CPU: {cpu:.0f}% | RAM: {mem.percent:.0f}% ({mem.used // 1024 // 1024}MB / {mem.total // 1024 // 1024}MB)"
except:
resources = "N/A"
return status, f"{uptime_h}h {uptime_m}m {uptime_s}s", tunnel_url, resources
def get_agent_connect_info():
get_tunnel_url()
url = _state.get("tunnel_url", "")
if not url:
return "Simulation tunnel not ready. The engine may still be starting up."
host, port = parse_address(url)
if url.startswith("tcp://"):
return f"## Agent Connection Endpoint\n\n**Host:** `{host}`\n**Port:** `{port}`\n**Protocol:** TCP (simulation v1.21.4)\n\nConnect an autonomous agent to this endpoint."
elif url.startswith("cloudflared:"):
return f"## Agent Connection Endpoint\n\n**Tunnel URL:** `{host}`\n**Protocol:** Cloudflare Tunnel (TCP proxy)\n\nUse cloudflared to proxy TCP connections through this tunnel.\nCommand: `cloudflared access tcp --hostname {host} --url localhost:25565`"
else:
return f"## Agent Connection Endpoint\n\n**Address:** `{host}`\n\nDirect connection may be available."
def get_logs():
get_engine_status()
logs = _state.get("engine_logs", [])
return "".join(logs[-30:])
demo = gr.Blocks(title="Embodied Navigation Benchmark", theme=gr.themes.Soft())
with demo:
gr.Markdown("# Embodied Navigation Benchmark\nReal-time evaluation of autonomous AI agents in 3D spatial navigation tasks.")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Engine Status")
status_out = gr.Textbox(label="Simulation Engine", value="Checking...", interactive=False)
uptime_out = gr.Textbox(label="Uptime", value="0h 0m 0s", interactive=False)
tunnel_out = gr.Textbox(label="Tunnel URL", value="Connecting...", interactive=False)
resource_out = gr.Textbox(label="System Resources", value="N/A", interactive=False)
refresh_btn = gr.Button("Refresh Status", variant="primary")
with gr.Column(scale=1):
connect_out = gr.Markdown(value="Loading connection info...")
connect_btn = gr.Button("Get Agent Endpoint")
with gr.Row():
with gr.Column():
gr.Markdown("### Engine Logs")
logs_out = gr.Textbox(label="Recent Logs", value="", lines=12, interactive=False, max_lines=12)
logs_btn = gr.Button("Refresh Logs")
refresh_btn.click(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out])
connect_btn.click(fn=get_agent_connect_info, outputs=[connect_out])
logs_btn.click(fn=get_logs, outputs=[logs_out])
demo.load(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out])
# Mount Gradio on /ui path so API routes on / get priority
app = gr.mount_gradio_app(app, demo, path="/ui")
# Redirect root to /ui for web browsers
@app.get("/", response_class=HTMLResponse)
async def root_redirect():
return '<html><head><meta http-equiv="refresh" content="0;url=/ui"></head><body><a href="/ui">Go to Dashboard</a></body></html>'
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)