feat: rename FlowNet-Lab to GNS3-Copilot across codebase

Update all references from FlowNet-Lab to GNS3-Copilot in package names, documentation, and logging. This includes:
- Module and package __init__.py files
- License headers and file descriptions
- Log messages and internal comments
- Remove deprecated tools: GNS3CreateAreaDrawingTool and LinuxTelnetBatchTool

The renaming aligns with the project's new branding while maintaining all existing functionality.
This commit is contained in:
YueGuobin committed 2026-03-04 00:55:29 +08:00
1 parent 95b309c7af
commit d525e5cad0
24 files changed
+47 -3487

No files matched your search

@@ -1,5 +1,5 @@
"""
FlowNet-Lab Tools Package
GNS3-Copilot Tools Package
This package provides various tools for interacting with GNS3 network simulator, including:
- Device configuration command execution
@@ -7,8 +7,6 @@ This package provides various tools for interacting with GNS3 network simulator,
- Multiple device command execution using Nornir
- VPCS device configuration using telnetlib3
- Node and link management
- Drawing management
- Notes management
Main modules:
- config_tools_nornir: Multiple device configuration command execution tool using Nornir
@@ -18,14 +16,7 @@ Main modules:
- gns3_create_link: GNS3 link creation tool
- gns3_start_node: GNS3 node startup tool
- gns3_get_node_temp: GNS3 template retrieval tool
- gns3_get_drawings: GNS3 drawing retrieval tool
- gns3_create_drawing: GNS3 drawing creation tool
- gns3_update_drawing: GNS3 drawing update tool
- gns3_delete_drawing: GNS3 drawing deletion tool
- gns3_create_area_drawing: GNS3 area annotation creation tool (ellipse for 2 nodes)
- gns3_drawing_utils: Drawing utility functions for calculating SVG parameters
- linux_tools_nornir: Linux Telnet batch command execution tool using Nornir
- window_controller: Frontend window control and text input tools (async WebSocket-based)
- gns3_update_node_name: GNS3 node name update tool
Note: GNS3TopologyTool is now available from gns3_client package
@@ -35,15 +26,12 @@ Author: Guobin Yue
# Import main tool classes
from .config_tools_nornir import ExecuteMultipleDeviceConfigCommands
from .display_tools_nornir import ExecuteMultipleDeviceCommands
from .gns3_create_area_drawing import GNS3CreateAreaDrawingTool
from .gns3_create_link import GNS3LinkTool
from .gns3_create_node import GNS3CreateNodeTool
from .gns3_get_node_temp import GNS3TemplateTool
from .gns3_start_node import GNS3StartNodeTool, GNS3StartNodeQuickTool
from .gns3_update_node_name import GNS3UpdateNodeNameTool
from .linux_tools_nornir import LinuxTelnetBatchTool
from .vpcs_tools_telnetlib3 import VPCSMultiCommands
from .window_controller import WindowControllerTool, TextInputTool
# Dynamic version management
try:
@@ -68,11 +56,7 @@ __all__ = [
"GNS3StartNodeQuickTool",
"GNS3UpdateNodeNameTool",
"GNS3TemplateTool",
"GNS3CreateAreaDrawingTool",
"LinuxTelnetBatchTool",
"WindowControllerTool",
"TextInputTool",
]
# Package initialization message
# print(f"FlowNet-Lab Tools package loaded (version {__version__})")
# print(f"GNS3-Copilot Tools package loaded (version {__version__})")
@@ -1,403 +0,0 @@
"""
GNS3 area annotation drawing tool for creating visual area markers.
This tool creates visual annotations (ellipses) for network devices to represent
groupings such as OSPF areas, EIGRP AS numbers, or other protocol-defined regions.
Currently supports two-node ellipse annotations.
"""
import json
import logging
from typing import Any
from langchain.tools import BaseTool
from langchain_core.callbacks import CallbackManagerForToolRun
from gns3_copilot.gns3_client import (
GNS3GetNodesTool,
Project,
get_gns3_connector,
)
from gns3_copilot.utils.gns3_drawing_utils import (
calculate_two_node_ellipse,
calculate_two_node_rectangle,
)
# Configure logging
logger = logging.getLogger(__name__)
class GNS3CreateAreaDrawingTool(BaseTool):
"""
A LangChain tool to create visual area annotations for network devices.
Creates ellipse annotations that connect two network devices,
automatically calculating optimal position, size, and rotation based on node coordinates.
**Input:**
A JSON object containing the project_id, area_name, and node_names.
Example input:
{
"project_id": "uuid-of-project",
"area_name": "Area 0",
"node_names": ["R-1", "R-2"]
}
**Output:**
A dictionary containing the creation results:
{
"project_id": "uuid-of-project",
"area_name": "Area 0",
"node_count": 2,
"shape_type": "ellipse",
"created_drawings": [
{
"drawing_id": "uuid-of-drawing1",
"type": "ellipse",
"status": "success"
},
{
"drawing_id": "uuid-of-drawing2",
"type": "text",
"status": "success"
}
],
"total_drawings": 2,
"successful_drawings": 2,
"failed_drawings": 0
}
**Use Cases:**
- Protocol Domains: OSPF areas, BGP AS, IS-IS levels
- Logical Isolation: VRF, VLAN, MSTP
- High Availability: VRRP, HSRP, Stack, M-LAG
- External Boundaries: Internet, DMZ
- Management Networks: OOB, Management
**Semantic Color Coding:**
The tool automatically applies business-professional colors based on area_name keywords:
| Category | Colors | Keywords in area_name | Border Style |
|------------------------|---------------------|------------------------------|--------------|
| Primary Routing | Blue (#2196F3) | BGP, AS, Area 0, Backbone | Solid |
| Secondary Routing | Light Blue (#64B5F6)| Area, Level | Solid |
| Logical Isolation | Purple (#9C27B0) | VRF, VLAN, MSTP | Dashed |
| High Availability | Amber (#FFC107) | VRRP, HSRP, HA, Stack | Solid |
| External/Boundary | Red (#EF5350) | INET, OUT, External, DMZ | Solid |
| Management | Gray (#757575) | MGMT, OOB | Dashed |
**Implementation Details:**
- Retrieves node coordinates from the GNS3 project topology
- Calculates rotated ellipse parameters (center, radius, rotation angle)
- Generates professional SVG graphics with semantic colors
- Creates two drawings: ellipse shape and text label
- All coordinates are integers as required by GNS3 API
**Example Usage:**
User: "Configure OSPF area 0 on R-1 and R-2"
→ Call: create_gns3_area_drawing(project_id="xxx", area_name="Area 0", node_names=["R-1", "R-2"])
User: "Create VLAN 10 for SW-1 and SW-2"
→ Call: create_gns3_area_drawing(project_id="xxx", area_name="VLAN 10", node_names=["SW-1", "SW-2"])
User: "Configure VRRP group 1 on R-1 and R-2"
→ Call: create_gns3_area_drawing(project_id="xxx", area_name="VRRP Group 1", node_names=["R-1", "R-2"])
"""
name: str = "create_gns3_area_drawing"
description: str = """
Creates a visual annotation (ellipse or rectangle) to mark logical groupings between two network devices.
Use for protocol domains (OSPF areas, BGP AS, IS-IS levels), logical isolation (VRF, VLAN),
or high availability groups (VRRP, HSRP). Automatically calculates optimal position,
size, rotation, and applies semantic colors.
Parameters:
- project_id: GNS3 project UUID
- area_name: Logical group name (e.g., "Area 0", "VLAN 10", "VRRP Group 1")
- node_names: List of exactly 2 node names (e.g., ["R-1", "R-2"])
- shape_type: Shape type, either "ellipse" (default) or "rectangle"
"""
def _run(
self,
tool_input: str,
run_manager: CallbackManagerForToolRun | None = None,
**kwargs: Any,
) -> dict[str, Any]:
"""
Creates a visual area annotation for network devices.
Args:
tool_input: A JSON string containing project_id, area_name, and node_names.
run_manager: LangChain run manager (unused).
Returns:
dict: A dictionary with creation results or an error message.
"""
# Log received input
logger.info("Received input: %s", tool_input)
try:
# Parse input JSON
input_data = json.loads(tool_input)
project_id = input_data.get("project_id")
area_name = input_data.get("area_name")
node_names = input_data.get("node_names", [])
shape_type = input_data.get("shape_type", "ellipse")
# Validate input
if not project_id:
logger.error("Invalid input: Missing project_id.")
return {"error": "Missing project_id."}
if not area_name:
logger.error("Invalid input: Missing area_name.")
return {"error": "Missing area_name."}
if not isinstance(node_names, list) or len(node_names) == 0:
logger.error("Invalid input: node_names must be a non-empty array.")
return {"error": "node_names must be a non-empty array."}
# Validate: requires exactly 2 nodes
if len(node_names) != 2:
logger.error(
"Invalid input: Exactly 2 nodes are required, got %d.",
len(node_names),
)
return {
"error": f"Exactly 2 nodes are required, got {len(node_names)}. "
"Please provide exactly 2 node names."
}
# Initialize Gns3Connector using factory function
logger.info("Connecting to GNS3 server...")
gns3_server = get_gns3_connector()
if gns3_server is None:
logger.error("Failed to create GNS3 connector")
return {
"error": "Failed to connect to GNS3 server. Please check your configuration."
}
# Initialize Project object for drawing creation
logger.info("Initializing project for drawing creation...")
project = Project(project_id=project_id, connector=gns3_server)
project.get() # Load project details
# Get node information using GNS3GetNodesTool to retrieve complete node data
# including height, width, coordinates, etc.
logger.info(
"Retrieving node information for project %s...",
project_id,
)
get_nodes_tool = GNS3GetNodesTool()
nodes_result = get_nodes_tool._run(json.dumps({"project_id": project_id}))
# Check if node retrieval was successful
if "error" in nodes_result:
logger.error("Failed to retrieve nodes: %s", nodes_result["error"])
return {"error": f"Failed to retrieve nodes: {nodes_result['error']}"}
# Build a dictionary mapping node names to their complete information
nodes_dict = {node["name"]: node for node in nodes_result.get("nodes", [])}
# Validate all requested nodes exist
for node_name in node_names:
if node_name not in nodes_dict:
logger.error("Node %s not found in project", node_name)
return {
"error": f"Node '{node_name}' not found in project topology."
}
# Retrieve all requested nodes
nodes = [nodes_dict[node_name] for node_name in node_names]
# Log node information
node_info_list = [
f"{node_name} at ({node['x']}, {node['y']}, size: {node.get('width', 'N/A')}x{node.get('height', 'N/A')})"
for node_name, node in zip(node_names, nodes, strict=True)
]
logger.info("Found nodes: %s", ", ".join(node_info_list))
# Validate shape_type
if shape_type not in ["ellipse", "rectangle"]:
logger.error("Invalid shape_type: %s", shape_type)
return {
"error": f"Invalid shape_type '{shape_type}'. Must be 'ellipse' or 'rectangle'."
}
# Calculate shape parameters for 2 nodes
logger.info("Calculating %s parameters for 2 nodes...", shape_type)
# Use the appropriate calculation function based on shape_type
if shape_type == "ellipse":
shape_result = calculate_two_node_ellipse(nodes[0], nodes[1], area_name)
shape_key = "ellipse"
else: # rectangle
shape_result = calculate_two_node_rectangle(
nodes[0], nodes[1], area_name
)
shape_key = "rectangle"
metadata = shape_result["metadata"]
logger.info(
"Two-node %s: center=(%.2f, %.2f), distance=%.2f, angle=%.2f°",
shape_type,
metadata["center_x"],
metadata["center_y"],
metadata["distance"],
metadata["angle_deg"],
)
# Prepare drawings for creation
# Ensure all coordinates are integers as required by GNS3 API
drawings = [
{
"svg": shape_result[shape_key]["svg"],
"x": int(shape_result[shape_key]["x"]),
"y": int(shape_result[shape_key]["y"]),
"z": shape_result[shape_key]["z"],
"locked": False,
"rotation": int(shape_result[shape_key]["rotation"]),
},
{
"svg": shape_result["text"]["svg"],
"x": int(shape_result["text"]["x"]),
"y": int(shape_result["text"]["y"]),
"z": shape_result["text"]["z"],
"locked": False,
"rotation": int(shape_result["text"]["rotation"]),
},
]
# Create drawings using Project method
logger.info(
"Creating %d drawings in project %s...", len(drawings), project_id
)
results: list[dict[str, Any]] = []
for i, drawing_data in enumerate(drawings):
try:
drawing_type = shape_type if i == 0 else "text"
logger.info(
"Creating drawing %d/%d: %s at (%d, %d) with rotation %d°...",
i + 1,
len(drawings),
drawing_type,
drawing_data["x"],
drawing_data["y"],
drawing_data["rotation"],
)
result = project.create_drawing(
svg=drawing_data["svg"],
x=drawing_data["x"],
y=drawing_data["y"],
z=drawing_data["z"],
locked=drawing_data["locked"],
rotation=drawing_data["rotation"],
)
drawing_info = {
"drawing_id": result.get("drawing_id"),
"type": drawing_type,
"status": "success",
}
results.append(drawing_info)
except Exception as e:
error_info = {
"type": "ellipse" if i == 0 else "text",
"error": f"Drawing {i + 1} creation failed: {str(e)}",
"status": "failed",
}
results.append(error_info)
logger.error("Failed to create drawing %d: %s", i + 1, e)
# Calculate summary statistics
successful_drawings = len(
[r for r in results if r.get("status") == "success"]
)
failed_drawings = len([r for r in results if r.get("status") == "failed"])
# Prepare final result
final_result = {
"project_id": project_id,
"area_name": area_name,
"node_count": len(node_names),
"nodes": node_names,
"shape_type": shape_type,
"created_drawings": results,
"total_drawings": len(drawings),
"successful_drawings": successful_drawings,
"failed_drawings": failed_drawings,
}
# Log the final result
logger.info(
"Area annotation creation completed: %d successful, %d failed out of %d total drawings.",
successful_drawings,
failed_drawings,
len(drawings),
)
# Return JSON-formatted result
return final_result
except json.JSONDecodeError as e:
logger.error("Invalid JSON input: %s", e)
return {"error": f"Invalid JSON input: {e}"}
except Exception as e:
logger.error("Failed to process area annotation request: %s", e)
return {"error": f"Failed to process area annotation request: {str(e)}"}
if __name__ == "__main__":
# Test the tool locally
from pprint import pprint
# Test with ellipse (default)
test_input_ellipse = json.dumps(
{
"project_id": "d7fc094c-685e-4db1-ac11-5e33a1b2e066", # Replace with actual project UUID
"area_name": "Core Area",
"node_names": ["R-6", "R-4"], # Replace with actual node names
"shape_type": "ellipse",
}
)
# Test with rectangle
test_input_rectangle = json.dumps(
{
"project_id": "d7fc094c-685e-4db1-ac11-5e33a1b2e066", # Replace with actual project UUID
"area_name": "VLAN 10",
"node_names": ["SW-1", "SW-2"], # Replace with actual node names
"shape_type": "rectangle",
}
)
tool = GNS3CreateAreaDrawingTool()
result = tool._run(test_input_ellipse)
pprint(result)
"""
example output:
{
'area_name': 'Area 0',
'created_drawings': [
{'drawing_id': 'uuid-of-drawing1', 'status': 'success', 'type': 'ellipse'},
{'drawing_id': 'uuid-of-drawing2', 'status': 'success', 'type': 'text'}
],
'failed_drawings': 0,
'node_count': 2,
'nodes': ['R-1', 'R-2'],
'project_id': '2245149a-71c8-4387-9d1f-441a683ef7e7',
'shape_type': 'ellipse',
'successful_drawings': 2,
'total_drawings': 2
}
"""