mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 22:11:14 +02:00
Merge branch '3.1' into mypy-agent-gns3_copilot-plus
This commit is contained in:
46 files changed
+185
-204
No files matched your search
@@ -57,7 +57,7 @@ except ImportError as e:
|
||||
AI_COPILOT_AVAILABLE = False
|
||||
|
||||
# Provide stub functions that raise helpful errors
|
||||
async def get_project_agent_manager():
|
||||
async def get_project_agent_manager() -> "ProjectAgentManager":
|
||||
"""
|
||||
Get the global ProjectAgentManager singleton instance.
|
||||
|
||||
@@ -68,7 +68,7 @@ except ImportError as e:
|
||||
"AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-features]"
|
||||
)
|
||||
|
||||
class ProjectAgentManager:
|
||||
class ProjectAgentManager: # type: ignore[no-redef]
|
||||
"""
|
||||
Stub class for ProjectAgentManager when AI dependencies are not installed.
|
||||
"""
|
||||
|
||||
@@ -140,7 +140,7 @@ def estimate_tool_tokens(tools: list[Any]) -> int:
|
||||
for tool in tools:
|
||||
try:
|
||||
# Build tool schema in OpenAI format
|
||||
tool_schema = {
|
||||
tool_schema: dict[str, Any] = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool.name,
|
||||
|
||||
@@ -110,7 +110,7 @@ def create_base_model(
|
||||
|
||||
try:
|
||||
# Prepare parameters for init_chat_model
|
||||
init_params = {
|
||||
init_params: dict[str, Any] = {
|
||||
"model": config_vars["model_name"],
|
||||
"model_provider": config_vars["model_provider"],
|
||||
"api_key": config_vars["api_key"],
|
||||
@@ -177,7 +177,7 @@ def create_title_model(
|
||||
|
||||
try:
|
||||
# Prepare parameters for init_chat_model
|
||||
init_params = {
|
||||
init_params: dict[str, Any] = {
|
||||
"model": config_vars["model_name"],
|
||||
"model_provider": config_vars["model_provider"],
|
||||
"api_key": config_vars["api_key"],
|
||||
|
||||
@@ -125,7 +125,7 @@ class ChatSessionsRepository:
|
||||
project_id: str,
|
||||
title: str = "New Conversation",
|
||||
copilot_mode: Optional[str] = None,
|
||||
) -> ChatSession:
|
||||
) -> Optional[ChatSession]:
|
||||
"""
|
||||
Create a new chat session.
|
||||
|
||||
@@ -158,6 +158,8 @@ class ChatSessionsRepository:
|
||||
session_id = cursor.lastrowid
|
||||
log.info("Created chat session: id=%s, thread_id=%s, copilot_mode=%s", session_id, thread_id, copilot_mode)
|
||||
|
||||
if session_id is None:
|
||||
return None
|
||||
return await self.get_session_by_id(session_id)
|
||||
|
||||
async def get_session_by_id(self, session_id: int) -> Optional[ChatSession]:
|
||||
@@ -214,7 +216,7 @@ class ChatSessionsRepository:
|
||||
List of ChatSession
|
||||
"""
|
||||
query = "SELECT * FROM chat_sessions"
|
||||
params = []
|
||||
params: List[Any] = []
|
||||
|
||||
conditions = []
|
||||
if user_id:
|
||||
@@ -268,7 +270,7 @@ class ChatSessionsRepository:
|
||||
Updated ChatSession or None
|
||||
"""
|
||||
updates = []
|
||||
params = []
|
||||
params: List[Any] = []
|
||||
|
||||
now = datetime.utcnow().isoformat()
|
||||
|
||||
|
||||
@@ -162,14 +162,14 @@ def build_gns3_ctx(jwt_token: str | None = None, url: str | None = None) -> dict
|
||||
}
|
||||
|
||||
|
||||
def _filter_node_response(node: dict, fields: list[str] = None) -> dict:
|
||||
def _filter_node_response(node: dict, fields: list[str] | None = None) -> dict:
|
||||
"""Filter node response to only include requested fields."""
|
||||
if not fields:
|
||||
fields = ["node_id", "name", "node_type", "status", "console"]
|
||||
return {k: node[k] for k in fields if k in node}
|
||||
|
||||
|
||||
def _filter_link_response(link: dict, fields: list[str] = None) -> dict:
|
||||
def _filter_link_response(link: dict, fields: list[str] | None = None) -> dict:
|
||||
"""Filter link response to only include requested fields."""
|
||||
if not fields:
|
||||
fields = LINK_DEFAULT_FIELDS
|
||||
@@ -278,7 +278,7 @@ def _batch_lifecycle(project_id, node_ids, action, conn, action_label):
|
||||
return list(pool.map(_act, node_ids))
|
||||
|
||||
|
||||
def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -296,7 +296,7 @@ def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict
|
||||
return {"message": f"Node {node_id} started", "node_id": node_id}
|
||||
|
||||
|
||||
def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -314,7 +314,7 @@ def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[
|
||||
return {"message": f"Node {node_id} stopped", "node_id": node_id}
|
||||
|
||||
|
||||
def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -332,7 +332,7 @@ def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> di
|
||||
return {"message": f"Node {node_id} suspended", "node_id": node_id}
|
||||
|
||||
|
||||
def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
@@ -401,7 +401,7 @@ def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
|
||||
return _filter_node_response(resp, fields)
|
||||
|
||||
|
||||
def delete_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def delete_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -706,7 +706,7 @@ def available_filters_handler(params: dict[str, Any], gns3_ctx: dict[str, Any])
|
||||
return conn.http_call("get", url).json()
|
||||
|
||||
|
||||
def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -762,7 +762,7 @@ def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
|
||||
return _filter_link_response(resp, fields)
|
||||
|
||||
|
||||
def delete_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def delete_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
@@ -809,7 +809,7 @@ def update_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic
|
||||
# ── Link capture / reset handlers ──────────────────────────────────────
|
||||
|
||||
|
||||
def reset_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
def reset_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]:
|
||||
project_id = params.get("project_id")
|
||||
if not project_id:
|
||||
return {"error": "project_id is required"}
|
||||
|
||||
@@ -347,7 +347,7 @@ def get_gns3_server_host() -> str:
|
||||
host = get_gns3_server_host()
|
||||
print(f"GNS3 server host: {host}")
|
||||
"""
|
||||
url = _detect_url_for_api()
|
||||
url = _detect_url_for_api() or DEFAULT_GNS3_URL
|
||||
|
||||
# Extract host from URL
|
||||
# URL format: protocol://host:port
|
||||
|
||||
@@ -49,7 +49,7 @@ def build_nodes_inventory(nodes: list[dict[str, Any]], server_host: str | None)
|
||||
"""
|
||||
inventory: dict[str, Any] = {}
|
||||
for n in nodes:
|
||||
inventory[n.get("name")] = {
|
||||
inventory[n.get("name") or ""] = {
|
||||
"server": server_host,
|
||||
"name": n.get("name"),
|
||||
"node_id": n.get("node_id"),
|
||||
@@ -68,7 +68,7 @@ def build_nodes_inventory(nodes: list[dict[str, Any]], server_host: str | None)
|
||||
return inventory
|
||||
|
||||
|
||||
def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]]) -> list[dict[str, str]]:
|
||||
def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Build a human/LLM-friendly link list resolving node and port names.
|
||||
|
||||
@@ -76,7 +76,7 @@ def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]]
|
||||
Links whose endpoints cannot be resolved are skipped, mirroring the
|
||||
original Project.links_summary behavior.
|
||||
"""
|
||||
summary: list[dict[str, str]] = []
|
||||
summary: list[dict[str, Any]] = []
|
||||
for link in links:
|
||||
if not link.get("nodes"):
|
||||
continue
|
||||
|
||||
@@ -248,7 +248,7 @@ class SkillsLoader:
|
||||
continue
|
||||
# Use device_type from YAML content as the key
|
||||
# Fallback to filename stem if device_type not present
|
||||
skill_key = skill_data.get("device_type") if isinstance(skill_data, dict) else None
|
||||
skill_key = skill_data.get("device_type")
|
||||
if not skill_key:
|
||||
skill_key = yaml_file.stem
|
||||
logger.warning(f"No device_type in {yaml_file}, using filename '{skill_key}' as key")
|
||||
|
||||
@@ -37,8 +37,10 @@ from typing import Optional, Dict, Any
|
||||
|
||||
try:
|
||||
import git
|
||||
|
||||
GIT_AVAILABLE = True
|
||||
except ImportError:
|
||||
git = None
|
||||
GIT_AVAILABLE = False
|
||||
|
||||
from gns3server.config import Config
|
||||
from .loader import SkillsLoader
|
||||
@@ -66,7 +68,7 @@ class SkillsManager:
|
||||
- Version tracking
|
||||
"""
|
||||
|
||||
def __init__(self, repo_url: str = None, branch: str = "main", auto_update: bool = False):
|
||||
def __init__(self, repo_url: Optional[str] = None, branch: str = "main", auto_update: bool = False):
|
||||
"""
|
||||
Initialize the skills manager.
|
||||
|
||||
@@ -90,7 +92,7 @@ class SkillsManager:
|
||||
self._repo: Optional["git.Repo"] = None
|
||||
self._prompt_count = 0
|
||||
|
||||
if git is None:
|
||||
if not GIT_AVAILABLE:
|
||||
logger.warning("GitPython is not installed. Skills management features will be limited.")
|
||||
|
||||
def initialize(self) -> bool:
|
||||
@@ -134,7 +136,7 @@ class SkillsManager:
|
||||
- Up to date → nothing
|
||||
- Network error → use existing files, log warning
|
||||
"""
|
||||
if git is None or self._repo is None:
|
||||
if not GIT_AVAILABLE or self._repo is None:
|
||||
return
|
||||
|
||||
# Check for uncommitted changes
|
||||
@@ -173,7 +175,7 @@ class SkillsManager:
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if git is None:
|
||||
if not GIT_AVAILABLE:
|
||||
logger.error("GitPython is not installed. Cannot clone repository.")
|
||||
return False
|
||||
|
||||
@@ -347,7 +349,7 @@ class SkillsManager:
|
||||
Returns:
|
||||
Commit hash as string, or empty string if not available
|
||||
"""
|
||||
if git is None or self._repo is None:
|
||||
if not GIT_AVAILABLE or self._repo is None:
|
||||
try:
|
||||
self._repo = git.Repo(self.local_path)
|
||||
except Exception:
|
||||
@@ -409,7 +411,7 @@ class SkillsManager:
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if git is None:
|
||||
if not GIT_AVAILABLE:
|
||||
logger.error("GitPython is not installed. Cannot rollback.")
|
||||
return False
|
||||
|
||||
@@ -436,7 +438,7 @@ class SkillsManager:
|
||||
Returns:
|
||||
List of commit information dictionaries
|
||||
"""
|
||||
if git is None:
|
||||
if not GIT_AVAILABLE:
|
||||
return []
|
||||
|
||||
try:
|
||||
|
||||
@@ -78,7 +78,7 @@ try:
|
||||
# so we use importlib to get the actual module
|
||||
import importlib
|
||||
|
||||
sd = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
sd: Any = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
|
||||
# Recalculate platforms lists to include custom device types
|
||||
sd.platforms = list(sd.CLASS_MAPPER.keys())
|
||||
@@ -196,7 +196,7 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
||||
|
||||
# Validate input
|
||||
device_configs_list, project_id = self._validate_tool_input(tool_input)
|
||||
if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
if len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
return device_configs_list
|
||||
|
||||
# Filter forbidden commands and store blocked commands info
|
||||
|
||||
@@ -78,7 +78,7 @@ try:
|
||||
# so we use importlib to get the actual module
|
||||
import importlib
|
||||
|
||||
sd = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
sd: Any = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
|
||||
# Recalculate platforms lists to include custom device types
|
||||
sd.platforms = list(sd.CLASS_MAPPER.keys())
|
||||
@@ -199,7 +199,7 @@ class ExecuteMultipleDeviceCommands(BaseTool):
|
||||
|
||||
# Validate input
|
||||
device_configs_list, project_id = self._validate_tool_input(tool_input)
|
||||
if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
if len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
return device_configs_list
|
||||
|
||||
# Filter forbidden commands and store blocked commands info
|
||||
|
||||
@@ -222,6 +222,8 @@ class GNS3LinkTool(BaseTool):
|
||||
},
|
||||
gns3_ctx,
|
||||
)
|
||||
if isinstance(link_resp, list):
|
||||
raise RuntimeError("Unexpected batch response when creating a single link")
|
||||
if "error" in link_resp:
|
||||
raise RuntimeError(link_resp["error"])
|
||||
|
||||
|
||||
@@ -230,6 +230,8 @@ class GNS3CreateNodeTool(BaseTool):
|
||||
},
|
||||
gns3_ctx,
|
||||
)
|
||||
if isinstance(created, list):
|
||||
raise RuntimeError("Unexpected batch response when creating a single node")
|
||||
if "error" in created:
|
||||
raise RuntimeError(created["error"])
|
||||
node_info = {
|
||||
|
||||
@@ -286,7 +286,7 @@ class GNS3PacketFilterTool(BaseTool):
|
||||
|
||||
# Check if output contains "Invalid" indicating syntax error
|
||||
if "Invalid" in result.stdout or "Invalid" in result.stderr:
|
||||
error_lines = []
|
||||
error_lines: list[str] = []
|
||||
if "Invalid" in result.stderr:
|
||||
error_lines.extend(line for line in result.stderr.split("\n") if "Invalid" in line)
|
||||
if "Invalid" in result.stdout:
|
||||
|
||||
@@ -136,7 +136,11 @@ class GNS3StartNodeTool(BaseTool):
|
||||
results = []
|
||||
known_ids = [nid for nid in node_ids if nid in nodes_by_id]
|
||||
start_results = start_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx)
|
||||
start_errors = {r["node_id"]: r.get("error") for r in start_results if r.get("status") == "error"}
|
||||
start_errors = (
|
||||
{r["node_id"]: r.get("error") for r in start_results if r.get("status") == "error"}
|
||||
if isinstance(start_results, list)
|
||||
else {}
|
||||
)
|
||||
|
||||
# Get immediate status (likely 'starting' or 'stopped') — one call
|
||||
listing = get_nodes_handler({"project_id": project_id}, gns3_ctx)
|
||||
|
||||
@@ -124,7 +124,11 @@ class GNS3StopNodeTool(BaseTool):
|
||||
results = []
|
||||
known_ids = [nid for nid in node_ids if nid in nodes_by_id]
|
||||
stop_results = stop_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx)
|
||||
stop_errors = {r["node_id"]: r.get("error") for r in stop_results if r.get("status") == "error"}
|
||||
stop_errors = (
|
||||
{r["node_id"]: r.get("error") for r in stop_results if r.get("status") == "error"}
|
||||
if isinstance(stop_results, list)
|
||||
else {}
|
||||
)
|
||||
|
||||
# Get updated status — one call
|
||||
listing = get_nodes_handler({"project_id": project_id}, gns3_ctx)
|
||||
|
||||
@@ -130,7 +130,11 @@ class GNS3SuspendNodeTool(BaseTool):
|
||||
results = []
|
||||
known_ids = [nid for nid in node_ids if nid in nodes_by_id]
|
||||
suspend_results = suspend_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx)
|
||||
suspend_errors = {r["node_id"]: r.get("error") for r in suspend_results if r.get("status") == "error"}
|
||||
suspend_errors = (
|
||||
{r["node_id"]: r.get("error") for r in suspend_results if r.get("status") == "error"}
|
||||
if isinstance(suspend_results, list)
|
||||
else {}
|
||||
)
|
||||
|
||||
# Get updated status — one call
|
||||
listing = get_nodes_handler({"project_id": project_id}, gns3_ctx)
|
||||
|
||||
@@ -143,7 +143,8 @@ class PacketAnalysisTool(BaseTool):
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
tshark.stdout.close()
|
||||
if tshark.stdout is not None:
|
||||
tshark.stdout.close()
|
||||
stdout, _ = grep.communicate(timeout=30)
|
||||
tshark.wait(timeout=5)
|
||||
except Exception as e:
|
||||
|
||||
@@ -68,7 +68,7 @@ try:
|
||||
# CRITICAL: Update netmiko.ssh_dispatcher platforms lists
|
||||
import importlib
|
||||
|
||||
sd = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
sd: Any = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
|
||||
# Recalculate platforms lists to include custom device types
|
||||
sd.platforms = list(sd.CLASS_MAPPER.keys())
|
||||
@@ -179,7 +179,7 @@ class VPCSCommands(BaseTool):
|
||||
|
||||
# Validate input
|
||||
device_configs_list, project_id = self._validate_tool_input(tool_input)
|
||||
if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
if len(device_configs_list) > 0 and "error" in device_configs_list[0]:
|
||||
return device_configs_list
|
||||
|
||||
# Create a mapping of device names to their commands
|
||||
@@ -386,7 +386,7 @@ class VPCSCommands(BaseTool):
|
||||
def _prepare_device_hosts_data(
|
||||
self,
|
||||
device_configs_list: list[dict[str, Any]],
|
||||
project_id: str,
|
||||
project_id: str | None,
|
||||
jwt_token: str | None = None,
|
||||
url: str | None = None,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
@@ -415,7 +415,7 @@ class VPCSCommands(BaseTool):
|
||||
device_ports = get_device_ports_from_topology(device_names, project_id=project_id, jwt_token=jwt_token, url=url)
|
||||
|
||||
# Build Nornir inventory hosts data
|
||||
hosts_data = {}
|
||||
hosts_data: dict[str, Any] = {}
|
||||
for device_name in device_names:
|
||||
if device_name not in device_ports:
|
||||
logger.error("Device '%s' not found in topology", device_name)
|
||||
|
||||
@@ -37,6 +37,7 @@ from typing import Dict
|
||||
from langchain_core.messages import AIMessage
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.messages import SystemMessage
|
||||
from langchain_core.messages import ToolCall
|
||||
from langchain_core.messages import ToolMessage
|
||||
|
||||
|
||||
@@ -86,7 +87,7 @@ def convert_langchain_to_openai(lc_message) -> Dict[str, Any]:
|
||||
tool_calls = []
|
||||
for tc in lc_message.tool_calls:
|
||||
# Convert to dict if it's an object
|
||||
tc_dict = tc if isinstance(tc, dict) else tc.model_dump()
|
||||
tc_dict = tc
|
||||
tool_calls.append(
|
||||
{
|
||||
"id": tc_dict.get("id", str(uuid.uuid4())),
|
||||
@@ -139,7 +140,7 @@ def convert_openai_to_langchain(msg: Dict[str, Any]):
|
||||
|
||||
# Restore tool calls if present
|
||||
if "tool_calls" in msg and msg["tool_calls"]:
|
||||
tool_calls = []
|
||||
tool_calls: list[ToolCall] = []
|
||||
for tc in msg["tool_calls"]:
|
||||
tool_calls.append(
|
||||
{
|
||||
|
||||
@@ -214,9 +214,7 @@ def parse_tool_content(
|
||||
return content
|
||||
|
||||
# Handle unsupported types
|
||||
error_msg = ( # type: ignore[unreachable]
|
||||
f"Content must be str, dict, list, int, float, bool, or None, got {type(content).__name__}"
|
||||
)
|
||||
error_msg = f"Content must be str, dict, list, int, float, bool, or None, got {type(content).__name__}"
|
||||
logger.error(error_msg)
|
||||
|
||||
if strict_mode:
|
||||
|
||||
@@ -81,10 +81,7 @@ from gns3server.utils.uuid_validator import validate_uuid
|
||||
if __name__ == "__main__":
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
|
||||
try:
|
||||
from gns3server.agent.web_wireshark.manager import WebWiresharkManager
|
||||
except ImportError:
|
||||
from manager import WebWiresharkManager
|
||||
from gns3server.agent.web_wireshark.manager import WebWiresharkManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class WebWiresharkManager:
|
||||
logger.warning(f"Container {container_id[:12]} health check failed: {e}")
|
||||
return False
|
||||
|
||||
async def _exec_in_container(self, container_id: str, command: str, timeout: int = None) -> tuple:
|
||||
async def _exec_in_container(self, container_id: str, command: str, timeout: Optional[int] = None) -> tuple:
|
||||
"""Execute command in container with timeout.
|
||||
|
||||
Args:
|
||||
@@ -197,7 +197,7 @@ class WebWiresharkManager:
|
||||
stdout, _ = await proc.communicate()
|
||||
|
||||
# Build parent->children mapping
|
||||
children_map = {}
|
||||
children_map: dict[int, list[int]] = {}
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 3:
|
||||
@@ -326,7 +326,7 @@ class WebWiresharkManager:
|
||||
return
|
||||
|
||||
# Build parent->children mapping and collect process info
|
||||
children_map = {} # ppid -> [pid]
|
||||
children_map: dict[int, list[int]] = {} # ppid -> [pid]
|
||||
process_info = {} # pid -> (ppid, command)
|
||||
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
@@ -480,7 +480,7 @@ class WebWiresharkManager:
|
||||
logger.debug(f"Cannot get URL from Config: {e}")
|
||||
return None
|
||||
|
||||
async def _get_container_gateway_ip(self, container_id: str = None) -> Optional[str]:
|
||||
async def _get_container_gateway_ip(self, container_id: Optional[str] = None) -> Optional[str]:
|
||||
"""Get the Docker bridge gateway IP for container to access host.
|
||||
|
||||
Args:
|
||||
@@ -519,7 +519,7 @@ class WebWiresharkManager:
|
||||
|
||||
return None
|
||||
|
||||
async def get_container_ip(self, container_name: str, container_id: str = None) -> Optional[str]:
|
||||
async def get_container_ip(self, container_name: str, container_id: Optional[str] = None) -> Optional[str]:
|
||||
"""Get the container IP address in the wireshark network.
|
||||
|
||||
Args:
|
||||
@@ -626,7 +626,7 @@ class WebWiresharkManager:
|
||||
project_id: str,
|
||||
image: str = "gns3/web-wireshark:latest",
|
||||
memory: str = "2g",
|
||||
memory_swap: str = None,
|
||||
memory_swap: Optional[str] = None,
|
||||
cpus: float = 1.0,
|
||||
pids_limit: int = 1000,
|
||||
) -> str:
|
||||
@@ -935,10 +935,10 @@ class WebWiresharkManager:
|
||||
project_id: str,
|
||||
link_id: str,
|
||||
jwt_token: str,
|
||||
capture_stream_url: str = None,
|
||||
capture_stream_url: Optional[str] = None,
|
||||
image: str = "gns3/web-wireshark:latest",
|
||||
memory: str = "2g",
|
||||
memory_swap: str = None,
|
||||
memory_swap: Optional[str] = None,
|
||||
cpus: float = 1.0,
|
||||
pids_limit: int = 1000,
|
||||
):
|
||||
|
||||
@@ -7,7 +7,7 @@ about Web Wireshark containers and sessions.
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,7 +30,7 @@ async def collect_webwireshark_stats(projects: List) -> Dict:
|
||||
"""
|
||||
from .manager import WebWiresharkManager
|
||||
|
||||
stats = {"total_containers": 0, "running_containers": 0, "active_sessions": 0, "containers": []}
|
||||
stats: Dict[str, Any] = {"total_containers": 0, "running_containers": 0, "active_sessions": 0, "containers": []}
|
||||
|
||||
# Create a single manager instance and reuse it
|
||||
manager = WebWiresharkManager()
|
||||
|
||||
Reference in new issue
Block a user