mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge branch '3.1' into image-manager-out-of-sync
This commit is contained in:
129 files changed
+944
-863
No files matched your search
@@ -18,6 +18,7 @@
|
||||
API routes for ATM switch nodes.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, Body, Path, status, HTTPException
|
||||
@@ -29,7 +30,9 @@ from gns3server import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.atm_switch import ATMSwitch
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or ATM switch node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or ATM switch node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -57,13 +60,13 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate
|
||||
|
||||
# Use the Dynamips ATM switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.get("name"),
|
||||
data.get("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
data.get("node_id"),
|
||||
node_type="atm_switch",
|
||||
mappings=node_data.get("mappings"),
|
||||
mappings=data.get("mappings"),
|
||||
)
|
||||
return node.asdict()
|
||||
|
||||
@@ -97,11 +100,11 @@ async def update_atm_switch(
|
||||
Update an ATM switch node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "mappings" in node_data:
|
||||
node.mappings = node_data["mappings"]
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data and node.name != data["name"]:
|
||||
await node.set_name(data["name"])
|
||||
if "mappings" in data:
|
||||
node.mappings = data["mappings"]
|
||||
node.updated()
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ def get_capabilities(request: Request) -> dict:
|
||||
node_types.extend(module.node_types())
|
||||
|
||||
# record the controller hostname or IP address
|
||||
request.app.state.controller_host = request.client.host
|
||||
if request.client:
|
||||
request.app.state.controller_host = request.client.host
|
||||
|
||||
return {
|
||||
"version": __version__,
|
||||
|
||||
@@ -23,14 +23,16 @@ import os
|
||||
from fastapi import APIRouter, Depends, Path, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from gns3server.compute.builtin.nodes.cloud import Cloud
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or cloud node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or cloud node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -57,21 +59,21 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate) -> sche
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
data.get("node_id"),
|
||||
node_type="cloud",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
ports=data.get("ports_mapping"),
|
||||
)
|
||||
|
||||
# add the remote console settings
|
||||
node.remote_console_host = node_data.get("remote_console_host", node.remote_console_host)
|
||||
node.remote_console_port = node_data.get("remote_console_port", node.remote_console_port)
|
||||
node.remote_console_type = node_data.get("remote_console_type", node.remote_console_type)
|
||||
node.remote_console_http_path = node_data.get("remote_console_http_path", node.remote_console_http_path)
|
||||
node.usage = node_data.get("usage", "")
|
||||
node.remote_console_host = data.get("remote_console_host", node.remote_console_host)
|
||||
node.remote_console_port = data.get("remote_console_port", node.remote_console_port)
|
||||
node.remote_console_type = data.get("remote_console_type", node.remote_console_type)
|
||||
node.remote_console_http_path = data.get("remote_console_http_path", node.remote_console_http_path)
|
||||
node.usage = data.get("usage", "")
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -90,8 +92,8 @@ async def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep
|
||||
Update a cloud node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
node.updated()
|
||||
@@ -178,7 +180,7 @@ async def update_cloud_nio(
|
||||
|
||||
nio = node.get_nio(port_number)
|
||||
nio.filters.clear()
|
||||
if nio_data.filters:
|
||||
if isinstance(nio_data, schemas.UDPNIO) and nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
# NIO type is a Union (Ethernet/TAP/UDP); only UDPNIO carries markers.
|
||||
nio.markers = getattr(nio_data, "markers", None) or {}
|
||||
|
||||
@@ -163,7 +163,7 @@ def compute_statistics() -> dict:
|
||||
|
||||
@router.get("/qemu/capabilities")
|
||||
async def get_qemu_capabilities() -> dict:
|
||||
capabilities = {"kvm": []}
|
||||
capabilities: dict[str, list] = {"kvm": []}
|
||||
kvms = await Qemu.get_kvm_archs()
|
||||
if kvms:
|
||||
capabilities["kvm"] = kvms
|
||||
|
||||
@@ -91,9 +91,9 @@ async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSock
|
||||
if not separator:
|
||||
raise invalid_user_credentials_exc
|
||||
|
||||
username = secrets.compare_digest(username, server_settings.compute_username)
|
||||
password = secrets.compare_digest(password, server_settings.compute_password.get_secret_value())
|
||||
if not (username and password):
|
||||
username_valid = secrets.compare_digest(username, server_settings.compute_username)
|
||||
password_valid = secrets.compare_digest(password, server_settings.compute_password.get_secret_value())
|
||||
if not (username_valid and password_valid):
|
||||
raise invalid_user_credentials_exc
|
||||
|
||||
except HTTPException as e:
|
||||
@@ -101,5 +101,6 @@ async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSock
|
||||
websocket_error = {"action": "log.error", "event": {"message": err_msg}}
|
||||
await websocket.send_json(websocket_error)
|
||||
log.error(err_msg)
|
||||
return await websocket.close(code=1008)
|
||||
await websocket.close(code=1008)
|
||||
return None
|
||||
return websocket
|
||||
@@ -24,14 +24,16 @@ from fastapi import APIRouter, WebSocket, Depends, Body, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.compute.docker.docker_vm import DockerVM
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -53,13 +55,13 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
|
||||
async def create_docker_node(project_id: UUID, node_create: schemas.DockerCreate) -> schemas.Docker:
|
||||
"""
|
||||
Create a new Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node_data = jsonable_encoder(node_create, exclude_unset=True)
|
||||
container = await docker_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
@@ -124,7 +126,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
|
||||
|
||||
@router.put("/{node_id}", response_model=schemas.Docker, dependencies=[Depends(compute_authentication)])
|
||||
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
async def update_docker_node(node_update: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
"""
|
||||
Update a Docker node.
|
||||
"""
|
||||
@@ -152,7 +154,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
||||
]
|
||||
|
||||
changed = False
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node_data = jsonable_encoder(node_update, exclude_unset=True)
|
||||
for prop in props:
|
||||
# hasattr: startup_config_content only exists on IOLDockerVM
|
||||
if prop in node_data and hasattr(node, prop) and node_data[prop] != getattr(node, prop):
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
from fastapi import APIRouter, WebSocket, Body, Depends, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import List, Union
|
||||
from typing import Any, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
@@ -32,7 +32,9 @@ from gns3server import schemas
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -64,25 +66,26 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
platform = node_data.platform
|
||||
chassis: Optional[str]
|
||||
if not node_data.chassis and platform in DEFAULT_CHASSIS:
|
||||
chassis = DEFAULT_CHASSIS[platform]
|
||||
else:
|
||||
chassis = node_data.chassis
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
dynamips_id=node_data.get("dynamips_id"),
|
||||
data.get("node_id"),
|
||||
dynamips_id=data.get("dynamips_id"),
|
||||
platform=platform,
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
console=data.get("console"),
|
||||
console_type=data.get("console_type", "telnet"),
|
||||
aux=data.get("aux"),
|
||||
aux_type=data.pop("aux_type", "none"),
|
||||
chassis=chassis,
|
||||
node_type="dynamips",
|
||||
)
|
||||
await dynamips_manager.update_vm_settings(vm, node_data)
|
||||
await dynamips_manager.update_vm_settings(vm, data)
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
API routes for Ethernet hub nodes.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, Body, Path, status, HTTPException
|
||||
@@ -29,7 +30,9 @@ from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.ethernet_hub import EthernetHub
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -57,13 +60,13 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr
|
||||
|
||||
# Use the Dynamips Ethernet hub to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
data.get("node_id"),
|
||||
node_type="ethernet_hub",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
ports=data.get("ports_mapping"),
|
||||
)
|
||||
return node.asdict()
|
||||
|
||||
@@ -97,11 +100,11 @@ async def update_ethernet_hub(
|
||||
Update an Ethernet hub.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "ports_mapping" in node_data:
|
||||
node.ports_mapping = node_data["ports_mapping"]
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data and node.name != data["name"]:
|
||||
await node.set_name(data["name"])
|
||||
if "ports_mapping" in data:
|
||||
node.ports_mapping = data["ports_mapping"]
|
||||
node.updated()
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ through uBridge's ``brctl`` module (see
|
||||
``gns3server.compute.builtin.nodes.ethernet_switch``).
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, Body, Path, status, HTTPException
|
||||
@@ -33,7 +34,9 @@ from gns3server.compute.builtin import Builtin
|
||||
from gns3server.compute.builtin.nodes.ethernet_switch import EthernetSwitch
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet switch node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet switch node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -60,17 +63,17 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
data.get("node_id"),
|
||||
console=data.get("console"),
|
||||
console_type=data.get("console_type"),
|
||||
node_type="ethernet_switch",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
ports=data.get("ports_mapping"),
|
||||
)
|
||||
node.usage = node_data.get("usage", "")
|
||||
node.usage = data.get("usage", "")
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -100,16 +103,16 @@ async def update_ethernet_switch(
|
||||
Update an Ethernet switch.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
node.name = node_data["name"]
|
||||
if "usage" in node_data:
|
||||
node.usage = node_data["usage"]
|
||||
if "ports_mapping" in node_data:
|
||||
node.ports_mapping = node_data["ports_mapping"]
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data and node.name != data["name"]:
|
||||
node.name = data["name"]
|
||||
if "usage" in data:
|
||||
node.usage = data["usage"]
|
||||
if "ports_mapping" in data:
|
||||
node.ports_mapping = data["ports_mapping"]
|
||||
await node.update_port_settings()
|
||||
if "console_type" in node_data:
|
||||
node.console_type = node_data["console_type"]
|
||||
if "console_type" in data:
|
||||
node.console_type = data["console_type"]
|
||||
node.updated()
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
API routes for Frame Relay switch nodes.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, Body, Path, status, HTTPException
|
||||
@@ -29,7 +30,9 @@ from gns3server import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.frame_relay_switch import FrameRelaySwitch
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Frame Relay switch node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Frame Relay switch node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -59,13 +62,13 @@ async def create_frame_relay_switch(
|
||||
|
||||
# Use the Dynamips Frame Relay switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
data.get("node_id"),
|
||||
node_type="frame_relay_switch",
|
||||
mappings=node_data.get("mappings"),
|
||||
mappings=data.get("mappings"),
|
||||
)
|
||||
return node.asdict()
|
||||
|
||||
@@ -99,11 +102,11 @@ async def update_frame_relay_switch(
|
||||
Update an Frame Relay switch node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "mappings" in node_data:
|
||||
node.mappings = node_data["mappings"]
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data and node.name != data["name"]:
|
||||
await node.set_name(data["name"])
|
||||
if "mappings" in data:
|
||||
node.mappings = data["mappings"]
|
||||
node.updated()
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
from fastapi import APIRouter, WebSocket, Depends, Body, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@@ -32,7 +32,9 @@ from gns3server.compute.iou.iou_vm import IOUVM
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -60,18 +62,18 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> sch
|
||||
"""
|
||||
|
||||
iou = IOU.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await iou.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
application_id=node_data.get("application_id"),
|
||||
path=node_data.get("path"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
data.get("node_id"),
|
||||
application_id=data.get("application_id"),
|
||||
path=data.get("path"),
|
||||
console=data.get("console"),
|
||||
console_type=data.get("console_type", "telnet"),
|
||||
)
|
||||
|
||||
for name, value in node_data.items():
|
||||
for name, value in data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the controller
|
||||
@@ -79,7 +81,7 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> sch
|
||||
continue
|
||||
if name == "private_config_content" and (vm.private_config_content and len(vm.private_config_content) > 0):
|
||||
continue
|
||||
if node_data.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
|
||||
if data.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
|
||||
continue
|
||||
setattr(vm, name, value)
|
||||
return vm.asdict()
|
||||
@@ -100,8 +102,8 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
||||
Update an IOU node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager
|
||||
@@ -147,8 +149,8 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
Start an IOU node.
|
||||
"""
|
||||
|
||||
start_data = jsonable_encoder(start_data, exclude_unset=True)
|
||||
for name, value in start_data.items():
|
||||
start_values = jsonable_encoder(start_data, exclude_unset=True)
|
||||
for name, value in start_values.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
|
||||
@@ -223,7 +225,7 @@ async def update_iou_node_nio(
|
||||
|
||||
nio = node.get_nio(adapter_number, port_number)
|
||||
nio.filters.clear()
|
||||
if nio_data.filters:
|
||||
if isinstance(nio_data, schemas.UDPNIO) and nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
# NIO type is a Union (Ethernet/TAP/UDP); only UDPNIO carries markers.
|
||||
nio.markers = getattr(nio_data, "markers", None) or {}
|
||||
|
||||
@@ -23,14 +23,16 @@ import os
|
||||
from fastapi import APIRouter, Depends, Path, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from gns3server.compute.builtin.nodes.nat import Nat
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or NAT node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or NAT node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -57,16 +59,16 @@ async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate) -> sch
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
data.get("node_id"),
|
||||
node_type="nat",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
ports=data.get("ports_mapping"),
|
||||
)
|
||||
|
||||
node.usage = node_data.get("usage", "")
|
||||
node.usage = data.get("usage", "")
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -85,8 +87,8 @@ async def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_
|
||||
Update a NAT node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
node.updated()
|
||||
@@ -170,7 +172,7 @@ async def update_nat_node_nio(
|
||||
|
||||
nio = node.get_nio(port_number)
|
||||
nio.filters.clear()
|
||||
if nio_data.filters:
|
||||
if isinstance(nio_data, schemas.UDPNIO) and nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await node.update_nio(port_number, nio)
|
||||
return nio.asdict()
|
||||
|
||||
@@ -39,14 +39,15 @@ async def project_ws_notifications(websocket: Union[None, WebSocket] = Depends(w
|
||||
"""
|
||||
|
||||
if websocket:
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
|
||||
client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown"
|
||||
log.info(f"New client {client} has connected to compute WebSocket")
|
||||
try:
|
||||
with NotificationManager.instance().queue() as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
await websocket.send_text(notification)
|
||||
except (ConnectionClosed, WebSocketDisconnect):
|
||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
|
||||
log.info(f"Client {client} has disconnected from compute WebSocket")
|
||||
except WebSocketException as e:
|
||||
log.warning(f"Error while sending to controller event to WebSocket client: {e}")
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ from gns3server import schemas
|
||||
router = APIRouter()
|
||||
|
||||
# How many clients have subscribed to notifications
|
||||
_notifications_listening = {}
|
||||
_notifications_listening: dict[str, int] = {}
|
||||
|
||||
|
||||
def dep_project(project_id: UUID) -> Project:
|
||||
@@ -74,12 +74,12 @@ def create_compute_project(project_data: schemas.ProjectCreate) -> schemas.Proje
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project_data = jsonable_encoder(project_data, exclude_unset=True)
|
||||
data = jsonable_encoder(project_data, exclude_unset=True)
|
||||
project = pm.create_project(
|
||||
name=project_data.get("name"),
|
||||
path=project_data.get("path"),
|
||||
project_id=project_data.get("project_id"),
|
||||
variables=project_data.get("variables", None),
|
||||
name=data.get("name"),
|
||||
path=data.get("path"),
|
||||
project_id=data.get("project_id"),
|
||||
variables=data.get("variables", None),
|
||||
)
|
||||
return project.asdict()
|
||||
|
||||
@@ -261,7 +261,7 @@ async def create_batch_nios(
|
||||
# This is what makes builtin L2 nodes (ethernet_switch/hub/cloud/nat)
|
||||
# start their uBridge concurrently during project open instead of one
|
||||
# at a time (~0.5s each for fork + socket connect).
|
||||
per_node = {}
|
||||
per_node: dict[str, list[schemas.BatchNIOEntry]] = {}
|
||||
for entry in batch.nios:
|
||||
per_node.setdefault(entry.node_id, []).append(entry)
|
||||
|
||||
@@ -308,7 +308,7 @@ async def update_batch_nios(
|
||||
# Group entries by node so that different nodes' uBridge processes are
|
||||
# updated in parallel (each node has its own AF_UNIX socket). Within a
|
||||
# node entries are serial to respect the per-node uBridge command lock.
|
||||
per_node = {}
|
||||
per_node: dict[str, list[schemas.BatchNIOEntry]] = {}
|
||||
for entry in batch.nios:
|
||||
per_node.setdefault(entry.node_id, []).append(entry)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@@ -36,7 +36,9 @@ import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -64,29 +66,29 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
|
||||
"""
|
||||
|
||||
qemu = Qemu.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
disk_images_to_reset = set(node_data.pop("disk_images_to_reset", []))
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
disk_images_to_reset = set(data.pop("disk_images_to_reset", []))
|
||||
vm = await qemu.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.pop("node_id", None),
|
||||
linked_clone=node_data.get("linked_clone", True),
|
||||
qemu_path=node_data.pop("qemu_path", None),
|
||||
console=node_data.pop("console", None),
|
||||
console_type=node_data.pop("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
platform=node_data.pop("platform", None),
|
||||
data.pop("node_id", None),
|
||||
linked_clone=data.get("linked_clone", True),
|
||||
qemu_path=data.pop("qemu_path", None),
|
||||
console=data.pop("console", None),
|
||||
console_type=data.pop("console_type", "telnet"),
|
||||
aux=data.get("aux"),
|
||||
aux_type=data.pop("aux_type", "none"),
|
||||
platform=data.pop("platform", None),
|
||||
)
|
||||
|
||||
# update the disk image with the backing file if provided
|
||||
# this is needed when duplicating a node that uses backed disk images
|
||||
drives = ["a", "b", "c", "d"]
|
||||
for drive in drives:
|
||||
disk_image_backing_file = node_data.get(f"hd{drive}_disk_image_backing_file")
|
||||
disk_image_backing_file = data.get(f"hd{drive}_disk_image_backing_file")
|
||||
if disk_image_backing_file:
|
||||
log.debug(f"Updating disk image for drive {drive} with backing file {disk_image_backing_file}")
|
||||
node_data[f"hd{drive}_disk_image"] = disk_image_backing_file
|
||||
data[f"hd{drive}_disk_image"] = disk_image_backing_file
|
||||
|
||||
# Validate every explicitly replaced disk before removing its stale
|
||||
# overlay. Other unresolved disks may still make this create request fail,
|
||||
@@ -94,7 +96,7 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
|
||||
for drive in drives:
|
||||
disk_image_property = f"hd{drive}_disk_image"
|
||||
if disk_image_property in disk_images_to_reset:
|
||||
replacement_image = node_data.get(disk_image_property)
|
||||
replacement_image = data.get(disk_image_property)
|
||||
if replacement_image:
|
||||
vm.manager.get_abs_image_path(replacement_image, vm.working_dir)
|
||||
local_disk_name = f"hd{drive}_disk.qcow2"
|
||||
@@ -107,11 +109,11 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
|
||||
log.info(
|
||||
"Removing stale linked-clone disk '%s' before using replacement image '%s'",
|
||||
local_disk,
|
||||
node_data.get(disk_image_property),
|
||||
data.get(disk_image_property),
|
||||
)
|
||||
vm.delete_disk_image(local_disk_name)
|
||||
|
||||
for name, value in node_data.items():
|
||||
for name, value in data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
@@ -133,10 +135,10 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
||||
Update a Qemu node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
# update the console first to avoid issue if updating console type
|
||||
node.console = node_data.pop("console", node.console)
|
||||
for name, value in node_data.items():
|
||||
node.console = data.pop("console", node.console)
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
await node.update_property(name, value)
|
||||
node.updated()
|
||||
|
||||
@@ -24,7 +24,7 @@ from fastapi import APIRouter, WebSocket, Depends, Path, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.virtualbox import VirtualBox
|
||||
@@ -33,7 +33,9 @@ from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses, deprecated=True)
|
||||
|
||||
@@ -61,24 +63,24 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vbox_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmname"),
|
||||
linked_clone=node_data.pop("linked_clone", False),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
adapters=node_data.get("adapters", 0),
|
||||
data.get("node_id"),
|
||||
data.pop("vmname"),
|
||||
linked_clone=data.pop("linked_clone", False),
|
||||
console=data.get("console", None),
|
||||
console_type=data.get("console_type", "telnet"),
|
||||
adapters=data.get("adapters", 0),
|
||||
)
|
||||
|
||||
if "ram" in node_data:
|
||||
ram = node_data.pop("ram")
|
||||
if "ram" in data:
|
||||
ram = data.pop("ram")
|
||||
if ram != vm.ram:
|
||||
await vm.set_ram(ram)
|
||||
|
||||
for name, value in node_data.items():
|
||||
for name, value in data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
@@ -103,10 +105,10 @@ async def update_virtualbox_node(
|
||||
Update a VirtualBox node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data:
|
||||
name = node_data.pop("name")
|
||||
vmname = node_data.pop("vmname", None)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data:
|
||||
name = data.pop("name")
|
||||
vmname = data.pop("vmname", None)
|
||||
if name != node.name:
|
||||
oldname = node.name
|
||||
node.name = name
|
||||
@@ -118,20 +120,20 @@ async def update_virtualbox_node(
|
||||
node.updated()
|
||||
raise e
|
||||
|
||||
if "adapters" in node_data:
|
||||
adapters = node_data.pop("adapters")
|
||||
if "adapters" in data:
|
||||
adapters = data.pop("adapters")
|
||||
if adapters != node.adapters:
|
||||
await node.set_adapters(adapters)
|
||||
|
||||
if "ram" in node_data:
|
||||
ram = node_data.pop("ram")
|
||||
if "ram" in data:
|
||||
ram = data.pop("ram")
|
||||
if ram != node.ram:
|
||||
await node.set_ram(ram)
|
||||
|
||||
# update the console first to avoid issue if updating console type
|
||||
node.console = node_data.pop("console", node.console)
|
||||
node.console = data.pop("console", node.console)
|
||||
|
||||
for name, value in node_data.items():
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from fastapi import APIRouter, WebSocket, Depends, Path, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.vmware import VMware
|
||||
@@ -32,7 +32,9 @@ from gns3server.compute.vmware.vmware_vm import VMwareVM
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses, deprecated=True)
|
||||
|
||||
@@ -60,18 +62,18 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate)
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vmware_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmx_path"),
|
||||
linked_clone=node_data.pop("linked_clone"),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
data.get("node_id"),
|
||||
data.pop("vmx_path"),
|
||||
linked_clone=data.pop("linked_clone"),
|
||||
console=data.get("console", None),
|
||||
console_type=data.get("console_type", "telnet"),
|
||||
)
|
||||
|
||||
for name, value in node_data.items():
|
||||
for name, value in data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
@@ -94,10 +96,10 @@ async def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = D
|
||||
Update a VMware node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
# update the console first to avoid issue if updating console type
|
||||
node.console = node_data.pop("console", node.console)
|
||||
for name, value in node_data.items():
|
||||
node.console = data.pop("console", node.console)
|
||||
for name, value in data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@@ -31,7 +31,9 @@ from gns3server.compute.vpcs import VPCS
|
||||
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -59,14 +61,14 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> s
|
||||
"""
|
||||
|
||||
vpcs = VPCS.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vpcs.create_node(
|
||||
node_data["name"],
|
||||
data["name"],
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
startup_script=node_data.get("startup_script"),
|
||||
data.get("node_id"),
|
||||
console=data.get("console"),
|
||||
console_type=data.get("console_type", "telnet"),
|
||||
startup_script=data.get("startup_script"),
|
||||
)
|
||||
|
||||
return vm.asdict()
|
||||
@@ -87,14 +89,14 @@ async def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends
|
||||
Update a VPCS node.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
name = node_data.get("name", node.name)
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
name = data.get("name", node.name)
|
||||
if node.name != name:
|
||||
node.name = name
|
||||
console = node_data.get("console", node.console)
|
||||
console = data.get("console", node.console)
|
||||
if node.console != console:
|
||||
node.console = console
|
||||
console_type = node_data.get("console_type", node.console_type)
|
||||
console_type = data.get("console_type", node.console_type)
|
||||
if node.console_type != console_type:
|
||||
node.console_type = console_type
|
||||
node.updated()
|
||||
|
||||
@@ -25,13 +25,14 @@ from fastapi import APIRouter, Depends, Request, status
|
||||
from fastapi.routing import APIRoute, _IncludedRouter
|
||||
from starlette.routing import BaseRoute, Mount
|
||||
from uuid import UUID
|
||||
from typing import Iterator, List, Sequence
|
||||
from typing import Iterator, List, Optional, Sequence
|
||||
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerNotFoundError
|
||||
|
||||
from gns3server.controller import Controller
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
@@ -96,7 +97,7 @@ async def endpoints(
|
||||
endpoints = [{"endpoint": "/", "name": "All endpoints", "endpoint_type": "root"}]
|
||||
|
||||
def add_to_endpoints(endpoint: str, name: str, endpoint_type: str) -> None:
|
||||
if endpoint not in endpoints:
|
||||
if not any(e["endpoint"] == endpoint for e in endpoints):
|
||||
endpoints.append({"endpoint": endpoint, "name": name, "endpoint_type": endpoint_type})
|
||||
|
||||
# projects
|
||||
@@ -175,7 +176,7 @@ async def endpoints(
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.ACE], dependencies=[Depends(has_privilege("ACE.Audit"))])
|
||||
async def get_aces(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[schemas.ACE]:
|
||||
async def get_aces(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[models.ACE]:
|
||||
"""
|
||||
Get all ACL entries.
|
||||
|
||||
@@ -193,7 +194,7 @@ async def get_aces(rbac_repo: RbacRepository = Depends(get_repository(RbacReposi
|
||||
)
|
||||
async def create_ace(
|
||||
request: Request, ace_create: schemas.ACECreate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.ACE:
|
||||
) -> models.ACE:
|
||||
"""
|
||||
Create a new ACL entry.
|
||||
|
||||
@@ -224,7 +225,7 @@ async def create_ace(
|
||||
async def get_ace(
|
||||
ace_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.ACE:
|
||||
) -> models.ACE:
|
||||
"""
|
||||
Get an ACL entry.
|
||||
|
||||
@@ -240,7 +241,7 @@ async def get_ace(
|
||||
@router.put("/{ace_id}", response_model=schemas.ACE, dependencies=[Depends(has_privilege("ACE.Modify"))])
|
||||
async def update_ace(
|
||||
ace_id: UUID, ace_update: schemas.ACEUpdate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.ACE:
|
||||
) -> Optional[models.ACE]:
|
||||
"""
|
||||
Update an ACL entry.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ API_KEY_PREFIX = "gns3_"
|
||||
API_KEY_BYTES = 32
|
||||
|
||||
|
||||
def _generate_api_key(api_key_id: UUID = None) -> tuple[str, str, str, UUID]:
|
||||
def _generate_api_key(api_key_id: UUID | None = None) -> tuple[str, str, str, UUID]:
|
||||
if api_key_id is None:
|
||||
api_key_id = uuid4()
|
||||
random_bytes = secrets.token_hex(API_KEY_BYTES)
|
||||
|
||||
@@ -23,7 +23,7 @@ Nested under projects: /v3/projects/{project_id}/chat/...
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
@@ -40,7 +40,7 @@ from .dependencies.authentication import get_current_active_user
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Resource not found"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {404: {"model": schemas.ErrorMessage, "description": "Resource not found"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from typing import Any, List, Union, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.controller import Controller
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.computes import ComputesRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.services.computes import ComputesService
|
||||
@@ -31,7 +32,7 @@ from gns3server import schemas
|
||||
from .dependencies.database import get_repository
|
||||
from .dependencies.rbac import has_privilege
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Compute not found"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {404: {"model": schemas.ErrorMessage, "description": "Compute not found"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -51,14 +52,14 @@ async def create_compute(
|
||||
compute_create: schemas.ComputeCreate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
connect: Optional[bool] = False,
|
||||
) -> schemas.Compute:
|
||||
) -> models.Compute:
|
||||
"""
|
||||
Create a new compute on the controller.
|
||||
|
||||
Required privilege: Compute.Allocate
|
||||
"""
|
||||
|
||||
return await ComputesService(computes_repo).create_compute(compute_create, connect)
|
||||
return await ComputesService(computes_repo).create_compute(compute_create, bool(connect))
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -86,7 +87,7 @@ async def connect_compute(compute_id: Union[str, UUID]) -> None:
|
||||
)
|
||||
async def get_compute(
|
||||
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
) -> schemas.Compute:
|
||||
) -> Union[models.Compute, dict]:
|
||||
"""
|
||||
Return a compute from the controller.
|
||||
|
||||
@@ -104,7 +105,7 @@ async def get_compute(
|
||||
)
|
||||
async def get_computes(
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
) -> List[schemas.Compute]:
|
||||
) -> List[models.Compute]:
|
||||
"""
|
||||
Return all computes known by the controller.
|
||||
|
||||
@@ -124,7 +125,7 @@ async def update_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
compute_update: schemas.ComputeUpdate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
) -> schemas.Compute:
|
||||
) -> models.Compute:
|
||||
"""
|
||||
Update a compute on the controller.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import os
|
||||
import time
|
||||
import psutil
|
||||
|
||||
from fastapi import APIRouter, Request, Depends, WebSocket, WebSocketDisconnect, status
|
||||
from fastapi import APIRouter, FastAPI, Request, Depends, WebSocket, WebSocketDisconnect, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.routing import Mount
|
||||
@@ -64,7 +64,7 @@ def get_version(request: Request) -> dict:
|
||||
# compute subapp
|
||||
controller_host = None
|
||||
for route in request.app.routes:
|
||||
if isinstance(route, Mount) and route.name == "compute":
|
||||
if isinstance(route, Mount) and route.name == "compute" and isinstance(route.app, FastAPI):
|
||||
controller_host = route.app.state.controller_host
|
||||
|
||||
local_server = Config.instance().settings.Server.local
|
||||
@@ -129,7 +129,7 @@ async def shutdown() -> None:
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
log.error(f"Could not close project: {e}", exc_info=1)
|
||||
log.error(f"Could not close project: {e}", exc_info=True)
|
||||
continue
|
||||
|
||||
# then shutdown the server itself
|
||||
@@ -191,8 +191,8 @@ async def statistics() -> dict:
|
||||
# Node statistics - distinguish open vs closed project nodes
|
||||
open_project_nodes = []
|
||||
closed_project_nodes = []
|
||||
node_by_type = {}
|
||||
node_by_status = {}
|
||||
node_by_type: Dict[str, int] = {}
|
||||
node_by_status: Dict[str, int] = {}
|
||||
|
||||
for project in projects:
|
||||
nodes = project.nodes.values()
|
||||
@@ -264,9 +264,8 @@ async def controller_http_notifications(request: Request) -> StreamingResponse:
|
||||
|
||||
from gns3server.api.server import app
|
||||
|
||||
log.info(
|
||||
f"New client {request.client.host}:{request.client.port} has connected to controller HTTP notification stream"
|
||||
)
|
||||
client = f"{request.client.host}:{request.client.port}" if request.client else "unknown"
|
||||
log.info(f"New client {client} has connected to controller HTTP notification stream")
|
||||
|
||||
async def event_stream():
|
||||
try:
|
||||
@@ -275,10 +274,7 @@ async def controller_http_notifications(request: Request) -> StreamingResponse:
|
||||
msg = await queue.get_json(5)
|
||||
yield f"{msg}\n".encode("utf-8")
|
||||
finally:
|
||||
log.info(
|
||||
f"Client {request.client.host}:{request.client.port} has disconnected from controller HTTP "
|
||||
f"notification stream"
|
||||
)
|
||||
log.info(f"Client {client} has disconnected from controller HTTP notification stream")
|
||||
|
||||
return StreamingResponse(event_stream(), media_type="application/json")
|
||||
|
||||
@@ -294,14 +290,15 @@ async def controller_ws_notifications(
|
||||
if current_user is None:
|
||||
return
|
||||
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to controller WebSocket")
|
||||
client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown"
|
||||
log.info(f"New client {client} has connected to controller WebSocket")
|
||||
try:
|
||||
with Controller.instance().notification.controller_queue() as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
await websocket.send_text(notification)
|
||||
except (ConnectionClosed, WebSocketDisconnect):
|
||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller WebSocket")
|
||||
log.info(f"Client {client} has disconnected from controller WebSocket")
|
||||
except WebSocketException as e:
|
||||
log.warning(f"Error while sending to controller event to WebSocket client: {e}")
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ async def get_user_from_token(
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
api_keys_repo: ApiKeysRepository = Depends(get_repository(ApiKeysRepository)),
|
||||
token: Optional[str] = Query(None, include_in_schema=False),
|
||||
) -> schemas.User:
|
||||
) -> models.User:
|
||||
|
||||
if bearer_token:
|
||||
# bearer token is used first, then any token passed as a URL parameter
|
||||
@@ -169,7 +169,7 @@ async def get_current_active_user_from_websocket(
|
||||
websocket: WebSocket,
|
||||
token: str = Query(...),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> Optional[schemas.User]:
|
||||
) -> Optional[models.User]:
|
||||
|
||||
# Extract requested subprotocols from headers for proper WebSocket negotiation
|
||||
# This is critical for protocols like xpra that require specific subprotocols
|
||||
@@ -238,4 +238,5 @@ async def get_current_active_user_from_websocket(
|
||||
websocket_error = {"action": "log.error", "event": {"message": err_msg}}
|
||||
await websocket.send_json(websocket_error)
|
||||
log.error(err_msg)
|
||||
return await websocket.close(code=1008)
|
||||
await websocket.close(code=1008)
|
||||
return None
|
||||
@@ -14,7 +14,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Callable, Type
|
||||
from typing import AsyncGenerator, Callable, Type
|
||||
from fastapi import Depends
|
||||
from starlette.requests import HTTPConnection
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -22,7 +22,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from gns3server.db.repositories.base import BaseRepository
|
||||
|
||||
|
||||
async def get_db_session(request: HTTPConnection) -> AsyncSession:
|
||||
async def get_db_session(request: HTTPConnection) -> AsyncGenerator[AsyncSession, None]:
|
||||
|
||||
async with AsyncSession(request.app.state._db_engine, expire_on_commit=False) as session:
|
||||
try:
|
||||
@@ -32,7 +32,7 @@ async def get_db_session(request: HTTPConnection) -> AsyncSession:
|
||||
|
||||
|
||||
def get_repository(repo: Type[BaseRepository]) -> Callable:
|
||||
def get_repo(db_session: AsyncSession = Depends(get_db_session)) -> Type[BaseRepository]:
|
||||
def get_repo(db_session: AsyncSession = Depends(get_db_session)) -> BaseRepository:
|
||||
return repo(db_session)
|
||||
|
||||
return get_repo
|
||||
@@ -20,7 +20,7 @@ API routes for drawings.
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from typing import List
|
||||
from typing import Any, List
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.controller import Controller
|
||||
@@ -30,7 +30,9 @@ from gns3server import schemas
|
||||
from .dependencies.database import get_repository
|
||||
from .dependencies.rbac import has_privilege
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ API routes for user groups.
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
@@ -31,6 +31,7 @@ from gns3server.controller.controller_error import (
|
||||
ControllerForbiddenError,
|
||||
)
|
||||
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
|
||||
@@ -47,7 +48,7 @@ router = APIRouter()
|
||||
@router.get("", response_model=List[schemas.UserGroup], dependencies=[Depends(has_privilege("Group.Audit"))])
|
||||
async def get_user_groups(
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> List[schemas.UserGroup]:
|
||||
) -> List[models.UserGroup]:
|
||||
"""
|
||||
Get all user groups.
|
||||
|
||||
@@ -65,7 +66,7 @@ async def get_user_groups(
|
||||
)
|
||||
async def create_user_group(
|
||||
user_group_create: schemas.UserGroupCreate, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.UserGroup:
|
||||
) -> models.UserGroup:
|
||||
"""
|
||||
Create a new user group.
|
||||
|
||||
@@ -82,7 +83,7 @@ async def create_user_group(
|
||||
async def get_user_group(
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.UserGroup:
|
||||
) -> models.UserGroup:
|
||||
"""
|
||||
Get a user group.
|
||||
|
||||
@@ -100,7 +101,7 @@ async def update_user_group(
|
||||
user_group_id: UUID,
|
||||
user_group_update: schemas.UserGroupUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.UserGroup:
|
||||
) -> Optional[models.UserGroup]:
|
||||
"""
|
||||
Update a user group.
|
||||
|
||||
@@ -148,7 +149,7 @@ async def delete_user_group(
|
||||
)
|
||||
async def get_user_group_members(
|
||||
user_group_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> List[schemas.User]:
|
||||
) -> List[models.User]:
|
||||
"""
|
||||
Get all user group members.
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ from gns3server.utils.images import (
|
||||
default_images_directory,
|
||||
get_builtin_disks,
|
||||
)
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
from gns3server.db.repositories.templates import TemplatesRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
@@ -109,7 +110,7 @@ async def create_qemu_image(
|
||||
image_path: str,
|
||||
image_data: schemas.QemuDiskImageCreate,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> schemas.Image:
|
||||
) -> models.Image:
|
||||
"""
|
||||
Create a new blank Qemu image.
|
||||
|
||||
@@ -158,7 +159,7 @@ async def get_images(
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
image_type: Optional[schemas.ImageType] = None,
|
||||
availability: Optional[Literal["unknown", "available", "missing", "unavailable", "invalid"]] = None,
|
||||
) -> List[schemas.Image]:
|
||||
) -> List[models.Image]:
|
||||
"""
|
||||
Return all images.
|
||||
|
||||
@@ -182,7 +183,7 @@ async def upload_image(
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
install_appliances: Optional[bool] = False,
|
||||
) -> schemas.Image:
|
||||
) -> models.Image:
|
||||
"""
|
||||
Upload an image.
|
||||
|
||||
@@ -306,7 +307,7 @@ async def install_images(
|
||||
async def get_image(
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> schemas.Image:
|
||||
) -> models.Image:
|
||||
"""
|
||||
Return an image.
|
||||
|
||||
@@ -349,7 +350,7 @@ async def delete_image(
|
||||
|
||||
templates = await images_repo.get_image_templates(image.image_id)
|
||||
if templates:
|
||||
template_names = ", ".join([template.name for template in templates])
|
||||
template_names = ", ".join([str(template.name) for template in templates])
|
||||
raise ControllerError(f"Image '{image_path}' is used by one or more templates: {template_names}")
|
||||
|
||||
project_names = Controller.instance().find_projects_using_image(image.filename)
|
||||
|
||||
@@ -26,7 +26,7 @@ import aiohttp
|
||||
from fastapi import APIRouter, Depends, Request, status, WebSocket
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from typing import List, Union
|
||||
from typing import Any, List, Union
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from gns3server.controller import Controller
|
||||
@@ -46,7 +46,9 @@ import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or link"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or link"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -91,7 +93,7 @@ async def get_links(project_id: UUID) -> List[schemas.Link]:
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))],
|
||||
)
|
||||
async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schemas.Link:
|
||||
async def create_link(project_id: UUID, link_create: schemas.LinkCreate) -> schemas.Link:
|
||||
"""
|
||||
Create a new link.
|
||||
|
||||
@@ -100,7 +102,7 @@ async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schema
|
||||
|
||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||
link = await project.add_link()
|
||||
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
||||
link_data = jsonable_encoder(link_create, exclude_unset=True)
|
||||
if "filters" in link_data:
|
||||
await link.update_filters(link_data["filters"])
|
||||
if "link_style" in link_data:
|
||||
@@ -156,14 +158,14 @@ async def get_link(link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))],
|
||||
)
|
||||
async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
async def update_link(link_update: schemas.LinkUpdate, link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"""
|
||||
Update a link.
|
||||
|
||||
Required privilege: Link.Modify
|
||||
"""
|
||||
|
||||
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
||||
link_data = jsonable_encoder(link_update, exclude_unset=True)
|
||||
if "filters" in link_data:
|
||||
await link.update_filters(link_data["filters"])
|
||||
if "link_style" in link_data:
|
||||
@@ -293,7 +295,7 @@ async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> Strea
|
||||
pcap_streaming_url = link.pcap_streaming_url()
|
||||
headers = multidict.MultiDict(request.headers)
|
||||
headers["Host"] = compute.host
|
||||
headers["Router-Host"] = request.client.host
|
||||
headers["Router-Host"] = request.client.host if request.client else ""
|
||||
body = await request.body()
|
||||
|
||||
async def compute_pcap_stream():
|
||||
@@ -487,7 +489,7 @@ async def update_marker(marker_name: str, marker_data: schemas.MarkerUpdate, lin
|
||||
response_model=Union[schemas.UDPPortInfo, schemas.EthernetPortInfo],
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_iface(link: Link = Depends(dep_link)) -> Union[schemas.UDPPortInfo, schemas.EthernetPortInfo]:
|
||||
async def get_iface(link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Return iface info for links to Cloud or NAT devices.
|
||||
|
||||
|
||||
@@ -206,7 +206,11 @@ async def create_user_llm_model_config(
|
||||
# Extract config fields (excluding table-level fields)
|
||||
config_fields = config_create.model_dump(exclude={"name", "model_type", "is_default"})
|
||||
new_config = await llm_repo.create_user_config(
|
||||
user_id, config_create.name, config_create.model_type, config_fields, is_default=config_create.is_default
|
||||
user_id,
|
||||
config_create.name,
|
||||
config_create.model_type,
|
||||
config_fields,
|
||||
is_default=bool(config_create.is_default),
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -346,6 +350,10 @@ async def set_user_default_llm_model_config(
|
||||
|
||||
# Get the updated config
|
||||
config = await llm_repo.get_user_config(config_id)
|
||||
if config is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
return schemas.LLMModelConfigResponse(
|
||||
config_id=config.config_id,
|
||||
name=config.name,
|
||||
@@ -506,7 +514,11 @@ async def create_group_llm_model_config(
|
||||
# Extract config fields (excluding table-level fields)
|
||||
config_fields = config_create.model_dump(exclude={"name", "model_type", "is_default"})
|
||||
new_config = await llm_repo.create_group_config(
|
||||
group_id, config_create.name, config_create.model_type, config_fields, is_default=config_create.is_default
|
||||
group_id,
|
||||
config_create.name,
|
||||
config_create.model_type,
|
||||
config_fields,
|
||||
is_default=bool(config_create.is_default),
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -646,6 +658,10 @@ async def set_group_default_llm_model_config(
|
||||
|
||||
# Get the updated config
|
||||
config = await llm_repo.get_group_config(config_id)
|
||||
if config is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
return schemas.LLMModelConfigResponse(
|
||||
config_id=config.config_id,
|
||||
name=config.name,
|
||||
|
||||
@@ -27,7 +27,7 @@ from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, Request,
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.routing import APIRoute
|
||||
from typing import List, Callable, Optional
|
||||
from typing import Any, List, Callable, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.controller import Controller
|
||||
@@ -47,7 +47,7 @@ import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
node_locks = {}
|
||||
node_locks: dict[str, dict[str, Any]] = {}
|
||||
|
||||
|
||||
class NodeConcurrency(APIRoute):
|
||||
@@ -84,7 +84,9 @@ class NodeConcurrency(APIRoute):
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}
|
||||
}
|
||||
|
||||
router = APIRouter(route_class=NodeConcurrency, responses=responses)
|
||||
|
||||
@@ -127,7 +129,7 @@ def _check_node_type(node: Node, *required_types: str) -> None:
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
|
||||
async def create_node(node_create: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
|
||||
"""
|
||||
Create a new node.
|
||||
|
||||
@@ -135,8 +137,8 @@ async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(
|
||||
"""
|
||||
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(str(node_data.compute_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
compute = controller.get_compute(str(node_create.compute_id))
|
||||
node_data = jsonable_encoder(node_create, exclude_unset=True)
|
||||
node = await project.add_node(compute, node_data.pop("name"), node_data.pop("node_id", None), **node_data)
|
||||
return node.asdict()
|
||||
|
||||
@@ -274,14 +276,14 @@ async def get_node(node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))],
|
||||
)
|
||||
async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
async def update_node(node_update: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
"""
|
||||
Update a node.
|
||||
|
||||
Required privilege: Node.Modify
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node_data = jsonable_encoder(node_update, exclude_unset=True)
|
||||
|
||||
# Ignore these because we only use them when creating a node
|
||||
node_data.pop("node_id", None)
|
||||
@@ -654,9 +656,8 @@ async def ws_console(
|
||||
return
|
||||
|
||||
compute = node.compute
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
|
||||
)
|
||||
client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown"
|
||||
log.info(f"New client {client} has connected to controller console WebSocket")
|
||||
|
||||
compute_host = compute.host
|
||||
try:
|
||||
@@ -690,9 +691,7 @@ async def ws_console(
|
||||
await ws_console_compute.send_bytes(msg["bytes"])
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller console WebSocket"
|
||||
)
|
||||
log.info(f"Client {client} has disconnected from controller console WebSocket")
|
||||
|
||||
async def ws_send(ws_console_compute):
|
||||
"""
|
||||
@@ -709,10 +708,7 @@ async def ws_console(
|
||||
break
|
||||
except WebSocketDisconnect:
|
||||
# the client disconnected while the compute was still streaming console output
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" console WebSocket"
|
||||
)
|
||||
log.info(f"Client {client} has disconnected from controller console WebSocket")
|
||||
|
||||
try:
|
||||
# forward WebSocket data in both directions between the client and the compute console WebSocket
|
||||
@@ -763,9 +759,8 @@ async def vnc_console(
|
||||
return
|
||||
|
||||
compute = node.compute
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller VNC console WebSocket"
|
||||
)
|
||||
client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown"
|
||||
log.info(f"New client {client} has connected to controller VNC console WebSocket")
|
||||
|
||||
compute_host = compute.host
|
||||
try:
|
||||
@@ -796,10 +791,7 @@ async def vnc_console(
|
||||
await vnc_console_compute.send_bytes(data)
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" VNC console WebSocket"
|
||||
)
|
||||
log.info(f"Client {client} has disconnected from controller VNC console WebSocket")
|
||||
|
||||
async def vnc_send(vnc_console_compute):
|
||||
"""
|
||||
@@ -814,10 +806,7 @@ async def vnc_console(
|
||||
break
|
||||
except WebSocketDisconnect:
|
||||
# the client disconnected while the compute was still streaming VNC console output
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" VNC console WebSocket"
|
||||
)
|
||||
log.info(f"Client {client} has disconnected from controller VNC console WebSocket")
|
||||
|
||||
try:
|
||||
# forward WebSocket data in both directions between the client and the compute VNC console WebSocket
|
||||
|
||||
@@ -21,12 +21,13 @@ API routes for resource pools.
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerBadRequestError, ControllerNotFoundError
|
||||
|
||||
from gns3server.controller import Controller
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.db.repositories.pools import ResourcePoolsRepository
|
||||
|
||||
@@ -43,7 +44,7 @@ router = APIRouter()
|
||||
@router.get("", response_model=List[schemas.ResourcePool], dependencies=[Depends(has_privilege("Pool.Audit"))])
|
||||
async def get_resource_pools(
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> List[schemas.ResourcePool]:
|
||||
) -> List[models.ResourcePool]:
|
||||
"""
|
||||
Get all resource pools.
|
||||
|
||||
@@ -62,7 +63,7 @@ async def get_resource_pools(
|
||||
async def create_resource_pool(
|
||||
resource_pool_create: schemas.ResourcePoolCreate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> schemas.ResourcePool:
|
||||
) -> models.ResourcePool:
|
||||
"""
|
||||
Create a new resource pool
|
||||
|
||||
@@ -80,7 +81,7 @@ async def create_resource_pool(
|
||||
)
|
||||
async def get_resource_pool(
|
||||
resource_pool_id: UUID, pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
) -> schemas.ResourcePool:
|
||||
) -> models.ResourcePool:
|
||||
"""
|
||||
Get a resource pool.
|
||||
|
||||
@@ -100,7 +101,7 @@ async def update_resource_pool(
|
||||
resource_pool_id: UUID,
|
||||
resource_pool_update: schemas.ResourcePoolUpdate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> schemas.ResourcePool:
|
||||
) -> Optional[models.ResourcePool]:
|
||||
"""
|
||||
Update a resource pool.
|
||||
|
||||
@@ -176,7 +177,7 @@ async def delete_resource_pool(
|
||||
async def get_pool_resources(
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> List[schemas.Resource]:
|
||||
) -> List[models.Resource]:
|
||||
"""
|
||||
Get all resource in a pool.
|
||||
|
||||
@@ -215,13 +216,13 @@ async def add_resource_to_pool(
|
||||
# we only support projects in resource pools for now
|
||||
project = Controller.instance().get_project(str(resource_id))
|
||||
|
||||
resource = await pools_repo.get_resource(resource_id)
|
||||
if not resource:
|
||||
db_resource = await pools_repo.get_resource(resource_id)
|
||||
if not db_resource:
|
||||
# the resource is not in the database yet, create it
|
||||
resource_create = schemas.ResourceCreate(resource_id=resource_id, resource_type="project", name=project.name)
|
||||
resource = await pools_repo.create_resource(resource_create)
|
||||
db_resource = await pools_repo.create_resource(resource_create)
|
||||
|
||||
await pools_repo.add_resource_to_pool(resource_pool_id, resource)
|
||||
await pools_repo.add_resource_to_pool(resource_pool_id, db_resource)
|
||||
|
||||
|
||||
@router.delete(
|
||||
|
||||
@@ -16,6 +16,7 @@ API route for privileges
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from .dependencies.database import get_repository
|
||||
from fastapi import APIRouter, Depends
|
||||
@@ -33,7 +34,7 @@ router = APIRouter()
|
||||
)
|
||||
async def get_privileges(
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> List[schemas.Privilege]:
|
||||
) -> List[models.Privilege]:
|
||||
"""
|
||||
Get all privileges.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from fastapi import APIRouter, Depends, Request, Body, HTTPException, status, We
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@@ -57,7 +57,9 @@ from .dependencies.rbac import has_privilege, has_privilege_on_websocket
|
||||
from .dependencies.authentication import get_current_active_user
|
||||
from .dependencies.database import get_repository
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -636,8 +638,8 @@ async def export_project(
|
||||
include_images: bool = False,
|
||||
reset_mac_addresses: bool = False,
|
||||
keep_compute_ids: bool = False,
|
||||
compression: schemas.ProjectCompression = "zstd",
|
||||
compression_level: int = None,
|
||||
compression: schemas.ProjectCompression = schemas.ProjectCompression.zstd,
|
||||
compression_level: Optional[int] = None,
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Export a project as a portable archive.
|
||||
@@ -650,19 +652,19 @@ async def export_project(
|
||||
|
||||
compression_query = compression.lower()
|
||||
if compression_query == "zip":
|
||||
compression = zipfile.ZIP_DEFLATED
|
||||
zip_compression = zipfile.ZIP_DEFLATED
|
||||
if compression_level is not None and (compression_level < 0 or compression_level > 9):
|
||||
raise ControllerBadRequestError("Compression level must be between 0 and 9 for ZIP compression")
|
||||
elif compression_query == "none":
|
||||
compression = zipfile.ZIP_STORED
|
||||
zip_compression = zipfile.ZIP_STORED
|
||||
elif compression_query == "bzip2":
|
||||
compression = zipfile.ZIP_BZIP2
|
||||
zip_compression = zipfile.ZIP_BZIP2
|
||||
if compression_level is not None and (compression_level < 1 or compression_level > 9):
|
||||
raise ControllerBadRequestError("Compression level must be between 1 and 9 for BZIP2 compression")
|
||||
elif compression_query == "lzma":
|
||||
compression = zipfile.ZIP_LZMA
|
||||
zip_compression = zipfile.ZIP_LZMA
|
||||
elif compression_query == "zstd":
|
||||
compression = zipfile.ZIP_ZSTANDARD
|
||||
zip_compression = zipfile.ZIP_ZSTANDARD
|
||||
if compression_level is not None and (compression_level < 1 or compression_level > 22):
|
||||
raise ControllerBadRequestError("Compression level must be between 1 and 22 for Zstandard compression")
|
||||
|
||||
@@ -681,7 +683,7 @@ async def export_project(
|
||||
f"Exporting project '{project.name}' with '{compression_query}' compression (level {compression_level})"
|
||||
)
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
with aiozipstream.ZipFile(compression=compression, compresslevel=compression_level) as zstream:
|
||||
with aiozipstream.ZipFile(compression=zip_compression, compresslevel=compression_level) as zstream:
|
||||
await export_controller_project(
|
||||
zstream,
|
||||
project,
|
||||
|
||||
@@ -21,7 +21,7 @@ API routes for roles.
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
@@ -31,6 +31,7 @@ from gns3server.controller.controller_error import (
|
||||
ControllerForbiddenError,
|
||||
)
|
||||
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from .dependencies.database import get_repository
|
||||
from .dependencies.rbac import has_privilege
|
||||
@@ -43,7 +44,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.Role], dependencies=[Depends(has_privilege("Role.Audit"))])
|
||||
async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[schemas.Role]:
|
||||
async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[models.Role]:
|
||||
"""
|
||||
Get all roles.
|
||||
|
||||
@@ -61,7 +62,7 @@ async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepos
|
||||
)
|
||||
async def create_role(
|
||||
role_create: schemas.RoleCreate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.Role:
|
||||
) -> Optional[models.Role]:
|
||||
"""
|
||||
Create a new role.
|
||||
|
||||
@@ -78,7 +79,7 @@ async def create_role(
|
||||
async def get_role(
|
||||
role_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.Role:
|
||||
) -> models.Role:
|
||||
"""
|
||||
Get a role.
|
||||
|
||||
@@ -94,7 +95,7 @@ async def get_role(
|
||||
@router.put("/{role_id}", response_model=schemas.Role, dependencies=[Depends(has_privilege("Role.Modify"))])
|
||||
async def update_role(
|
||||
role_id: UUID, role_update: schemas.RoleUpdate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.Role:
|
||||
) -> Optional[models.Role]:
|
||||
"""
|
||||
Update a role.
|
||||
|
||||
@@ -142,7 +143,7 @@ async def delete_role(
|
||||
)
|
||||
async def get_role_privileges(
|
||||
role_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> List[schemas.Privilege]:
|
||||
) -> List[models.Privilege]:
|
||||
"""
|
||||
Get all role privileges.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from typing import List
|
||||
from typing import Any, List
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.controller.project import Project
|
||||
@@ -35,7 +35,9 @@ from gns3server.controller import Controller
|
||||
from .dependencies.database import get_repository
|
||||
from .dependencies.rbac import has_privilege
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from fastapi import APIRouter, Request, HTTPException, Depends, Response, status, Query
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@@ -43,7 +43,9 @@ from .dependencies.authentication import get_current_active_user
|
||||
from .dependencies.rbac import has_privilege
|
||||
from .dependencies.database import get_repository
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find template"}}
|
||||
responses: dict[int | str, dict[str, Any]] = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find template"}
|
||||
}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@@ -57,7 +59,7 @@ router = APIRouter(responses=responses)
|
||||
async def create_template(
|
||||
template_create: schemas.TemplateCreate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> schemas.Template:
|
||||
) -> dict:
|
||||
"""
|
||||
Create a new template.
|
||||
|
||||
@@ -80,7 +82,7 @@ async def get_template(
|
||||
request: Request,
|
||||
response: Response,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> schemas.Template:
|
||||
) -> Union[dict, Response]:
|
||||
"""
|
||||
Return a template.
|
||||
|
||||
@@ -108,7 +110,7 @@ async def update_template(
|
||||
template_id: UUID,
|
||||
template_update: schemas.TemplateUpdate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> schemas.Template:
|
||||
) -> dict:
|
||||
"""
|
||||
Update a template.
|
||||
|
||||
@@ -125,7 +127,7 @@ async def delete_template(
|
||||
template_id: UUID,
|
||||
prune_images: Optional[bool] = False,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
images_repo: RbacRepository = Depends(get_repository(ImagesRepository)),
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
@@ -155,7 +157,7 @@ async def delete_template(
|
||||
if str(template.template_id) != str(template_id)
|
||||
]
|
||||
if other_templates:
|
||||
template_names = ", ".join([template.name for template in other_templates])
|
||||
template_names = ", ".join([str(template.name) for template in other_templates])
|
||||
raise ControllerError(f"Image '{image.path}' is used by one or more templates: {template_names}")
|
||||
|
||||
if referenced_filenames is None:
|
||||
@@ -193,7 +195,7 @@ async def get_templates(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
tags: Optional[List[str]] = Query(None, description="Filter by tags (e.g. tags=vendor:cisco&tags=model:7200)"),
|
||||
) -> List[schemas.Template]:
|
||||
) -> List[dict]:
|
||||
"""
|
||||
Return all templates.
|
||||
|
||||
@@ -244,7 +246,7 @@ async def get_templates(
|
||||
)
|
||||
async def duplicate_template(
|
||||
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
) -> schemas.Template:
|
||||
) -> dict:
|
||||
"""
|
||||
Duplicate a template.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ API routes for users.
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
@@ -32,6 +32,7 @@ from gns3server.controller.controller_error import (
|
||||
ControllerForbiddenError,
|
||||
)
|
||||
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.services import auth_service
|
||||
@@ -168,7 +169,7 @@ async def update_logged_in_user(
|
||||
user_update: schemas.LoggedInUserUpdate,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.User:
|
||||
) -> Optional[models.User]:
|
||||
"""
|
||||
Update the current active user.
|
||||
"""
|
||||
@@ -180,7 +181,7 @@ async def update_logged_in_user(
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.User], dependencies=[Depends(has_privilege("User.Audit"))])
|
||||
async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> List[schemas.User]:
|
||||
async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> List[models.User]:
|
||||
"""
|
||||
Get all users.
|
||||
|
||||
@@ -198,7 +199,7 @@ async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRe
|
||||
)
|
||||
async def create_user(
|
||||
user_create: schemas.UserCreate, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.User:
|
||||
) -> models.User:
|
||||
"""
|
||||
Create a new user.
|
||||
|
||||
@@ -218,7 +219,7 @@ async def create_user(
|
||||
async def get_user(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.User:
|
||||
) -> models.User:
|
||||
"""
|
||||
Get a user.
|
||||
|
||||
@@ -236,7 +237,7 @@ async def update_user(
|
||||
user_id: UUID,
|
||||
user_update: schemas.UserUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.User:
|
||||
) -> Optional[models.User]:
|
||||
"""
|
||||
Update a user.
|
||||
|
||||
@@ -287,7 +288,7 @@ async def delete_user(
|
||||
)
|
||||
async def get_user_memberships(
|
||||
user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> List[schemas.UserGroup]:
|
||||
) -> List[models.UserGroup]:
|
||||
"""
|
||||
Get user memberships.
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
FastAPI app
|
||||
"""
|
||||
|
||||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI, Request, HTTPException, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
@@ -118,7 +120,7 @@ def handle_exit(*args, **kwargs):
|
||||
unicorn_exit_handler(*args, **kwargs)
|
||||
|
||||
|
||||
UvicornServer.handle_exit = handle_exit
|
||||
UvicornServer.handle_exit = handle_exit # type: ignore[method-assign]
|
||||
|
||||
|
||||
# Configure self-hosting JavaScript and CSS for docs
|
||||
@@ -134,7 +136,7 @@ async def custom_swagger_ui_html():
|
||||
)
|
||||
|
||||
|
||||
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
|
||||
@app.get(cast(str, app.swagger_ui_oauth2_redirect_url), include_in_schema=False)
|
||||
async def swagger_ui_redirect():
|
||||
return get_swagger_ui_oauth2_redirect_html()
|
||||
|
||||
|
||||
Reference in new issue
Block a user