Aaron Brown commited on
Commit
aef9d26
·
1 Parent(s): 2968b9c

Add Gradio range dashboard and enable web interface

Browse files

- Add custom Gradio tab with network topology, reward bars, flag status
- Mount at /web via gr.mount_gradio_app (compatible with installed openenv)
- Add gradio>=4.0.0 to dependencies
- Set ENABLE_WEB_INTERFACE=true in Dockerfile

Dockerfile CHANGED
@@ -96,6 +96,8 @@ ENV OPENRANGE_EXECUTION_MODE=subprocess
96
  ENV OPENRANGE_RUNTIME_MANIFEST=manifests/tier1_basic.yaml
97
  ENV OPENRANGE_RUNTIME_VALIDATOR_PROFILE=offline
98
  ENV OPENRANGE_SNAPSHOT_POOL_SIZE=1
 
 
99
 
100
  # ── 6. Health check ──────────────────────────────────────────────────────────
101
 
 
96
  ENV OPENRANGE_RUNTIME_MANIFEST=manifests/tier1_basic.yaml
97
  ENV OPENRANGE_RUNTIME_VALIDATOR_PROFILE=offline
98
  ENV OPENRANGE_SNAPSHOT_POOL_SIZE=1
99
+ # Enable the OpenEnv Gradio web interface at /web
100
+ ENV ENABLE_WEB_INTERFACE=true
101
 
102
  # ── 6. Health check ──────────────────────────────────────────────────────────
103
 
pyproject.toml CHANGED
@@ -17,6 +17,7 @@ dependencies = [
17
  "docker>=7.0",
18
  "jinja2>=3.1",
19
  "uvicorn>=0.24.0",
 
20
  ]
21
 
22
  [project.optional-dependencies]
 
17
  "docker>=7.0",
18
  "jinja2>=3.1",
19
  "uvicorn>=0.24.0",
20
+ "gradio>=4.0.0",
21
  ]
22
 
23
  [project.optional-dependencies]
src/open_range/server/app.py CHANGED
@@ -38,6 +38,23 @@ def create_app() -> FastAPI:
38
  env_name="open_range",
39
  )
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  fastapp.state.env = env_factory()
42
  if runtime is not None:
43
  fastapp.state.runtime = runtime
 
38
  env_name="open_range",
39
  )
40
 
41
+ # Mount custom Gradio dashboard at /web if gradio is available
42
+ try:
43
+ import gradio as gr
44
+ from open_range.server.gradio_ui import build_openrange_gradio_app
45
+
46
+ blocks = build_openrange_gradio_app(
47
+ web_manager=None,
48
+ action_fields=[],
49
+ metadata=None,
50
+ is_chat_env=False,
51
+ title="OpenRange",
52
+ quick_start_md="",
53
+ )
54
+ fastapp = gr.mount_gradio_app(fastapp, blocks, path="/web")
55
+ except Exception:
56
+ pass # Gradio is optional
57
+
58
  fastapp.state.env = env_factory()
59
  if runtime is not None:
60
  fastapp.state.runtime = runtime
