mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
Merge branch '3.1' into api-issue-07-x7z6we
This commit is contained in:
30 files changed
+549
-72
No files matched your search
@@ -19,6 +19,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
# Check AI Copilot availability
|
||||
from gns3server.agent import AI_COPILOT_AVAILABLE
|
||||
from gns3server.api.operation_ids import add_stub_routes
|
||||
from gns3server.config import Config
|
||||
|
||||
# Conditionally import AI-dependent routes
|
||||
if AI_COPILOT_AVAILABLE:
|
||||
@@ -33,6 +34,11 @@ else:
|
||||
_copilot_router = APIRouter()
|
||||
_llm_router = APIRouter()
|
||||
|
||||
@_chat_router.api_route("/{path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT"], include_in_schema=False)
|
||||
@_copilot_router.api_route(
|
||||
"/{path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT"], include_in_schema=False
|
||||
)
|
||||
@_llm_router.api_route("/{path:path}", methods=["GET", "POST", "DELETE", "PATCH", "PUT"], include_in_schema=False)
|
||||
async def ai_not_available(path: str = ""):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
@@ -69,6 +75,8 @@ from . import (
|
||||
)
|
||||
from .dependencies.authentication import get_current_active_user
|
||||
|
||||
_include_ai_in_schema = Config.instance().settings.Server.openapi_include_ai
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(controller.router, tags=["Controller"])
|
||||
@@ -118,11 +126,19 @@ router.include_router(
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_llm_router, prefix="/access", dependencies=[Depends(get_current_active_user)], tags=["LLM Model Configurations"]
|
||||
_llm_router,
|
||||
prefix="/access",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["LLM Model Configurations"],
|
||||
include_in_schema=_include_ai_in_schema,
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_copilot_router, prefix="/copilot", dependencies=[Depends(get_current_active_user)], tags=["GNS3 Copilot"]
|
||||
_copilot_router,
|
||||
prefix="/copilot",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["GNS3 Copilot"],
|
||||
include_in_schema=_include_ai_in_schema,
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
@@ -130,6 +146,7 @@ router.include_router(
|
||||
prefix="/copilot/projects/{project_id}/chat",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["GNS3 Copilot"],
|
||||
include_in_schema=_include_ai_in_schema,
|
||||
)
|
||||
|
||||
router.include_router(api_keys.router, dependencies=[Depends(get_current_active_user)], tags=["API Keys"])
|
||||
@@ -19,7 +19,7 @@ API routes for appliances.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Union
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
@@ -85,9 +85,7 @@ def get_appliance(appliance_id: UUID) -> schemas.Appliance:
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))],
|
||||
)
|
||||
def add_appliance_version(
|
||||
appliance_id: UUID, appliance_version: Union[schemas.ApplianceVersion, schemas.ApplianceVersionV8]
|
||||
) -> dict:
|
||||
def add_appliance_version(appliance_id: UUID, appliance_version: schemas.ApplianceVersionCreate) -> dict:
|
||||
"""
|
||||
Add a version to an appliance.
|
||||
|
||||
@@ -109,7 +107,7 @@ def add_appliance_version(
|
||||
if version.get("name") == appliance_version.name:
|
||||
raise ControllerError(message=f"Appliance '{appliance_id}' already has version '{appliance_version.name}'")
|
||||
|
||||
appliance.versions.append(appliance_version.model_dump(exclude_unset=True))
|
||||
appliance.versions.append(appliance_version.model_dump(exclude_unset=True, exclude={"registry_version"}))
|
||||
return appliance.asdict()
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ from starlette.requests import ClientDisconnect
|
||||
|
||||
import gns3server.db.models as models
|
||||
from gns3server import schemas
|
||||
from gns3server.api.openapi import binary_request_body
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.config import Config
|
||||
from gns3server.controller import Controller
|
||||
@@ -214,6 +215,7 @@ async def get_images(
|
||||
response_model=schemas.ImageUpload,
|
||||
response_model_exclude_unset=True,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
openapi_extra=binary_request_body(),
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))],
|
||||
)
|
||||
async def upload_image(
|
||||
|
||||
@@ -20,7 +20,7 @@ API routes for links.
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, List, Optional, Union
|
||||
from typing import Any, List, Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import aiohttp
|
||||
@@ -31,6 +31,7 @@ from fastapi.responses import FileResponse, StreamingResponse
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.agent.web_wireshark.manager import WebWiresharkManager
|
||||
from gns3server.api.openapi import PCAP_MEDIA_TYPE, binary_response
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError
|
||||
from gns3server.controller.link import _UNSET, Link
|
||||
@@ -330,7 +331,12 @@ async def restart_wireshark(http_request: Request, link: Link = Depends(dep_link
|
||||
return {"status": "restarted"}
|
||||
|
||||
|
||||
@router.get("/{link_id}/capture/stream", dependencies=[Depends(has_privilege("Link.Capture"))])
|
||||
@router.get(
|
||||
"/{link_id}/capture/stream",
|
||||
response_class=StreamingResponse,
|
||||
responses={200: binary_response(PCAP_MEDIA_TYPE, "Packet capture stream")},
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))],
|
||||
)
|
||||
async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> StreamingResponse:
|
||||
"""
|
||||
Stream the PCAP capture file from compute.
|
||||
@@ -539,7 +545,7 @@ async def update_marker(marker_name: str, marker_data: schemas.MarkerUpdate, lin
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/iface",
|
||||
response_model=Union[schemas.UDPPortInfo, schemas.EthernetPortInfo],
|
||||
response_model=schemas.LinkIfaceInfo,
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_iface(link: Link = Depends(dep_link)) -> dict:
|
||||
@@ -570,6 +576,7 @@ async def get_iface(link: Link = Depends(dep_link)) -> dict:
|
||||
port_type = port.get("type", "")
|
||||
if "udp" in port_type.lower():
|
||||
ifaces_info = {
|
||||
"kind": "udp",
|
||||
"node_id": node.id,
|
||||
"type": f"{port_type}",
|
||||
"lport": port["lport"],
|
||||
@@ -578,6 +585,7 @@ async def get_iface(link: Link = Depends(dep_link)) -> dict:
|
||||
}
|
||||
else:
|
||||
ifaces_info = {
|
||||
"kind": "ethernet",
|
||||
"node_id": node.id,
|
||||
"type": f"{port_type}",
|
||||
"interface": port["interface"],
|
||||
|
||||
@@ -32,6 +32,7 @@ from fastapi.responses import StreamingResponse
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.api.openapi import binary_request_body, binary_response
|
||||
from gns3server.config import Config
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import (
|
||||
@@ -632,7 +633,12 @@ async def list_node_files(
|
||||
return res.json
|
||||
|
||||
|
||||
@router.get("/{node_id}/files/{file_path:path}", dependencies=[Depends(has_privilege("Node.Audit"))])
|
||||
@router.get(
|
||||
"/{node_id}/files/{file_path:path}",
|
||||
response_class=StreamingResponse,
|
||||
responses={200: binary_response(description="File content")},
|
||||
dependencies=[Depends(has_privilege("Node.Audit"))],
|
||||
)
|
||||
async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response:
|
||||
"""
|
||||
Return a file from the node directory.
|
||||
@@ -673,6 +679,13 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response:
|
||||
@router.post(
|
||||
"/{node_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={
|
||||
201: {
|
||||
"description": "File written",
|
||||
"content": {"application/json": {"schema": {"type": "null"}}},
|
||||
}
|
||||
},
|
||||
openapi_extra=binary_request_body(),
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))],
|
||||
)
|
||||
async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)):
|
||||
|
||||
@@ -51,6 +51,7 @@ from fastapi.responses import FileResponse, StreamingResponse
|
||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.api.openapi import ZIP_MEDIA_TYPE, binary_request_body, binary_response
|
||||
from gns3server.api.responses import NDJSON_MEDIA_TYPE, NOTIFICATION_STREAM_RESPONSES, NDJSONResponse
|
||||
from gns3server.controller import Controller, marker_replay
|
||||
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerError
|
||||
@@ -688,7 +689,12 @@ async def project_marker_ws_notifications(
|
||||
log.warning(f"Error while sending marker event to WebSocket client: {e}")
|
||||
|
||||
|
||||
@router.get("/{project_id}/export", dependencies=[Depends(has_privilege("Project.Audit"))])
|
||||
@router.get(
|
||||
"/{project_id}/export",
|
||||
response_class=StreamingResponse,
|
||||
responses={200: binary_response(ZIP_MEDIA_TYPE, "Portable project archive")},
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))],
|
||||
)
|
||||
async def export_project(
|
||||
project: Project = Depends(dep_project),
|
||||
include_snapshots: bool = False,
|
||||
@@ -774,6 +780,7 @@ async def export_project(
|
||||
"/{project_id}/import",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
openapi_extra=binary_request_body(ZIP_MEDIA_TYPE),
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def import_project(project_id: UUID, request: Request, name: Optional[str] = None) -> schemas.Project:
|
||||
@@ -898,7 +905,12 @@ async def unlock_project(project: Project = Depends(dep_project)) -> None:
|
||||
project.unlock()
|
||||
|
||||
|
||||
@router.get("/{project_id}/files/{file_path:path}", dependencies=[Depends(has_privilege("Project.Audit"))])
|
||||
@router.get(
|
||||
"/{project_id}/files/{file_path:path}",
|
||||
response_class=FileResponse,
|
||||
responses={200: binary_response(description="File content")},
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))],
|
||||
)
|
||||
async def get_file(file_path: str, project: Project = Depends(dep_project)) -> FileResponse:
|
||||
"""
|
||||
Return a file from a project.
|
||||
@@ -920,7 +932,17 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)) -> F
|
||||
return FileResponse(path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.get("/{project_id}/gns3file", dependencies=[Depends(has_privilege("Project.Audit"))])
|
||||
@router.get(
|
||||
"/{project_id}/gns3file",
|
||||
response_class=FileResponse,
|
||||
responses={
|
||||
200: {
|
||||
"description": "Project topology file",
|
||||
"content": {"application/json": {"schema": {"type": "object"}}},
|
||||
}
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))],
|
||||
)
|
||||
async def get_project_gns3_file(project: Project = Depends(dep_project)) -> FileResponse:
|
||||
"""
|
||||
Return the .gns3 topology file of a project.
|
||||
@@ -938,6 +960,7 @@ async def get_project_gns3_file(project: Project = Depends(dep_project)) -> File
|
||||
@router.post(
|
||||
"/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
openapi_extra=binary_request_body(),
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
||||
|
||||
@@ -27,6 +27,7 @@ from fastapi import APIRouter, Depends, Request, Response, status
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.api.openapi import binary_request_body, binary_response
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError, ControllerNotFoundError
|
||||
from gns3server.utils.get_resource import get_resource
|
||||
@@ -53,7 +54,11 @@ def get_symbols() -> List[dict]:
|
||||
|
||||
@router.get(
|
||||
"/{symbol_id:path}/raw",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}},
|
||||
response_class=FileResponse,
|
||||
responses={
|
||||
200: binary_response(description="Symbol file"),
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))],
|
||||
)
|
||||
async def get_symbol(symbol_id: str, request: Request) -> Response:
|
||||
@@ -112,6 +117,7 @@ def get_default_symbols() -> dict:
|
||||
@router.post(
|
||||
"/{symbol_id:path}/raw",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
openapi_extra=binary_request_body(),
|
||||
dependencies=[Depends(has_privilege("Symbol.Allocate"))],
|
||||
)
|
||||
async def upload_symbol(symbol_id: str, request: Request) -> None:
|
||||
|
||||
Reference in new issue
Block a user