Spaces:
Sleeping
Sleeping
| """OpenRange HF Space β Gradio frontend proxying to local Kind/Helm backend.""" | |
| import json | |
| import os | |
| import gradio as gr | |
| import httpx | |
| BACKEND_URL = os.environ.get("BACKEND_URL", "").rstrip("/") | |
| print(f"[openrange] BACKEND_URL = {BACKEND_URL[:50]}..." if BACKEND_URL else "[openrange] WARNING: BACKEND_URL not set") | |
| def _get(path: str, timeout: float = 60.0) -> str: | |
| if not BACKEND_URL: | |
| return '{"error": "BACKEND_URL not configured"}' | |
| try: | |
| r = httpx.get(f"{BACKEND_URL}/{path}", timeout=timeout) | |
| return r.text | |
| except Exception as e: | |
| return json.dumps({"error": f"{type(e).__name__}: {e}"}) | |
| def _post(path: str, body: dict | None = None, timeout: float = 120.0) -> str: | |
| if not BACKEND_URL: | |
| return '{"error": "BACKEND_URL not configured"}' | |
| try: | |
| r = httpx.post(f"{BACKEND_URL}/{path}", json=body or {}, timeout=timeout) | |
| return r.text | |
| except Exception as e: | |
| return json.dumps({"error": f"{type(e).__name__}: {e}"}) | |
| def _pretty(text: str) -> str: | |
| try: | |
| return json.dumps(json.loads(text), indent=2) | |
| except Exception: | |
| return text | |
| # ββ Callbacks ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def check_health(): | |
| return _pretty(_get("health")) | |
| def get_metadata(): | |
| return _pretty(_get("metadata")) | |
| def get_state(): | |
| return _pretty(_get("state")) | |
| def get_console_snapshot(): | |
| return _pretty(_get("console/api/snapshot")) | |
| def get_console_episode(): | |
| return _pretty(_get("console/api/episode")) | |
| def get_console_history(): | |
| return _pretty(_get("console/api/history")) | |
| def do_reset(): | |
| return _pretty(_post("reset")) | |
| def do_step(command: str, mode: str): | |
| if not command.strip(): | |
| return "Enter a command first" | |
| return _pretty(_post("step", {"command": command, "mode": mode})) | |
| # ββ UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks( | |
| title="OpenRange", | |
| theme=gr.themes.Default(primary_hue="blue", neutral_hue="slate"), | |
| css=""" | |
| .output textarea { font-family: 'SF Mono','Fira Code',Consolas,monospace !important; | |
| font-size: 13px !important; } | |
| """, | |
| ) as demo: | |
| gr.Markdown( | |
| "# OpenRange\n" | |
| "Multi-agent cybersecurity gymnasium β proxied to local Kind/Helm backend" | |
| ) | |
| with gr.Tab("Operator Console"): | |
| with gr.Row(): | |
| health_btn = gr.Button("Health", size="sm") | |
| state_btn = gr.Button("State", size="sm") | |
| meta_btn = gr.Button("Metadata", size="sm") | |
| reset_btn = gr.Button("Reset Environment", variant="primary", size="sm") | |
| info_out = gr.Textbox(label="Response", lines=8, interactive=False, elem_classes="output") | |
| health_btn.click(fn=check_health, outputs=info_out) | |
| state_btn.click(fn=get_state, outputs=info_out) | |
| meta_btn.click(fn=get_metadata, outputs=info_out) | |
| reset_btn.click(fn=do_reset, outputs=info_out) | |
| gr.Markdown("---") | |
| gr.Markdown("### Snapshot & Episode") | |
| with gr.Row(): | |
| snap_btn = gr.Button("Snapshot Info", size="sm") | |
| ep_btn = gr.Button("Episode Info", size="sm") | |
| hist_btn = gr.Button("Action History", size="sm") | |
| console_out = gr.Textbox(label="Console Data", lines=10, interactive=False, elem_classes="output") | |
| snap_btn.click(fn=get_console_snapshot, outputs=console_out) | |
| ep_btn.click(fn=get_console_episode, outputs=console_out) | |
| hist_btn.click(fn=get_console_history, outputs=console_out) | |
| with gr.Tab("Execute Step"): | |
| gr.Markdown("Send a command to the range as Red (attacker) or Blue (defender).") | |
| with gr.Row(): | |
| cmd_input = gr.Textbox( | |
| label="Command", | |
| placeholder="nmap -sV 10.0.1.0/24", | |
| scale=4, | |
| ) | |
| mode_input = gr.Dropdown( | |
| choices=["red", "blue"], | |
| value="red", | |
| label="Mode", | |
| scale=1, | |
| ) | |
| step_btn = gr.Button("Execute", variant="primary") | |
| step_out = gr.Textbox(label="Observation", lines=15, interactive=False, elem_classes="output") | |
| step_btn.click(fn=do_step, inputs=[cmd_input, mode_input], outputs=step_out) | |
| with gr.Tab("API Info"): | |
| gr.Markdown(f""" | |
| ### Backend URL | |
| `{BACKEND_URL or 'NOT SET β configure BACKEND_URL Space variable'}` | |
| ### OpenEnv Endpoints (proxied) | |
| | Method | Path | Description | | |
| |--------|------|-------------| | |
| | GET | `/health` | Backend health check | | |
| | GET | `/metadata` | Environment metadata | | |
| | GET | `/schema` | Action/Observation JSON schemas | | |
| | POST | `/reset` | Reset environment (select new snapshot) | | |
| | POST | `/step` | Execute an action | | |
| | GET | `/state` | Current episode state | | |
| | GET | `/console/` | Operator debug console | | |
| ### Architecture | |
| ``` | |
| You (browser) -> HF Space (this Gradio app) -> bore tunnel -> local machine -> Kind cluster | |
| ``` | |
| """) | |
| demo.launch() | |