Spaces:
Sleeping
Sleeping
Graham Paasch commited on
Commit ·
5f0d60a
1
Parent(s): e8329fa
Add Build Network tab with MCP integration
Browse files- Added build_network_from_description MCP tool integration
- New Build Network tab with music festival example
- Users can describe networks in natural language
- Auto-configuration option for devices
- Real network building, not mock demos
- Updated network_ops.py with new function
- Full workflow: description → topology → configuration
- .gitignore +1 -0
- agent/network_ops.py +52 -16
- app.py +83 -0
.gitignore
CHANGED
|
@@ -2,3 +2,4 @@
|
|
| 2 |
venv/
|
| 3 |
__pycache__/
|
| 4 |
*.pyc
|
|
|
|
|
|
| 2 |
venv/
|
| 3 |
__pycache__/
|
| 4 |
*.pyc
|
| 5 |
+
*.log
|
agent/network_ops.py
CHANGED
|
@@ -135,32 +135,68 @@ def configure_device(host: str, commands: List[str], username: str = "admin") ->
|
|
| 135 |
|
| 136 |
def backup_device_config(host: str, device_name: str, username: str = "admin") -> Dict:
|
| 137 |
"""
|
| 138 |
-
Backup device configuration
|
| 139 |
|
| 140 |
Args:
|
| 141 |
-
host: Device IP
|
| 142 |
-
device_name:
|
| 143 |
username: SSH username
|
| 144 |
|
| 145 |
Returns:
|
| 146 |
-
Dict with
|
| 147 |
"""
|
| 148 |
client = get_mcp_client()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
try:
|
| 151 |
-
result = client.
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
| 158 |
except Exception as e:
|
| 159 |
-
return {
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
|
| 166 |
# Keep existing NCS simulator integration
|
|
|
|
| 135 |
|
| 136 |
def backup_device_config(host: str, device_name: str, username: str = "admin") -> Dict:
|
| 137 |
"""
|
| 138 |
+
Backup device configuration to timestamped file
|
| 139 |
|
| 140 |
Args:
|
| 141 |
+
host: Device IP or hostname
|
| 142 |
+
device_name: Name for the backup
|
| 143 |
username: SSH username
|
| 144 |
|
| 145 |
Returns:
|
| 146 |
+
Dict with success status and backup info
|
| 147 |
"""
|
| 148 |
client = get_mcp_client()
|
| 149 |
+
try:
|
| 150 |
+
result = client.call_tool(
|
| 151 |
+
"backup_device_config",
|
| 152 |
+
{
|
| 153 |
+
"host": host,
|
| 154 |
+
"device_name": device_name,
|
| 155 |
+
"username": username
|
| 156 |
+
}
|
| 157 |
+
)
|
| 158 |
+
return result
|
| 159 |
+
except Exception as e:
|
| 160 |
+
return {"success": False, "error": str(e)}
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def build_network_from_description(description: str, project_name: str = "overgrowth", auto_configure: bool = True) -> Dict:
|
| 164 |
+
"""
|
| 165 |
+
Build a complete network from natural language description
|
| 166 |
+
|
| 167 |
+
This calls the MCP tool that:
|
| 168 |
+
1. Creates GNS3 topology based on description
|
| 169 |
+
2. Auto-configures all devices (if auto_configure=True)
|
| 170 |
+
3. Returns complete network info
|
| 171 |
|
| 172 |
+
Args:
|
| 173 |
+
description: Natural language description of network needs
|
| 174 |
+
project_name: GNS3 project name (default: overgrowth)
|
| 175 |
+
auto_configure: Whether to auto-configure devices (default: True)
|
| 176 |
+
|
| 177 |
+
Returns:
|
| 178 |
+
Dict with success status, topology info, and configuration results
|
| 179 |
+
"""
|
| 180 |
+
client = get_mcp_client()
|
| 181 |
try:
|
| 182 |
+
result = client.call_tool(
|
| 183 |
+
"build_network_from_description",
|
| 184 |
+
{
|
| 185 |
+
"description": description,
|
| 186 |
+
"project_name": project_name,
|
| 187 |
+
"auto_configure": auto_configure
|
| 188 |
+
}
|
| 189 |
+
)
|
| 190 |
+
return result
|
| 191 |
except Exception as e:
|
| 192 |
+
return {"success": False, "error": str(e)}
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def simulate_change(steps: List[Dict]) -> Tuple[List[Dict], List[Dict]]:
|
| 196 |
+
"""
|
| 197 |
+
Wrapper for backward compatibility
|
| 198 |
+
"""
|
| 199 |
+
return simulate_network_change_with_ncs(steps)
|
| 200 |
|
| 201 |
|
| 202 |
# Keep existing NCS simulator integration
|
app.py
CHANGED
|
@@ -14,6 +14,7 @@ from agent.network_ops import (
|
|
| 14 |
get_device_configuration,
|
| 15 |
configure_device,
|
| 16 |
backup_device_config,
|
|
|
|
| 17 |
)
|
| 18 |
import json
|
| 19 |
|
|
@@ -253,6 +254,37 @@ def build_ui():
|
|
| 253 |
synapse_btn = gr.Button("Refresh Log")
|
| 254 |
synapse_md = gr.Markdown(label="Overgrowth Synapse Log")
|
| 255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
with gr.Tab("Lab Management"):
|
| 257 |
gr.Markdown("### 🌱 GNS3 Lab Control\nManage your actual lab infrastructure at lab.grahampaasch.com")
|
| 258 |
|
|
@@ -406,6 +438,52 @@ def build_ui():
|
|
| 406 |
return f"✅ Backup completed for **{dev_name}**\n\n{result.get('backup_info', '')}"
|
| 407 |
else:
|
| 408 |
return f"❌ Backup failed: {result.get('error', 'unknown error')}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
run_btn.click(
|
| 411 |
fn=on_run,
|
|
@@ -471,6 +549,11 @@ def build_ui():
|
|
| 471 |
inputs=[device_host, device_user, backup_name],
|
| 472 |
outputs=[backup_result]
|
| 473 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
|
| 475 |
return demo
|
| 476 |
|
|
|
|
| 14 |
get_device_configuration,
|
| 15 |
configure_device,
|
| 16 |
backup_device_config,
|
| 17 |
+
build_network_from_description,
|
| 18 |
)
|
| 19 |
import json
|
| 20 |
|
|
|
|
| 254 |
synapse_btn = gr.Button("Refresh Log")
|
| 255 |
synapse_md = gr.Markdown(label="Overgrowth Synapse Log")
|
| 256 |
|
| 257 |
+
with gr.Tab("Build Network"):
|
| 258 |
+
gr.Markdown("### 🌱 Build Network from Description\nDescribe your network needs in plain English - Overgrowth builds and configures it automatically.")
|
| 259 |
+
|
| 260 |
+
network_description = gr.Textbox(
|
| 261 |
+
label="Network Description",
|
| 262 |
+
placeholder=(
|
| 263 |
+
"Example: I'm running a big music festival with multiple stages. "
|
| 264 |
+
"I need networks for: main stage with audio/video systems, "
|
| 265 |
+
"food truck area with 8 POS terminals, VIP lounge with WiFi, "
|
| 266 |
+
"ticketing booth with 4 terminals, and backstage with artist WiFi. "
|
| 267 |
+
"All need secure management access and internet connectivity."
|
| 268 |
+
),
|
| 269 |
+
lines=8,
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
with gr.Row():
|
| 273 |
+
build_project_name = gr.Textbox(
|
| 274 |
+
label="Project Name",
|
| 275 |
+
value="overgrowth",
|
| 276 |
+
scale=2
|
| 277 |
+
)
|
| 278 |
+
auto_config_checkbox = gr.Checkbox(
|
| 279 |
+
label="Auto-Configure Devices",
|
| 280 |
+
value=True,
|
| 281 |
+
scale=1
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
build_btn = gr.Button("🚀 Build Network", variant="primary", size="lg")
|
| 285 |
+
build_result = gr.Markdown(label="Build Results")
|
| 286 |
+
build_topology = gr.Code(label="Topology Details", language="json")
|
| 287 |
+
|
| 288 |
with gr.Tab("Lab Management"):
|
| 289 |
gr.Markdown("### 🌱 GNS3 Lab Control\nManage your actual lab infrastructure at lab.grahampaasch.com")
|
| 290 |
|
|
|
|
| 438 |
return f"✅ Backup completed for **{dev_name}**\n\n{result.get('backup_info', '')}"
|
| 439 |
else:
|
| 440 |
return f"❌ Backup failed: {result.get('error', 'unknown error')}"
|
| 441 |
+
|
| 442 |
+
def build_network(description, project, auto_config):
|
| 443 |
+
if not description or not description.strip():
|
| 444 |
+
return "⚠️ Please provide a network description", ""
|
| 445 |
+
|
| 446 |
+
result_md = "### 🏗️ Building Network...\n\n"
|
| 447 |
+
result_md += f"**Description:** {description}\n\n"
|
| 448 |
+
result_md += f"**Project:** {project}\n\n"
|
| 449 |
+
result_md += "---\n\n"
|
| 450 |
+
|
| 451 |
+
result = build_network_from_description(description, project, auto_config)
|
| 452 |
+
|
| 453 |
+
if result.get('success'):
|
| 454 |
+
result_md += "## ✅ Network Built Successfully!\n\n"
|
| 455 |
+
|
| 456 |
+
# Parse the result
|
| 457 |
+
topology_info = result.get('topology', {})
|
| 458 |
+
config_info = result.get('configuration', {})
|
| 459 |
+
|
| 460 |
+
result_md += f"### 📊 Topology Created\n"
|
| 461 |
+
result_md += f"- **Nodes:** {topology_info.get('node_count', 'N/A')}\n"
|
| 462 |
+
result_md += f"- **Links:** {topology_info.get('link_count', 'N/A')}\n\n"
|
| 463 |
+
|
| 464 |
+
if auto_config and config_info:
|
| 465 |
+
result_md += f"### 🔧 Auto-Configuration\n"
|
| 466 |
+
result_md += f"- **Status:** {config_info.get('status', 'Completed')}\n"
|
| 467 |
+
result_md += f"- **Devices Configured:** {config_info.get('configured_count', 'N/A')}\n\n"
|
| 468 |
+
|
| 469 |
+
if config_info.get('details'):
|
| 470 |
+
result_md += "**Configuration Details:**\n"
|
| 471 |
+
result_md += f"```\n{config_info['details']}\n```\n\n"
|
| 472 |
+
|
| 473 |
+
result_md += "### 🎯 Next Steps\n"
|
| 474 |
+
result_md += "1. Go to **Lab Management** tab to view topology\n"
|
| 475 |
+
result_md += "2. Verify device configurations\n"
|
| 476 |
+
result_md += "3. Start testing your network\n"
|
| 477 |
+
|
| 478 |
+
# Return topology as formatted JSON
|
| 479 |
+
import json
|
| 480 |
+
topology_json = json.dumps(result, indent=2)
|
| 481 |
+
return result_md, topology_json
|
| 482 |
+
else:
|
| 483 |
+
error_msg = "## ❌ Build Failed\n\n"
|
| 484 |
+
error_msg += f"**Error:** {result.get('error', 'Unknown error')}\n\n"
|
| 485 |
+
error_msg += "Please check your description and try again."
|
| 486 |
+
return error_msg, ""
|
| 487 |
|
| 488 |
run_btn.click(
|
| 489 |
fn=on_run,
|
|
|
|
| 549 |
inputs=[device_host, device_user, backup_name],
|
| 550 |
outputs=[backup_result]
|
| 551 |
)
|
| 552 |
+
build_btn.click(
|
| 553 |
+
fn=build_network,
|
| 554 |
+
inputs=[network_description, build_project_name, auto_config_checkbox],
|
| 555 |
+
outputs=[build_result, build_topology]
|
| 556 |
+
)
|
| 557 |
|
| 558 |
return demo
|
| 559 |
|