src/open_range/server/gradio_ui.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Custom Gradio tab for OpenRange — cybersecurity range dashboard.
2
+
3
+ Renders a network topology visualization, live action feed, flag status,
4
+ and reward dashboard as a custom tab alongside the default OpenEnv Playground.
5
+
6
+ Signature matches the gradio_builder contract from OpenEnv.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import gradio as gr
14
+
15
+
16
+ def _range_dashboard_html() -> str:
17
+ """Static HTML for the OpenRange dashboard overview."""
18
+ return """
19
+ <div style="font-family: 'SF Mono', 'Fira Code', monospace; max-width: 900px; margin: 0 auto; padding: 16px;">
20
+ <style>
21
+ .range-card {
22
+ background: #0d1117;
23
+ border: 1px solid #30363d;
24
+ border-radius: 6px;
25
+ padding: 16px;
26
+ margin-bottom: 12px;
27
+ }
28
+ .range-card h3 {
29
+ color: #58a6ff;
30
+ margin: 0 0 12px 0;
31
+ font-size: 14px;
32
+ text-transform: uppercase;
33
+ letter-spacing: 1px;
34
+ }
35
+ .topo-grid {
36
+ display: grid;
37
+ grid-template-columns: repeat(4, 1fr);
38
+ gap: 8px;
39
+ }
40
+ .zone-label {
41
+ font-size: 10px;
42
+ text-transform: uppercase;
43
+ letter-spacing: 1px;
44
+ color: #8b949e;
45
+ margin-bottom: 4px;
46
+ text-align: center;
47
+ }
48
+ .host-node {
49
+ background: #161b22;
50
+ border: 1px solid #30363d;
51
+ border-radius: 4px;
52
+ padding: 8px;
53
+ text-align: center;
54
+ font-size: 11px;
55
+ color: #c9d1d9;
56
+ transition: border-color 0.2s;
57
+ }
58
+ .host-node:hover { border-color: #58a6ff; }
59
+ .host-node .icon { font-size: 18px; margin-bottom: 4px; }
60
+ .host-node .name { font-weight: bold; }
61
+ .host-node .ip { color: #8b949e; font-size: 10px; }
62
+ .zone-external .host-node { border-color: #f85149; }
63
+ .zone-dmz .host-node { border-color: #d29922; }
64
+ .zone-internal .host-node { border-color: #3fb950; }
65
+ .zone-mgmt .host-node { border-color: #58a6ff; }
66
+ .reward-bar {
67
+ display: flex;
68
+ align-items: center;
69
+ margin: 6px 0;
70
+ }
71
+ .reward-label {
72
+ width: 110px;
73
+ color: #8b949e;
74
+ font-size: 12px;
75
+ }
76
+ .reward-track {
77
+ flex: 1;
78
+ height: 8px;
79
+ background: #21262d;
80
+ border-radius: 4px;
81
+ overflow: hidden;
82
+ }
83
+ .reward-fill {
84
+ height: 100%;
85
+ border-radius: 4px;
86
+ transition: width 0.3s;
87
+ }
88
+ .reward-value {
89
+ width: 50px;
90
+ text-align: right;
91
+ color: #c9d1d9;
92
+ font-size: 12px;
93
+ }
94
+ .flag-row {
95
+ display: flex;
96
+ align-items: center;
97
+ padding: 6px 0;
98
+ border-bottom: 1px solid #21262d;
99
+ }
100
+ .flag-icon { font-size: 16px; margin-right: 8px; }
101
+ .flag-id { color: #c9d1d9; font-size: 12px; flex: 1; }
102
+ .flag-status {
103
+ font-size: 11px;
104
+ padding: 2px 8px;
105
+ border-radius: 10px;
106
+ }
107
+ .flag-pending { background: #21262d; color: #8b949e; }
108
+ .flag-captured { background: #238636; color: #fff; }
109
+ .action-log {
110
+ max-height: 200px;
111
+ overflow-y: auto;
112
+ font-size: 11px;
113
+ }
114
+ .action-entry {
115
+ padding: 4px 0;
116
+ border-bottom: 1px solid #21262d;
117
+ display: flex;
118
+ }
119
+ .action-step { color: #8b949e; width: 30px; }
120
+ .action-mode { width: 40px; font-weight: bold; }
121
+ .action-red { color: #f85149; }
122
+ .action-blue { color: #58a6ff; }
123
+ .action-cmd { color: #c9d1d9; flex: 1; font-family: monospace; }
124
+ </style>
125
+
126
+ <div class="range-card">
127
+ <h3>Network Topology</h3>
128
+ <div class="topo-grid">
129
+ <div class="zone-external">
130
+ <div class="zone-label">External</div>
131
+ <div class="host-node">
132
+ <div class="icon">&#x1F5A5;</div>
133
+ <div class="name">attacker</div>
134
+ <div class="ip">10.0.0.10</div>
135
+ </div>
136
+ <div class="host-node" style="margin-top:8px">
137
+ <div class="icon">&#x1F6E1;</div>
138
+ <div class="name">firewall</div>
139
+ <div class="ip">10.0.0.2</div>
140
+ </div>
141
+ </div>
142
+ <div class="zone-dmz">
143
+ <div class="zone-label">DMZ</div>
144
+ <div class="host-node">
145
+ <div class="icon">&#x1F310;</div>
146
+ <div class="name">web</div>
147
+ <div class="ip">10.0.1.10</div>
148
+ </div>
149
+ <div class="host-node" style="margin-top:8px">
150
+ <div class="icon">&#x2709;</div>
151
+ <div class="name">mail</div>
152
+ <div class="ip">10.0.1.11</div>
153
+ </div>
154
+ </div>
155
+ <div class="zone-internal">
156
+ <div class="zone-label">Internal</div>
157
+ <div class="host-node">
158
+ <div class="icon">&#x1F4BE;</div>
159
+ <div class="name">db</div>
160
+ <div class="ip">10.0.2.20</div>
161
+ </div>
162
+ <div class="host-node" style="margin-top:8px">
163
+ <div class="icon">&#x1F4C1;</div>
164
+ <div class="name">files</div>
165
+ <div class="ip">10.0.2.21</div>
166
+ </div>
167
+ </div>
168
+ <div class="zone-mgmt">
169
+ <div class="zone-label">Management</div>
170
+ <div class="host-node">
171
+ <div class="icon">&#x1F511;</div>
172
+ <div class="name">ldap</div>
173
+ <div class="ip">10.0.3.20</div>
174
+ </div>
175
+ <div class="host-node" style="margin-top:8px">
176
+ <div class="icon">&#x1F4CA;</div>
177
+ <div class="name">siem</div>
178
+ <div class="ip">10.0.3.21</div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
185
+ <div class="range-card">
186
+ <h3>Red Rewards</h3>
187
+ <div class="reward-bar">
188
+ <span class="reward-label">Flag Capture</span>
189
+ <div class="reward-track"><div class="reward-fill" style="width:0%;background:#f85149;"></div></div>
190
+ <span class="reward-value">0.0</span>
191
+ </div>
192
+ <div class="reward-bar">
193
+ <span class="reward-label">Efficiency</span>
194
+ <div class="reward-track"><div class="reward-fill" style="width:80%;background:#d29922;"></div></div>
195
+ <span class="reward-value">0.8</span>
196
+ </div>
197
+ <div class="reward-bar">
198
+ <span class="reward-label">Stealth</span>
199
+ <div class="reward-track"><div class="reward-fill" style="width:100%;background:#3fb950;"></div></div>
200
+ <span class="reward-value">1.0</span>
201
+ </div>
202
+ <div class="reward-bar">
203
+ <span class="reward-label">Anti-Halluc</span>
204
+ <div class="reward-track"><div class="reward-fill" style="width:100%;background:#3fb950;"></div></div>
205
+ <span class="reward-value">0.0</span>
206
+ </div>
207
+ </div>
208
+ <div class="range-card">
209
+ <h3>Blue Rewards</h3>
210
+ <div class="reward-bar">
211
+ <span class="reward-label">Detection</span>
212
+ <div class="reward-track"><div class="reward-fill" style="width:0%;background:#58a6ff;"></div></div>
213
+ <span class="reward-value">0.0</span>
214
+ </div>
215
+ <div class="reward-bar">
216
+ <span class="reward-label">Patch Valid</span>
217
+ <div class="reward-track"><div class="reward-fill" style="width:0%;background:#58a6ff;"></div></div>
218
+ <span class="reward-value">0.0</span>
219
+ </div>
220
+ <div class="reward-bar">
221
+ <span class="reward-label">Availability</span>
222
+ <div class="reward-track"><div class="reward-fill" style="width:100%;background:#3fb950;"></div></div>
223
+ <span class="reward-value">1.0</span>
224
+ </div>
225
+ <div class="reward-bar">
226
+ <span class="reward-label">FP Penalty</span>
227
+ <div class="reward-track"><div class="reward-fill" style="width:100%;background:#3fb950;"></div></div>
228
+ <span class="reward-value">0.0</span>
229
+ </div>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="range-card">
234
+ <h3>Flags</h3>
235
+ <div class="flag-row">
236
+ <span class="flag-icon">&#x1F6A9;</span>
237
+ <span class="flag-id">FLAG{...} &mdash; Web Application</span>
238
+ <span class="flag-status flag-pending">pending</span>
239
+ </div>
240
+ <div class="flag-row">
241
+ <span class="flag-icon">&#x1F6A9;</span>
242
+ <span class="flag-id">FLAG{...} &mdash; Database</span>
243
+ <span class="flag-status flag-pending">pending</span>
244
+ </div>
245
+ <p style="color:#8b949e;font-size:11px;margin-top:8px;">
246
+ Use the <strong>Playground</strong> tab to reset and interact. Flags update after <code>submit_flag</code>.
247
+ </p>
248
+ </div>
249
+ </div>
250
+ """
251
+
252
+
253
+ def build_openrange_gradio_app(
254
+ web_manager: Any,
255
+ action_fields: List[Dict[str, Any]],
256
+ metadata: Any,
257
+ is_chat_env: bool,
258
+ title: str,
259
+ quick_start_md: str,
260
+ ) -> gr.Blocks:
261
+ """Build the Custom tab for OpenRange: network topology + dashboard.
262
+
263
+ Signature matches the gradio_builder contract (see OpenEnv docs).
264
+ """
265
+ with gr.Blocks(title=f"{title} — Range Dashboard") as blocks:
266
+ gr.Markdown("# Range Dashboard")
267
+ gr.Markdown(
268
+ "This tab shows the **network topology**, reward signals, and flag status. "
269
+ "Use the **Playground** tab to Reset and Step with commands "
270
+ "(e.g. `nmap -sV 10.0.1.0/24`)."
271
+ )
272
+ gr.HTML(value=_range_dashboard_html())
273
+ return blocks