mirror of
https://github.com/GNS3/gns3-server
synced 2025-01-08 07:01:02 +00:00
Merge remote-tracking branch 'origin/3.0' into gh-pages
This commit is contained in:
commit
967f3c7d20
22
CHANGELOG
22
CHANGELOG
@ -1,5 +1,27 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 2.2.23 05/08/2021
|
||||||
|
|
||||||
|
* Release web UI 2.2.23
|
||||||
|
* Fix hostname inconsistencies during script execution
|
||||||
|
* Add option `--without-kvm`
|
||||||
|
* Add a `reload` server endpoint. Fixes #1926
|
||||||
|
* Handle -no-kvm param deprecated in Qemu >= v5.2
|
||||||
|
* Fix binary websocket access to the console
|
||||||
|
* Change how to generate random MAC addresses
|
||||||
|
* setup.py: prevent installing tests directory
|
||||||
|
* Support cloning of encrypted qcow2 base image files
|
||||||
|
* Fix VMware VM support on Linux and Windows. Fixes #1919
|
||||||
|
|
||||||
|
## 2.2.22 10/06/2021
|
||||||
|
|
||||||
|
* Fix VMware support on macOS BigSur
|
||||||
|
* Link style support. Fixes https://github.com/GNS3/gns3-gui/issues/2461
|
||||||
|
* Release web UI version 2.2.22
|
||||||
|
* Preserve auto_start/auto_open/auto_close when restoring snapshot
|
||||||
|
* Fix uBridge errors for cloud nodes not visible in logs. Fixes #1895
|
||||||
|
* Prevent directory traversal. Fixes #1894
|
||||||
|
|
||||||
## 2.2.21 10/05/2021
|
## 2.2.21 10/05/2021
|
||||||
|
|
||||||
* Release Web-Ui v2.2.21
|
* Release Web-Ui v2.2.21
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
pytest==6.2.4
|
pytest==6.2.4
|
||||||
flake8==3.9.1
|
flake8==3.9.2
|
||||||
pytest-timeout==1.4.2
|
pytest-timeout==1.4.2
|
||||||
pytest-asyncio==0.15.1
|
pytest-asyncio==0.15.1
|
||||||
requests==2.25.1
|
requests==2.26.0
|
||||||
httpx==0.18.1
|
httpx==0.18.2
|
||||||
|
@ -20,7 +20,7 @@ API routes for ATM switch nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Body, Path, status
|
from fastapi import APIRouter, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -109,42 +109,43 @@ async def update_atm_switch(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> None:
|
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an ATM switch node.
|
Delete an ATM switch node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def start_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
def start_atm_switch(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start an ATM switch node.
|
Start an ATM switch node.
|
||||||
This endpoint results in no action since ATM switch nodes are always on.
|
This endpoint results in no action since ATM switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop an ATM switch node.
|
Stop an ATM switch node.
|
||||||
This endpoint results in no action since ATM switch nodes are always on.
|
This endpoint results in no action since ATM switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend an ATM switch node.
|
Suspend an ATM switch node.
|
||||||
This endpoint results in no action since ATM switch nodes are always on.
|
This endpoint results in no action since ATM switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -170,7 +171,7 @@ async def create_nio(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)) -> None:
|
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove a NIO (Network Input/Output) from the node.
|
Remove a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
@ -178,6 +179,7 @@ async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = De
|
|||||||
|
|
||||||
nio = await node.remove_nio(port_number)
|
nio = await node.remove_nio(port_number)
|
||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -207,13 +209,14 @@ async def stop_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: ATMSwitch = Depends(dep_node)
|
node: ATMSwitch = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -20,7 +20,7 @@ API routes for cloud nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Path, status
|
from fastapi import APIRouter, Depends, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -99,41 +99,43 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_cloud(node: Cloud = Depends(dep_node)) -> None:
|
async def delete_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a cloud node.
|
Delete a cloud node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Builtin.instance().delete_node(node.id)
|
await Builtin.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_cloud(node: Cloud = Depends(dep_node)) -> None:
|
async def start_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a cloud node.
|
Start a cloud node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_cloud(node: Cloud = Depends(dep_node)) -> None:
|
async def stop_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a cloud node.
|
Stop a cloud node.
|
||||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None:
|
async def suspend_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a cloud node.
|
Suspend a cloud node.
|
||||||
This endpoint results in no action since cloud nodes cannot be suspended.
|
This endpoint results in no action since cloud nodes cannot be suspended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -188,13 +190,14 @@ async def delete_cloud_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: Cloud = Depends(dep_node)
|
node: Cloud = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove a NIO (Network Input/Output) from the node.
|
Remove a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the cloud is always 0.
|
The adapter number on the cloud is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.remove_nio(port_number)
|
await node.remove_nio(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -223,13 +226,14 @@ async def stop_cloud_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: Cloud = Depends(dep_node)
|
node: Cloud = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the cloud is always 0.
|
The adapter number on the cloud is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap")
|
||||||
|
@ -34,7 +34,7 @@ from gns3server.compute.virtualbox import VirtualBox
|
|||||||
from gns3server.compute.vmware import VMware
|
from gns3server.compute.vmware import VMware
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Body, status
|
from fastapi import APIRouter, HTTPException, Body, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
@ -150,7 +150,7 @@ async def get_qemu_capabilities() -> dict:
|
|||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}},
|
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}},
|
||||||
)
|
)
|
||||||
async def create_qemu_image(image_data: schemas.QemuImageCreate) -> None:
|
async def create_qemu_image(image_data: schemas.QemuImageCreate) -> Response:
|
||||||
"""
|
"""
|
||||||
Create a Qemu image.
|
Create a Qemu image.
|
||||||
"""
|
"""
|
||||||
@ -163,13 +163,15 @@ async def create_qemu_image(image_data: schemas.QemuImageCreate) -> None:
|
|||||||
image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True)
|
image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/qemu/img",
|
"/qemu/img",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}},
|
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}},
|
||||||
)
|
)
|
||||||
async def update_qemu_image(image_data: schemas.QemuImageUpdate) -> None:
|
async def update_qemu_image(image_data: schemas.QemuImageUpdate) -> Response:
|
||||||
"""
|
"""
|
||||||
Update a Qemu image.
|
Update a Qemu image.
|
||||||
"""
|
"""
|
||||||
@ -181,6 +183,8 @@ async def update_qemu_image(image_data: schemas.QemuImageUpdate) -> None:
|
|||||||
if image_data.extend:
|
if image_data.extend:
|
||||||
await Qemu.instance().resize_disk(image_data.qemu_img, image_data.path, image_data.extend)
|
await Qemu.instance().resize_disk(image_data.qemu_img, image_data.path, image_data.extend)
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/virtualbox/vms", response_model=List[dict])
|
@router.get("/virtualbox/vms", response_model=List[dict])
|
||||||
async def get_virtualbox_vms() -> List[dict]:
|
async def get_virtualbox_vms() -> List[dict]:
|
||||||
|
@ -20,7 +20,7 @@ API routes for Docker nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -132,66 +132,73 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a Docker node.
|
Start a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a Docker node.
|
Stop a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a Docker node.
|
Suspend a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.pause()
|
await node.pause()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a Docker node.
|
Reload a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.restart()
|
await node.restart()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Pause a Docker node.
|
Pause a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.pause()
|
await node.pause()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Unpause a Docker node.
|
Unpause a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.unpause()
|
await node.unpause()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a Docker node.
|
Delete a Docker node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.delete()
|
await node.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
|
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
|
||||||
@ -250,13 +257,14 @@ async def delete_docker_node_nio(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: DockerVM = Depends(dep_node)
|
node: DockerVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The port number on the Docker node is always 0.
|
The port number on the Docker node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -284,13 +292,14 @@ async def stop_docker_node_capture(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: DockerVM = Depends(dep_node)
|
node: DockerVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The port number on the Docker node is always 0.
|
The port number on the Docker node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -319,6 +328,7 @@ async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
|
async def reset_console(node: DockerVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -21,7 +21,7 @@ API routes for Dynamips nodes.
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, status
|
from fastapi import APIRouter, WebSocket, Depends, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -105,16 +105,17 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
async def delete_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a Dynamips router.
|
Delete a Dynamips router.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_router(node: Router = Depends(dep_node)) -> None:
|
async def start_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a Dynamips router.
|
Start a Dynamips router.
|
||||||
"""
|
"""
|
||||||
@ -124,39 +125,44 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
pass
|
pass
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
async def stop_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a Dynamips router.
|
Stop a Dynamips router.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
async def suspend_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resume_router(node: Router = Depends(dep_node)) -> None:
|
async def resume_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Resume a suspended Dynamips router.
|
Resume a suspended Dynamips router.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.resume()
|
await node.resume()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_router(node: Router = Depends(dep_node)) -> None:
|
async def reload_router(node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a suspended Dynamips router.
|
Reload a suspended Dynamips router.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -202,13 +208,14 @@ async def update_nio(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
nio = await node.slot_remove_nio_binding(adapter_number, port_number)
|
nio = await node.slot_remove_nio_binding(adapter_number, port_number)
|
||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -240,12 +247,13 @@ async def start_capture(
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||||
)
|
)
|
||||||
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number, port_number)
|
await node.stop_capture(adapter_number, port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -303,6 +311,7 @@ async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) ->
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: Router = Depends(dep_node)) -> None:
|
async def reset_console(node: Router = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -20,7 +20,7 @@ API routes for Ethernet hub nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Body, Path, status
|
from fastapi import APIRouter, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -108,42 +108,43 @@ async def update_ethernet_hub(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an Ethernet hub.
|
Delete an Ethernet hub.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start an Ethernet hub.
|
Start an Ethernet hub.
|
||||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop an Ethernet hub.
|
Stop an Ethernet hub.
|
||||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend an Ethernet hub.
|
Suspend an Ethernet hub.
|
||||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -174,7 +175,7 @@ async def delete_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: EthernetHub = Depends(dep_node)
|
node: EthernetHub = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the hub is always 0.
|
The adapter number on the hub is always 0.
|
||||||
@ -182,6 +183,7 @@ async def delete_nio(
|
|||||||
|
|
||||||
nio = await node.remove_nio(port_number)
|
nio = await node.remove_nio(port_number)
|
||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -210,13 +212,14 @@ async def stop_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: EthernetHub = Depends(dep_node)
|
node: EthernetHub = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the hub is always 0.
|
The adapter number on the hub is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -20,7 +20,7 @@ API routes for Ethernet switch nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Body, Path, status
|
from fastapi import APIRouter, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -112,42 +112,43 @@ async def update_ethernet_switch(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an Ethernet switch.
|
Delete an Ethernet switch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start an Ethernet switch.
|
Start an Ethernet switch.
|
||||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop an Ethernet switch.
|
Stop an Ethernet switch.
|
||||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend an Ethernet switch.
|
Suspend an Ethernet switch.
|
||||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -174,7 +175,7 @@ async def delete_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: EthernetSwitch = Depends(dep_node)
|
node: EthernetSwitch = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
@ -182,6 +183,7 @@ async def delete_nio(
|
|||||||
|
|
||||||
nio = await node.remove_nio(port_number)
|
nio = await node.remove_nio(port_number)
|
||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -210,13 +212,14 @@ async def stop_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: EthernetSwitch = Depends(dep_node)
|
node: EthernetSwitch = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -20,7 +20,7 @@ API routes for Frame Relay switch nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Body, Path, status
|
from fastapi import APIRouter, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -112,42 +112,43 @@ async def update_frame_relay_switch(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a Frame Relay switch node.
|
Delete a Frame Relay switch node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a Frame Relay switch node.
|
Start a Frame Relay switch node.
|
||||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a Frame Relay switch node.
|
Stop a Frame Relay switch node.
|
||||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a Frame Relay switch node.
|
Suspend a Frame Relay switch node.
|
||||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -178,7 +179,7 @@ async def delete_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: FrameRelaySwitch = Depends(dep_node)
|
node: FrameRelaySwitch = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove a NIO (Network Input/Output) from the node.
|
Remove a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
@ -186,6 +187,7 @@ async def delete_nio(
|
|||||||
|
|
||||||
nio = await node.remove_nio(port_number)
|
nio = await node.remove_nio(port_number)
|
||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -214,13 +216,14 @@ async def stop_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: FrameRelaySwitch = Depends(dep_node)
|
node: FrameRelaySwitch = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the switch is always 0.
|
The adapter number on the switch is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -21,7 +21,7 @@ API routes for images.
|
|||||||
import os
|
import os
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, status, HTTPException
|
from fastapi import APIRouter, Request, status, Response, HTTPException
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -54,13 +54,14 @@ async def get_dynamips_images() -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def upload_dynamips_image(filename: str, request: Request) -> None:
|
async def upload_dynamips_image(filename: str, request: Request) -> Response:
|
||||||
"""
|
"""
|
||||||
Upload a Dynamips IOS image.
|
Upload a Dynamips IOS image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dynamips_manager = Dynamips.instance()
|
dynamips_manager = Dynamips.instance()
|
||||||
await dynamips_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
await dynamips_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dynamips/images/{filename:path}")
|
@router.get("/dynamips/images/{filename:path}")
|
||||||
@ -95,13 +96,14 @@ async def get_iou_images() -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/iou/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/iou/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def upload_iou_image(filename: str, request: Request) -> None:
|
async def upload_iou_image(filename: str, request: Request) -> Response:
|
||||||
"""
|
"""
|
||||||
Upload an IOU image.
|
Upload an IOU image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
iou_manager = IOU.instance()
|
iou_manager = IOU.instance()
|
||||||
await iou_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
await iou_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/iou/images/{filename:path}")
|
@router.get("/iou/images/{filename:path}")
|
||||||
@ -132,10 +134,11 @@ async def get_qemu_images() -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/qemu/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/qemu/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def upload_qemu_image(filename: str, request: Request) -> None:
|
async def upload_qemu_image(filename: str, request: Request) -> Response:
|
||||||
|
|
||||||
qemu_manager = Qemu.instance()
|
qemu_manager = Qemu.instance()
|
||||||
await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/qemu/images/{filename:path}")
|
@router.get("/qemu/images/{filename:path}")
|
||||||
|
@ -20,7 +20,7 @@ API routes for IOU nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -113,12 +113,13 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an IOU node.
|
Delete an IOU node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await IOU.instance().delete_node(node.id)
|
await IOU.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
|
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
|
||||||
@ -135,7 +136,7 @@ async def duplicate_iou_node(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
|
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start an IOU node.
|
Start an IOU node.
|
||||||
"""
|
"""
|
||||||
@ -146,35 +147,37 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
|||||||
setattr(node, name, value)
|
setattr(node, name, value)
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
return node.asdict()
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop an IOU node.
|
Stop an IOU node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend an IOU node.
|
Suspend an IOU node.
|
||||||
Does nothing since IOU doesn't support being suspended.
|
Does nothing since IOU doesn't support being suspended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload an IOU node.
|
Reload an IOU node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -220,12 +223,13 @@ async def update_iou_node_nio(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.adapter_remove_nio_binding(adapter_number, port_number)
|
await node.adapter_remove_nio_binding(adapter_number, port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -247,12 +251,13 @@ async def start_iou_node_capture(
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||||
)
|
)
|
||||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number, port_number)
|
await node.stop_capture(adapter_number, port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -280,6 +285,7 @@ async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> N
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
|
async def reset_console(node: IOUVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -20,7 +20,7 @@ API routes for NAT nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Path, status
|
from fastapi import APIRouter, Depends, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -94,41 +94,43 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node))
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_nat_node(node: Nat = Depends(dep_node)) -> None:
|
async def delete_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a cloud node.
|
Delete a cloud node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Builtin.instance().delete_node(node.id)
|
await Builtin.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_nat_node(node: Nat = Depends(dep_node)) -> None:
|
async def start_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a NAT node.
|
Start a NAT node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_nat_node(node: Nat = Depends(dep_node)) -> None:
|
async def stop_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a NAT node.
|
Stop a NAT node.
|
||||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None:
|
async def suspend_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a NAT node.
|
Suspend a NAT node.
|
||||||
This endpoint results in no action since NAT nodes cannot be suspended.
|
This endpoint results in no action since NAT nodes cannot be suspended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -183,13 +185,14 @@ async def delete_nat_node_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: Nat = Depends(dep_node)
|
node: Nat = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove a NIO (Network Input/Output) from the node.
|
Remove a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the cloud is always 0.
|
The adapter number on the cloud is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.remove_nio(port_number)
|
await node.remove_nio(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -218,13 +221,14 @@ async def stop_nat_node_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: Nat = Depends(dep_node)
|
node: Nat = Depends(dep_node)
|
||||||
):
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the cloud is always 0.
|
The adapter number on the cloud is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -25,7 +25,7 @@ import logging
|
|||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -103,7 +103,7 @@ def get_compute_project(project: Project = Depends(dep_project)) -> schemas.Proj
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def close_compute_project(project: Project = Depends(dep_project)) -> None:
|
async def close_compute_project(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Close a project on the compute.
|
Close a project on the compute.
|
||||||
"""
|
"""
|
||||||
@ -118,17 +118,18 @@ async def close_compute_project(project: Project = Depends(dep_project)) -> None
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
log.warning("Skip project closing, another client is listening for project notifications")
|
log.warning("Skip project closing, another client is listening for project notifications")
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_compute_project(project: Project = Depends(dep_project)) -> None:
|
async def delete_compute_project(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete project from the compute.
|
Delete project from the compute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.delete()
|
await project.delete()
|
||||||
ProjectManager.instance().remove_project(project.id)
|
ProjectManager.instance().remove_project(project.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
# @Route.get(
|
# @Route.get(
|
||||||
# r"/projects/{project_id}/notifications",
|
# r"/projects/{project_id}/notifications",
|
||||||
@ -214,7 +215,11 @@ async def get_compute_project_file(file_path: str, project: Project = Depends(de
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
async def write_compute_project_file(
|
||||||
|
file_path: str,
|
||||||
|
request: Request,
|
||||||
|
project: Project = Depends(dep_project)
|
||||||
|
) -> Response:
|
||||||
|
|
||||||
file_path = urllib.parse.unquote(file_path)
|
file_path = urllib.parse.unquote(file_path)
|
||||||
path = os.path.normpath(file_path)
|
path = os.path.normpath(file_path)
|
||||||
@ -238,3 +243,5 @@ async def write_compute_project_file(file_path: str, request: Request, project:
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@ -20,7 +20,7 @@ API routes for Qemu nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -104,12 +104,13 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a Qemu node.
|
Delete a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await Qemu.instance().delete_node(node.id)
|
await Qemu.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
|
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
|
||||||
@ -126,61 +127,67 @@ async def duplicate_qemu_node(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)) -> None:
|
async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.resize_disk(node_data.drive_name, node_data.extend)
|
await node.resize_disk(node_data.drive_name, node_data.extend)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a Qemu node.
|
Start a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
qemu_manager = Qemu.instance()
|
qemu_manager = Qemu.instance()
|
||||||
hardware_accel = qemu_manager.config.settings.Qemu.enable_hardware_acceleration
|
hardware_accel = qemu_manager.config.settings.Qemu.enable_hardware_acceleration
|
||||||
if hardware_accel and "-no-kvm" not in node.options and "-no-hax" not in node.options:
|
if hardware_accel and "-machine accel=tcg" not in node.options:
|
||||||
pm = ProjectManager.instance()
|
pm = ProjectManager.instance()
|
||||||
if pm.check_hardware_virtualization(node) is False:
|
if pm.check_hardware_virtualization(node) is False:
|
||||||
pass # FIXME: check this
|
pass # FIXME: check this
|
||||||
# raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
# raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a Qemu node.
|
Stop a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a Qemu node.
|
Reload a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a Qemu node.
|
Suspend a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Resume a Qemu node.
|
Resume a Qemu node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.resume()
|
await node.resume()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -236,13 +243,14 @@ async def delete_qemu_node_nio(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: QemuVM = Depends(dep_node)
|
node: QemuVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The port number on the Qemu node is always 0.
|
The port number on the Qemu node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -270,13 +278,14 @@ async def stop_qemu_node_capture(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: QemuVM = Depends(dep_node)
|
node: QemuVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The port number on the Qemu node is always 0.
|
The port number on the Qemu node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -304,6 +313,7 @@ async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) ->
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
|
async def reset_console(node: QemuVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -20,7 +20,7 @@ API routes for VirtualBox nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Path, status
|
from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -137,57 +137,63 @@ async def update_virtualbox_node(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a VirtualBox node.
|
Delete a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await VirtualBox.instance().delete_node(node.id)
|
await VirtualBox.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a VirtualBox node.
|
Start a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a VirtualBox node.
|
Stop a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a VirtualBox node.
|
Suspend a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Resume a VirtualBox node.
|
Resume a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.resume()
|
await node.resume()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a VirtualBox node.
|
Reload a VirtualBox node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -243,13 +249,14 @@ async def delete_virtualbox_node_nio(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: VirtualBoxVM = Depends(dep_node)
|
node: VirtualBoxVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The port number on the VirtualBox node is always 0.
|
The port number on the VirtualBox node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -277,13 +284,14 @@ async def stop_virtualbox_node_capture(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: VirtualBoxVM = Depends(dep_node)
|
node: VirtualBoxVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The port number on the VirtualBox node is always 0.
|
The port number on the VirtualBox node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -312,6 +320,7 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -20,7 +20,7 @@ API routes for VMware nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Path, status
|
from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -103,57 +103,63 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a VMware node.
|
Delete a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await VMware.instance().delete_node(node.id)
|
await VMware.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a VMware node.
|
Start a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a VMware node.
|
Stop a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a VMware node.
|
Suspend a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Resume a VMware node.
|
Resume a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.resume()
|
await node.resume()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a VMware node.
|
Reload a VMware node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -207,13 +213,14 @@ async def delete_vmware_node_nio(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: VMwareVM = Depends(dep_node)
|
node: VMwareVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The port number on the VMware node is always 0.
|
The port number on the VMware node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -241,13 +248,14 @@ async def stop_vmware_node_capture(
|
|||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
node: VMwareVM = Depends(dep_node)
|
node: VMwareVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The port number on the VMware node is always 0.
|
The port number on the VMware node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
@ -289,6 +297,7 @@ async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
|
async def reset_console(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -20,7 +20,7 @@ API routes for VPCS nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -93,12 +93,13 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a VPCS node.
|
Delete a VPCS node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await VPCS.instance().delete_node(node.id)
|
await VPCS.instance().delete_node(node.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
|
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
|
||||||
@ -114,40 +115,43 @@ async def duplicate_vpcs_node(
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a VPCS node.
|
Start a VPCS node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start()
|
await node.start()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a VPCS node.
|
Stop a VPCS node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a VPCS node.
|
Suspend a VPCS node.
|
||||||
Does nothing, suspend is not supported by VPCS.
|
Does nothing, suspend is not supported by VPCS.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a VPCS node.
|
Reload a VPCS node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -202,13 +206,14 @@ async def delete_vpcs_node_nio(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: VPCSVM = Depends(dep_node)
|
node: VPCSVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
The adapter number on the VPCS node is always 0.
|
The adapter number on the VPCS node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.port_remove_nio_binding(port_number)
|
await node.port_remove_nio_binding(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||||
@ -237,19 +242,21 @@ async def stop_vpcs_node_capture(
|
|||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
port_number: int,
|
port_number: int,
|
||||||
node: VPCSVM = Depends(dep_node)
|
node: VPCSVM = Depends(dep_node)
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a packet capture on the node.
|
Stop a packet capture on the node.
|
||||||
The adapter number on the VPCS node is always 0.
|
The adapter number on the VPCS node is always 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
|
async def reset_console(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||||
|
@ -95,7 +95,6 @@ router.include_router(
|
|||||||
|
|
||||||
router.include_router(
|
router.include_router(
|
||||||
symbols.router,
|
symbols.router,
|
||||||
dependencies=[Depends(get_current_active_user)],
|
|
||||||
prefix="/symbols", tags=["Symbols"]
|
prefix="/symbols", tags=["Symbols"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
API routes for computes.
|
API routes for computes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@ -93,12 +93,13 @@ async def update_compute(
|
|||||||
@router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_compute(
|
async def delete_compute(
|
||||||
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a compute from the controller.
|
Delete a compute from the controller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await ComputesService(computes_repo).delete_compute(compute_id)
|
await ComputesService(computes_repo).delete_compute(compute_id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{compute_id}/{emulator}/images")
|
@router.get("/{compute_id}/{emulator}/images")
|
||||||
|
@ -18,7 +18,7 @@ import asyncio
|
|||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -67,15 +67,29 @@ def check_version(version: schemas.Version) -> dict:
|
|||||||
return {"version": __version__}
|
return {"version": __version__}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/reload",
|
||||||
|
dependencies=[Depends(get_current_active_user)],
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
)
|
||||||
|
async def reload() -> Response:
|
||||||
|
"""
|
||||||
|
Reload the controller
|
||||||
|
"""
|
||||||
|
|
||||||
|
await Controller.instance().reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/shutdown",
|
"/shutdown",
|
||||||
dependencies=[Depends(get_current_active_user)],
|
dependencies=[Depends(get_current_active_user)],
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}},
|
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}},
|
||||||
)
|
)
|
||||||
async def shutdown() -> None:
|
async def shutdown() -> Response:
|
||||||
"""
|
"""
|
||||||
Shutdown the local server
|
Shutdown the server
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if Config.instance().settings.Server.local is False:
|
if Config.instance().settings.Server.local is False:
|
||||||
@ -101,6 +115,7 @@ async def shutdown() -> None:
|
|||||||
|
|
||||||
# then shutdown the server itself
|
# then shutdown the server itself
|
||||||
os.kill(os.getpid(), signal.SIGTERM)
|
os.kill(os.getpid(), signal.SIGTERM)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
API routes for drawings.
|
API routes for drawings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter, Response, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -76,10 +76,11 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_drawing(project_id: UUID, drawing_id: UUID) -> None:
|
async def delete_drawing(project_id: UUID, drawing_id: UUID) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a drawing.
|
Delete a drawing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||||
await project.delete_drawing(str(drawing_id))
|
await project.delete_drawing(str(drawing_id))
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
API routes for user groups.
|
API routes for user groups.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ async def update_user_group(
|
|||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
if user_group.builtin:
|
if user_group.is_builtin:
|
||||||
raise ControllerForbiddenError(f"Built-in user group '{user_group_id}' cannot be updated")
|
raise ControllerForbiddenError(f"Built-in user group '{user_group_id}' cannot be updated")
|
||||||
|
|
||||||
return await users_repo.update_user_group(user_group_id, user_group_update)
|
return await users_repo.update_user_group(user_group_id, user_group_update)
|
||||||
@ -112,7 +112,7 @@ async def update_user_group(
|
|||||||
async def delete_user_group(
|
async def delete_user_group(
|
||||||
user_group_id: UUID,
|
user_group_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an user group
|
Delete an user group
|
||||||
"""
|
"""
|
||||||
@ -121,13 +121,15 @@ async def delete_user_group(
|
|||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
if user_group.builtin:
|
if user_group.is_builtin:
|
||||||
raise ControllerForbiddenError(f"Built-in user group '{user_group_id}' cannot be deleted")
|
raise ControllerForbiddenError(f"Built-in user group '{user_group_id}' cannot be deleted")
|
||||||
|
|
||||||
success = await users_repo.delete_user_group(user_group_id)
|
success = await users_repo.delete_user_group(user_group_id)
|
||||||
if not success:
|
if not success:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' could not be deleted")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' could not be deleted")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{user_group_id}/members", response_model=List[schemas.User])
|
@router.get("/{user_group_id}/members", response_model=List[schemas.User])
|
||||||
async def get_user_group_members(
|
async def get_user_group_members(
|
||||||
@ -149,7 +151,7 @@ async def add_member_to_group(
|
|||||||
user_group_id: UUID,
|
user_group_id: UUID,
|
||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Add member to an user group.
|
Add member to an user group.
|
||||||
"""
|
"""
|
||||||
@ -162,6 +164,8 @@ async def add_member_to_group(
|
|||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{user_group_id}/members/{user_id}",
|
"/{user_group_id}/members/{user_id}",
|
||||||
@ -171,7 +175,7 @@ async def remove_member_from_group(
|
|||||||
user_group_id: UUID,
|
user_group_id: UUID,
|
||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove member from an user group.
|
Remove member from an user group.
|
||||||
"""
|
"""
|
||||||
@ -184,6 +188,8 @@ async def remove_member_from_group(
|
|||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{user_group_id}/roles", response_model=List[schemas.Role])
|
@router.get("/{user_group_id}/roles", response_model=List[schemas.Role])
|
||||||
async def get_user_group_roles(
|
async def get_user_group_roles(
|
||||||
@ -206,7 +212,7 @@ async def add_role_to_group(
|
|||||||
role_id: UUID,
|
role_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Add role to an user group.
|
Add role to an user group.
|
||||||
"""
|
"""
|
||||||
@ -219,6 +225,8 @@ async def add_role_to_group(
|
|||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{user_group_id}/roles/{role_id}",
|
"/{user_group_id}/roles/{role_id}",
|
||||||
@ -229,7 +237,7 @@ async def remove_role_from_group(
|
|||||||
role_id: UUID,
|
role_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove role from an user group.
|
Remove role from an user group.
|
||||||
"""
|
"""
|
||||||
@ -241,3 +249,5 @@ async def remove_role_from_group(
|
|||||||
user_group = await users_repo.remove_role_from_user_group(user_group_id, role)
|
user_group = await users_repo.remove_role_from_user_group(user_group_id, role)
|
||||||
if not user_group:
|
if not user_group:
|
||||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -21,7 +21,7 @@ API routes for links.
|
|||||||
import multidict
|
import multidict
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request, status
|
from fastapi import APIRouter, Depends, Request, Response, status
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -81,6 +81,8 @@ async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schema
|
|||||||
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
||||||
if "filters" in link_data:
|
if "filters" in link_data:
|
||||||
await link.update_filters(link_data["filters"])
|
await link.update_filters(link_data["filters"])
|
||||||
|
if "link_style" in link_data:
|
||||||
|
await link.update_link_style(link_data["link_style"])
|
||||||
if "suspend" in link_data:
|
if "suspend" in link_data:
|
||||||
await link.update_suspend(link_data["suspend"])
|
await link.update_suspend(link_data["suspend"])
|
||||||
try:
|
try:
|
||||||
@ -124,6 +126,8 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li
|
|||||||
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
link_data = jsonable_encoder(link_data, exclude_unset=True)
|
||||||
if "filters" in link_data:
|
if "filters" in link_data:
|
||||||
await link.update_filters(link_data["filters"])
|
await link.update_filters(link_data["filters"])
|
||||||
|
if "link_style" in link_data:
|
||||||
|
await link.update_link_style(link_data["link_style"])
|
||||||
if "suspend" in link_data:
|
if "suspend" in link_data:
|
||||||
await link.update_suspend(link_data["suspend"])
|
await link.update_suspend(link_data["suspend"])
|
||||||
if "nodes" in link_data:
|
if "nodes" in link_data:
|
||||||
@ -132,13 +136,14 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> None:
|
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a link.
|
Delete a link.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||||
await project.delete_link(link.id)
|
await project.delete_link(link.id)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{link_id}/reset", response_model=schemas.Link)
|
@router.post("/{link_id}/reset", response_model=schemas.Link)
|
||||||
@ -165,12 +170,13 @@ async def start_capture(capture_data: dict, link: Link = Depends(dep_link)) -> s
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_capture(link: Link = Depends(dep_link)) -> None:
|
async def stop_capture(link: Link = Depends(dep_link)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop packet capture on the link.
|
Stop packet capture on the link.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await link.stop_capture()
|
await link.stop_capture()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{link_id}/capture/stream")
|
@router.get("/{link_id}/capture/stream")
|
||||||
|
@ -130,40 +130,44 @@ async def get_nodes(project: Project = Depends(dep_project)) -> List[schemas.Nod
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_all_nodes(project: Project = Depends(dep_project)) -> None:
|
async def start_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start all nodes belonging to a given project.
|
Start all nodes belonging to a given project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.start_all()
|
await project.start_all()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_all_nodes(project: Project = Depends(dep_project)) -> None:
|
async def stop_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop all nodes belonging to a given project.
|
Stop all nodes belonging to a given project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.stop_all()
|
await project.stop_all()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None:
|
async def suspend_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend all nodes belonging to a given project.
|
Suspend all nodes belonging to a given project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.suspend_all()
|
await project.suspend_all()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_all_nodes(project: Project = Depends(dep_project)) -> None:
|
async def reload_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload all nodes belonging to a given project.
|
Reload all nodes belonging to a given project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.stop_all()
|
await project.stop_all()
|
||||||
await project.start_all()
|
await project.start_all()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.Node)
|
@router.get("/{node_id}", response_model=schemas.Node)
|
||||||
@ -197,12 +201,13 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
|
|||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}},
|
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}},
|
||||||
)
|
)
|
||||||
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)) -> None:
|
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a node from a project.
|
Delete a node from a project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.delete_node(str(node_id))
|
await project.delete_node(str(node_id))
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED)
|
@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED)
|
||||||
@ -216,39 +221,43 @@ async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Dep
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> None:
|
async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Start a node.
|
Start a node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start(data=start_data)
|
await node.start(data=start_data)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def stop_node(node: Node = Depends(dep_node)) -> None:
|
async def stop_node(node: Node = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Stop a node.
|
Stop a node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.stop()
|
await node.stop()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def suspend_node(node: Node = Depends(dep_node)) -> None:
|
async def suspend_node(node: Node = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Suspend a node.
|
Suspend a node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reload_node(node: Node = Depends(dep_node)) -> None:
|
async def reload_node(node: Node = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reload a node.
|
Reload a node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.reload()
|
await node.reload()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True)
|
@router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True)
|
||||||
@ -282,11 +291,13 @@ async def idlepc_proposals(node: Node = Depends(dep_node)) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)) -> None:
|
async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)) -> Response:
|
||||||
"""
|
"""
|
||||||
Resize a disk image.
|
Resize a disk image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.post("/resize_disk", **resize_data)
|
await node.post("/resize_disk", **resize_data)
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/files/{file_path:path}")
|
@router.get("/{node_id}/files/{file_path:path}")
|
||||||
@ -377,15 +388,17 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> No
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> None:
|
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Reset console for all nodes belonging to the project.
|
Reset console for all nodes belonging to the project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.reset_console_all()
|
await project.reset_console_all()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def console_reset(node: Node = Depends(dep_node)) -> None:
|
async def console_reset(node: Node = Depends(dep_node)) -> Response:
|
||||||
|
|
||||||
await node.post("/console/reset") # , request.json)
|
await node.post("/console/reset")
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
API routes for permissions.
|
API routes for permissions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ async def update_permission(
|
|||||||
async def delete_permission(
|
async def delete_permission(
|
||||||
permission_id: UUID,
|
permission_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a permission.
|
Delete a permission.
|
||||||
"""
|
"""
|
||||||
@ -115,3 +115,5 @@ async def delete_permission(
|
|||||||
success = await rbac_repo.delete_permission(permission_id)
|
success = await rbac_repo.delete_permission(permission_id)
|
||||||
if not success:
|
if not success:
|
||||||
raise ControllerNotFoundError(f"Permission '{permission_id}' could not be deleted")
|
raise ControllerNotFoundError(f"Permission '{permission_id}' could not be deleted")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -30,7 +30,7 @@ import logging
|
|||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request, Body, HTTPException, status, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, Depends, Request, Response, Body, HTTPException, status, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse, FileResponse
|
from fastapi.responses import StreamingResponse, FileResponse
|
||||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||||
@ -48,6 +48,8 @@ from gns3server.utils.asyncio import aiozipstream
|
|||||||
from gns3server.utils.path import is_safe_path
|
from gns3server.utils.path import is_safe_path
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
from gns3server.db.repositories.rbac import RbacRepository
|
from gns3server.db.repositories.rbac import RbacRepository
|
||||||
|
from gns3server.db.repositories.templates import TemplatesRepository
|
||||||
|
from gns3server.services.templates import TemplatesService
|
||||||
|
|
||||||
from .dependencies.authentication import get_current_active_user
|
from .dependencies.authentication import get_current_active_user
|
||||||
from .dependencies.database import get_repository
|
from .dependencies.database import get_repository
|
||||||
@ -139,7 +141,7 @@ async def update_project(
|
|||||||
async def delete_project(
|
async def delete_project(
|
||||||
project: Project = Depends(dep_project),
|
project: Project = Depends(dep_project),
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a project.
|
Delete a project.
|
||||||
"""
|
"""
|
||||||
@ -148,6 +150,7 @@ async def delete_project(
|
|||||||
await project.delete()
|
await project.delete()
|
||||||
controller.remove_project(project)
|
controller.remove_project(project)
|
||||||
await rbac_repo.delete_all_permissions_with_path(f"/projects/{project.id}")
|
await rbac_repo.delete_all_permissions_with_path(f"/projects/{project.id}")
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{project_id}/stats")
|
@router.get("/{project_id}/stats")
|
||||||
@ -164,12 +167,13 @@ def get_project_stats(project: Project = Depends(dep_project)) -> dict:
|
|||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}},
|
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}},
|
||||||
)
|
)
|
||||||
async def close_project(project: Project = Depends(dep_project)) -> None:
|
async def close_project(project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Close a project.
|
Close a project.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.close()
|
await project.close()
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -413,7 +417,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)) -> F
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post("/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Write a file from a project.
|
Write a file from a project.
|
||||||
"""
|
"""
|
||||||
@ -437,3 +441,30 @@ async def write_file(file_path: str, request: Request, project: Project = Depend
|
|||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise ControllerError(str(e))
|
raise ControllerError(str(e))
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{project_id}/templates/{template_id}",
|
||||||
|
response_model=schemas.Node,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}},
|
||||||
|
)
|
||||||
|
async def create_node_from_template(
|
||||||
|
project_id: UUID,
|
||||||
|
template_id: UUID,
|
||||||
|
template_usage: schemas.TemplateUsage,
|
||||||
|
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||||
|
) -> schemas.Node:
|
||||||
|
"""
|
||||||
|
Create a new node from a template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template = await TemplatesService(templates_repo).get_template(template_id)
|
||||||
|
controller = Controller.instance()
|
||||||
|
project = controller.get_project(str(project_id))
|
||||||
|
node = await project.add_node_from_template(
|
||||||
|
template, x=template_usage.x, y=template_usage.y, compute_id=template_usage.compute_id
|
||||||
|
)
|
||||||
|
return node.asdict()
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
API routes for roles.
|
API routes for roles.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ async def update_role(
|
|||||||
if not role:
|
if not role:
|
||||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||||
|
|
||||||
if role.builtin:
|
if role.is_builtin:
|
||||||
raise ControllerForbiddenError(f"Built-in role '{role_id}' cannot be updated")
|
raise ControllerForbiddenError(f"Built-in role '{role_id}' cannot be updated")
|
||||||
|
|
||||||
return await rbac_repo.update_role(role_id, role_update)
|
return await rbac_repo.update_role(role_id, role_update)
|
||||||
@ -105,7 +105,7 @@ async def update_role(
|
|||||||
async def delete_role(
|
async def delete_role(
|
||||||
role_id: UUID,
|
role_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a role.
|
Delete a role.
|
||||||
"""
|
"""
|
||||||
@ -114,13 +114,15 @@ async def delete_role(
|
|||||||
if not role:
|
if not role:
|
||||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||||
|
|
||||||
if role.builtin:
|
if role.is_builtin:
|
||||||
raise ControllerForbiddenError(f"Built-in role '{role_id}' cannot be deleted")
|
raise ControllerForbiddenError(f"Built-in role '{role_id}' cannot be deleted")
|
||||||
|
|
||||||
success = await rbac_repo.delete_role(role_id)
|
success = await rbac_repo.delete_role(role_id)
|
||||||
if not success:
|
if not success:
|
||||||
raise ControllerNotFoundError(f"Role '{role_id}' could not be deleted")
|
raise ControllerNotFoundError(f"Role '{role_id}' could not be deleted")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{role_id}/permissions", response_model=List[schemas.Permission])
|
@router.get("/{role_id}/permissions", response_model=List[schemas.Permission])
|
||||||
async def get_role_permissions(
|
async def get_role_permissions(
|
||||||
@ -142,7 +144,7 @@ async def add_permission_to_role(
|
|||||||
role_id: UUID,
|
role_id: UUID,
|
||||||
permission_id: UUID,
|
permission_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Add a permission to a role.
|
Add a permission to a role.
|
||||||
"""
|
"""
|
||||||
@ -155,6 +157,8 @@ async def add_permission_to_role(
|
|||||||
if not role:
|
if not role:
|
||||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{role_id}/permissions/{permission_id}",
|
"/{role_id}/permissions/{permission_id}",
|
||||||
@ -164,7 +168,7 @@ async def remove_permission_from_role(
|
|||||||
role_id: UUID,
|
role_id: UUID,
|
||||||
permission_id: UUID,
|
permission_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove member from an user group.
|
Remove member from an user group.
|
||||||
"""
|
"""
|
||||||
@ -176,3 +180,5 @@ async def remove_permission_from_role(
|
|||||||
role = await rbac_repo.remove_permission_from_role(role_id, permission)
|
role = await rbac_repo.remove_permission_from_role(role_id, permission)
|
||||||
if not role:
|
if not role:
|
||||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@ -23,7 +23,7 @@ import logging
|
|||||||
|
|
||||||
log = logging.getLogger()
|
log = logging.getLogger()
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@ -69,12 +69,13 @@ def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snaps
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> None:
|
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a snapshot.
|
Delete a snapshot.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await project.delete_snapshot(str(snapshot_id))
|
await project.delete_snapshot(str(snapshot_id))
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
||||||
|
@ -21,7 +21,7 @@ API routes for symbols.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, status
|
from fastapi import APIRouter, Request, Depends, Response, status
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -29,6 +29,8 @@ from gns3server.controller import Controller
|
|||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError
|
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError
|
||||||
|
|
||||||
|
from .dependencies.authentication import get_current_active_user
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -57,7 +59,7 @@ async def get_symbol(symbol_id: str) -> FileResponse:
|
|||||||
symbol = controller.symbols.get_path(symbol_id)
|
symbol = controller.symbols.get_path(symbol_id)
|
||||||
return FileResponse(symbol)
|
return FileResponse(symbol)
|
||||||
except (KeyError, OSError) as e:
|
except (KeyError, OSError) as e:
|
||||||
return ControllerNotFoundError(f"Could not get symbol file: {e}")
|
raise ControllerNotFoundError(f"Could not get symbol file: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@ -75,11 +77,25 @@ async def get_symbol_dimensions(symbol_id: str) -> dict:
|
|||||||
symbol_dimensions = {"width": width, "height": height}
|
symbol_dimensions = {"width": width, "height": height}
|
||||||
return symbol_dimensions
|
return symbol_dimensions
|
||||||
except (KeyError, OSError, ValueError) as e:
|
except (KeyError, OSError, ValueError) as e:
|
||||||
return ControllerNotFoundError(f"Could not get symbol file: {e}")
|
raise ControllerNotFoundError(f"Could not get symbol file: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{symbol_id:path}/raw", status_code=status.HTTP_204_NO_CONTENT)
|
@router.get("/default_symbols")
|
||||||
async def upload_symbol(symbol_id: str, request: Request) -> None:
|
def get_default_symbols() -> dict:
|
||||||
|
"""
|
||||||
|
Return all default symbols.
|
||||||
|
"""
|
||||||
|
|
||||||
|
controller = Controller.instance()
|
||||||
|
return controller.symbols.default_symbols()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{symbol_id:path}/raw",
|
||||||
|
dependencies=[Depends(get_current_active_user)],
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT
|
||||||
|
)
|
||||||
|
async def upload_symbol(symbol_id: str, request: Request) -> Response:
|
||||||
"""
|
"""
|
||||||
Upload a symbol file.
|
Upload a symbol file.
|
||||||
"""
|
"""
|
||||||
@ -96,12 +112,4 @@ async def upload_symbol(symbol_id: str, request: Request) -> None:
|
|||||||
# Reset the symbol list
|
# Reset the symbol list
|
||||||
controller.symbols.list()
|
controller.symbols.list()
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
@router.get("/default_symbols")
|
|
||||||
def get_default_symbols() -> dict:
|
|
||||||
"""
|
|
||||||
Return all default symbols.
|
|
||||||
"""
|
|
||||||
|
|
||||||
controller = Controller.instance()
|
|
||||||
return controller.symbols.default_symbols()
|
|
||||||
|
@ -25,12 +25,11 @@ import logging
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, Response, HTTPException, Depends, status
|
from fastapi import APIRouter, Request, Response, HTTPException, Depends, Response, status
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.controller import Controller
|
|
||||||
from gns3server.db.repositories.templates import TemplatesRepository
|
from gns3server.db.repositories.templates import TemplatesRepository
|
||||||
from gns3server.services.templates import TemplatesService
|
from gns3server.services.templates import TemplatesService
|
||||||
from gns3server.db.repositories.rbac import RbacRepository
|
from gns3server.db.repositories.rbac import RbacRepository
|
||||||
@ -103,13 +102,14 @@ async def delete_template(
|
|||||||
template_id: UUID,
|
template_id: UUID,
|
||||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete a template.
|
Delete a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await TemplatesService(templates_repo).delete_template(template_id)
|
await TemplatesService(templates_repo).delete_template(template_id)
|
||||||
await rbac_repo.delete_all_permissions_with_path(f"/templates/{template_id}")
|
await rbac_repo.delete_all_permissions_with_path(f"/templates/{template_id}")
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/templates", response_model=List[schemas.Template], response_model_exclude_unset=True)
|
@router.get("/templates", response_model=List[schemas.Template], response_model_exclude_unset=True)
|
||||||
@ -152,28 +152,3 @@ async def duplicate_template(
|
|||||||
template = await TemplatesService(templates_repo).duplicate_template(template_id)
|
template = await TemplatesService(templates_repo).duplicate_template(template_id)
|
||||||
await rbac_repo.add_permission_to_user_with_path(current_user.user_id, f"/templates/{template_id}/*")
|
await rbac_repo.add_permission_to_user_with_path(current_user.user_id, f"/templates/{template_id}/*")
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/projects/{project_id}/templates/{template_id}",
|
|
||||||
response_model=schemas.Node,
|
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}},
|
|
||||||
)
|
|
||||||
async def create_node_from_template(
|
|
||||||
project_id: UUID,
|
|
||||||
template_id: UUID,
|
|
||||||
template_usage: schemas.TemplateUsage,
|
|
||||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
|
||||||
) -> schemas.Node:
|
|
||||||
"""
|
|
||||||
Create a new node from a template.
|
|
||||||
"""
|
|
||||||
|
|
||||||
template = await TemplatesService(templates_repo).get_template(template_id)
|
|
||||||
controller = Controller.instance()
|
|
||||||
project = controller.get_project(str(project_id))
|
|
||||||
node = await project.add_node_from_template(
|
|
||||||
template, x=template_usage.x, y=template_usage.y, compute_id=template_usage.compute_id
|
|
||||||
)
|
|
||||||
return node.asdict()
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
API routes for users.
|
API routes for users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List
|
from typing import List
|
||||||
@ -98,13 +98,20 @@ async def get_logged_in_user(current_user: schemas.User = Depends(get_current_ac
|
|||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me", response_model=schemas.User)
|
@router.put("/me", response_model=schemas.User)
|
||||||
async def get_logged_in_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
|
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:
|
||||||
"""
|
"""
|
||||||
Get the current active user.
|
Update the current active user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return current_user
|
if user_update.email and await users_repo.get_user_by_email(user_update.email):
|
||||||
|
raise ControllerBadRequestError(f"Email '{user_update.email}' is already registered")
|
||||||
|
|
||||||
|
return await users_repo.update_user(current_user.user_id, user_update)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=List[schemas.User], dependencies=[Depends(get_current_active_user)])
|
@router.get("", response_model=List[schemas.User], dependencies=[Depends(get_current_active_user)])
|
||||||
@ -166,6 +173,12 @@ async def update_user(
|
|||||||
Update an user.
|
Update an user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if user_update.username and await users_repo.get_user_by_username(user_update.username):
|
||||||
|
raise ControllerBadRequestError(f"Username '{user_update.username}' is already registered")
|
||||||
|
|
||||||
|
if user_update.email and await users_repo.get_user_by_email(user_update.email):
|
||||||
|
raise ControllerBadRequestError(f"Email '{user_update.email}' is already registered")
|
||||||
|
|
||||||
user = await users_repo.update_user(user_id, user_update)
|
user = await users_repo.update_user(user_id, user_update)
|
||||||
if not user:
|
if not user:
|
||||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||||
@ -180,7 +193,7 @@ async def update_user(
|
|||||||
async def delete_user(
|
async def delete_user(
|
||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Delete an user.
|
Delete an user.
|
||||||
"""
|
"""
|
||||||
@ -196,6 +209,8 @@ async def delete_user(
|
|||||||
if not success:
|
if not success:
|
||||||
raise ControllerNotFoundError(f"User '{user_id}' could not be deleted")
|
raise ControllerNotFoundError(f"User '{user_id}' could not be deleted")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/{user_id}/groups",
|
"/{user_id}/groups",
|
||||||
@ -238,7 +253,7 @@ async def add_permission_to_user(
|
|||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
permission_id: UUID,
|
permission_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Add a permission to an user.
|
Add a permission to an user.
|
||||||
"""
|
"""
|
||||||
@ -251,6 +266,8 @@ async def add_permission_to_user(
|
|||||||
if not user:
|
if not user:
|
||||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{user_id}/permissions/{permission_id}",
|
"/{user_id}/permissions/{permission_id}",
|
||||||
@ -261,7 +278,7 @@ async def remove_permission_from_user(
|
|||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
permission_id: UUID,
|
permission_id: UUID,
|
||||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||||
) -> None:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove permission from an user.
|
Remove permission from an user.
|
||||||
"""
|
"""
|
||||||
@ -273,3 +290,5 @@ async def remove_permission_from_user(
|
|||||||
user = await rbac_repo.remove_permission_from_user(user_id, permission)
|
user = await rbac_repo.remove_permission_from_user(user_id, permission)
|
||||||
if not user:
|
if not user:
|
||||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||||
|
|
||||||
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
46
gns3server/appliances/6wind-turbo-router.gns3a
Normal file
46
gns3server/appliances/6wind-turbo-router.gns3a
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "6WIND Turbo Router",
|
||||||
|
"category": "router",
|
||||||
|
"description": "6WIND Turbo Router is a high performance, ready-to-use software virtual router. It can be deployed bare metal or in virtual machines on commercial-off-the-shelf (COTS) servers. It is a carrier-grade solution for Service Prodivers aiming at using white boxes to deploy network functions. Typical use-cases are transit/peering router, IPsec VPN gateway and CGNAT.",
|
||||||
|
"vendor_name": "6WIND",
|
||||||
|
"vendor_url": "https://www.6wind.com/",
|
||||||
|
"documentation_url": "https://doc.6wind.com/turbo-router-3/latest/turbo-router/",
|
||||||
|
"product_name": "6WIND Turbo Router",
|
||||||
|
"product_url": "https://www.6wind.com/vrouter-solutions/turbo-router/",
|
||||||
|
"registry_version": 4,
|
||||||
|
"status": "stable",
|
||||||
|
"maintainer": "GNS3 Team",
|
||||||
|
"maintainer_email": "developers@gns3.net",
|
||||||
|
"usage": "Default username / password is admin / admin.",
|
||||||
|
"symbol": "6wind.svg",
|
||||||
|
"port_name_format": "eth{0}",
|
||||||
|
"qemu": {
|
||||||
|
"adapter_type": "virtio-net-pci",
|
||||||
|
"adapters": 8,
|
||||||
|
"ram": 4096,
|
||||||
|
"cpus": 4,
|
||||||
|
"hda_disk_interface": "virtio",
|
||||||
|
"arch": "x86_64",
|
||||||
|
"console_type": "telnet",
|
||||||
|
"boot_priority": "c",
|
||||||
|
"kvm": "require",
|
||||||
|
"options": "-cpu host"
|
||||||
|
},
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "6wind-vrouter-tr-ae-x86_64-v3.1.4.m1.qcow2",
|
||||||
|
"version": "3.1.4.m1",
|
||||||
|
"md5sum": "bc84b81fba4f2f01eda6a338469e37a5",
|
||||||
|
"filesize": 693829632,
|
||||||
|
"download_url": "https://portal.6wind.com/register.php?utm_campaign=GNS3-2021-EVAL"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "3.1.4.m1",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "6wind-vrouter-tr-ae-x86_64-v3.1.4.m1.qcow2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -29,7 +29,6 @@
|
|||||||
"process_priority": "normal"
|
"process_priority": "normal"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"filename": "arubaoscx-disk-image-genericx86-p4-20201110192651.vmdk",
|
"filename": "arubaoscx-disk-image-genericx86-p4-20201110192651.vmdk",
|
||||||
"version": "10.06.0001",
|
"version": "10.06.0001",
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
"vendor_name": "HPE Aruba",
|
"vendor_name": "HPE Aruba",
|
||||||
"vendor_url": "arubanetworks.com",
|
"vendor_url": "arubanetworks.com",
|
||||||
"documentation_url": "https://asp.arubanetworks.com/downloads;products=Aruba%20SD-WAN",
|
"documentation_url": "https://asp.arubanetworks.com/downloads;products=Aruba%20SD-WAN",
|
||||||
"product_url": "https://www.arubanetworks.com/products/networking/gateways-and-controllers/",
|
|
||||||
"product_name": "Aruba SD-WAN Virtual Gateway",
|
"product_name": "Aruba SD-WAN Virtual Gateway",
|
||||||
|
"product_url": "https://www.arubanetworks.com/products/networking/gateways-and-controllers/",
|
||||||
"registry_version": 4,
|
"registry_version": 4,
|
||||||
"status": "stable",
|
"status": "stable",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"console_type": "telnet",
|
"console_type": "telnet",
|
||||||
"kernel_command_line": "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt",
|
"kernel_command_line": "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt",
|
||||||
"kvm": "disable",
|
"kvm": "disable",
|
||||||
"options": "-no-kvm -icount auto -hdachs 980,16,32",
|
"options": "-machine accel=tcg -icount auto -hdachs 980,16,32",
|
||||||
"cpu_throttling": 80,
|
"cpu_throttling": 80,
|
||||||
"process_priority": "low"
|
"process_priority": "low"
|
||||||
},
|
},
|
||||||
|
@ -25,6 +25,20 @@
|
|||||||
"kvm": "require"
|
"kvm": "require"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "asav9-15-1.qcow2",
|
||||||
|
"version": "9.15.1",
|
||||||
|
"md5sum": "4e8747667f52e9046979f126128a61d1",
|
||||||
|
"filesize": 252444672,
|
||||||
|
"download_url": "https://software.cisco.com/download/home/286119613/type/280775065/release/9.15.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "asav9-14-1.qcow2",
|
||||||
|
"version": "9.14.1",
|
||||||
|
"md5sum": "03d89e18e7f8ad00fe8e979c4790587d",
|
||||||
|
"filesize": 211877888,
|
||||||
|
"download_url": "https://software.cisco.com/download/home/286119613/type/280775065/release/9.14.1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "asav9-12-2-9.qcow2",
|
"filename": "asav9-12-2-9.qcow2",
|
||||||
"version": "9.12.2-9",
|
"version": "9.12.2-9",
|
||||||
@ -90,6 +104,18 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "9.15.1",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "asav9-15-1.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "9.14.1",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "asav9-14-1.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "9.12.2-9",
|
"name": "9.12.2-9",
|
||||||
"images": {
|
"images": {
|
||||||
|
@ -31,7 +31,14 @@
|
|||||||
"download_url": "https://sourceforge.net/projects/gns-3/files",
|
"download_url": "https://sourceforge.net/projects/gns-3/files",
|
||||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/IOSv_startup_config.img/download"
|
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/IOSv_startup_config.img/download"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename": "vios-adventerprisek9-m.spa.159-3.m3.qcow2",
|
||||||
|
"version": "15.9(3)M3",
|
||||||
|
"md5sum": "12893843af18e4c62f13d07266755653",
|
||||||
|
"filesize": 57296384,
|
||||||
|
"download_url": "https://learningnetworkstore.cisco.com/myaccount"
|
||||||
|
},
|
||||||
|
{
|
||||||
"filename": "vios-adventerprisek9-m.spa.159-3.m2.qcow2",
|
"filename": "vios-adventerprisek9-m.spa.159-3.m2.qcow2",
|
||||||
"version": "15.9(3)M2",
|
"version": "15.9(3)M2",
|
||||||
"md5sum": "a19e998bc3086825c751d125af722329",
|
"md5sum": "a19e998bc3086825c751d125af722329",
|
||||||
@ -75,7 +82,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
|
"name": "15.9(3)M3",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "vios-adventerprisek9-m.spa.159-3.m3.qcow2",
|
||||||
|
"hdb_disk_image": "IOSv_startup_config.img"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "15.9(3)M2",
|
"name": "15.9(3)M2",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "vios-adventerprisek9-m.spa.159-3.m2.qcow2",
|
"hda_disk_image": "vios-adventerprisek9-m.spa.159-3.m2.qcow2",
|
||||||
|
@ -23,6 +23,13 @@
|
|||||||
"kvm": "require"
|
"kvm": "require"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "vios_l2-adventerprisek9-m.ssa.high_iron_20200929.qcow2",
|
||||||
|
"version": "15.2(20200924:215240)",
|
||||||
|
"md5sum": "99ecab32de12410c533e6abd4e9710aa",
|
||||||
|
"filesize": 90409984,
|
||||||
|
"download_url": "https://learningnetworkstore.cisco.com/myaccount"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "vios_l2-adventerprisek9-m.ssa.high_iron_20190423.qcow2",
|
"filename": "vios_l2-adventerprisek9-m.ssa.high_iron_20190423.qcow2",
|
||||||
"version": "15.2(6.0.81)E",
|
"version": "15.2(6.0.81)E",
|
||||||
@ -53,6 +60,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "15.2(20200924:215240)",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "vios_l2-adventerprisek9-m.ssa.high_iron_20200929.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "15.2(6.0.81)E",
|
"name": "15.2(6.0.81)E",
|
||||||
"images": {
|
"images": {
|
||||||
|
@ -25,12 +25,12 @@
|
|||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
{
|
{
|
||||||
"filename": "cumulus-linux-4.3.0-vx-amd64-qemu.qcow2",
|
"filename": "cumulus-linux-4.3.0-vx-amd64-qemu.qcow2",
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"md5sum": "aba2f0bb462b26a208afb6202bc97d51",
|
"md5sum": "aba2f0bb462b26a208afb6202bc97d51",
|
||||||
"filesize": 2819325952,
|
"filesize": 2819325952,
|
||||||
"download_url": "https://cumulusnetworks.com/cumulus-vx/download/",
|
"download_url": "https://cumulusnetworks.com/cumulus-vx/download/",
|
||||||
"direct_download_url": "https://d2cd9e7ca6hntp.cloudfront.net/public/CumulusLinux-4.3.0/cumulus-linux-4.3.0-vx-amd64-qemu.qcow2"
|
"direct_download_url": "https://d2cd9e7ca6hntp.cloudfront.net/public/CumulusLinux-4.3.0/cumulus-linux-4.3.0-vx-amd64-qemu.qcow2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "cumulus-linux-4.2.0-vx-amd64-qemu.qcow2",
|
"filename": "cumulus-linux-4.2.0-vx-amd64-qemu.qcow2",
|
||||||
@ -233,7 +233,7 @@
|
|||||||
{
|
{
|
||||||
"name": "4.3.0",
|
"name": "4.3.0",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "cumulus-linux-4.3.0-vx-amd64-qemu.qcow2"
|
"hda_disk_image": "cumulus-linux-4.3.0-vx-amd64-qemu.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"options": "-cpu core2duo"
|
"options": "-cpu core2duo"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
{
|
{
|
||||||
"filename": "EXOS-VM_v31.1.1.3.qcow2",
|
"filename": "EXOS-VM_v31.1.1.3.qcow2",
|
||||||
"version": "31.1.1.3",
|
"version": "31.1.1.3",
|
||||||
"md5sum": "e4936ad94a5304bfeeca8dfc6f285cc0",
|
"md5sum": "e4936ad94a5304bfeeca8dfc6f285cc0",
|
||||||
|
@ -26,19 +26,26 @@
|
|||||||
"options": "-nographic"
|
"options": "-nographic"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "VOSSGNS3.8.4.0.0.qcow2",
|
||||||
|
"version": "v8.4.0.0",
|
||||||
|
"md5sum": "f457e7da3c1dec50670624c421333ef3",
|
||||||
|
"filesize": 386203648,
|
||||||
|
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.4.0.0.qcow2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "VOSSGNS3.8.3.0.0.qcow2",
|
"filename": "VOSSGNS3.8.3.0.0.qcow2",
|
||||||
"version": "v8.3.0.0",
|
"version": "v8.3.0.0",
|
||||||
"md5sum": "e1c789e439c5951728e349cf44690230",
|
"md5sum": "e1c789e439c5951728e349cf44690230",
|
||||||
"filesize": 384696320,
|
"filesize": 384696320,
|
||||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.3.0.0.qcow2"
|
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.3.0.0.qcow2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "VOSSGNS3.8.2.0.0.qcow2",
|
"filename": "VOSSGNS3.8.2.0.0.qcow2",
|
||||||
"version": "v8.2.0.0",
|
"version": "v8.2.0.0",
|
||||||
"md5sum": "9a0cd77c08644abbf3a69771c125c011",
|
"md5sum": "9a0cd77c08644abbf3a69771c125c011",
|
||||||
"filesize": 331808768,
|
"filesize": 331808768,
|
||||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.2.0.0.qcow2"
|
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.2.0.0.qcow2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "VOSSGNS3.8.1.5.0.qcow2",
|
"filename": "VOSSGNS3.8.1.5.0.qcow2",
|
||||||
@ -70,18 +77,24 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "v8.4.0.0",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "VOSSGNS3.8.4.0.0.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "v8.3.0.0",
|
"name": "v8.3.0.0",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "VOSSGNS3.8.3.0.0.qcow2"
|
"hda_disk_image": "VOSSGNS3.8.3.0.0.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "v8.2.0.0",
|
"name": "v8.2.0.0",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "VOSSGNS3.8.2.0.0.qcow2"
|
"hda_disk_image": "VOSSGNS3.8.2.0.0.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "8.1.5.0",
|
"name": "8.1.5.0",
|
||||||
"images": {
|
"images": {
|
||||||
|
@ -179,8 +179,8 @@
|
|||||||
{
|
{
|
||||||
"name": "6.4.5",
|
"name": "6.4.5",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
"hda_disk_image": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||||
"hdb_disk_image": "empty30G.qcow2"
|
"hdb_disk_image": "empty30G.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -254,11 +254,11 @@
|
|||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"name": "6.4.5",
|
"name": "6.4.5",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "FGT_VM64_KVM-v6-build1828-FORTINET.out.kvm.qcow2",
|
"hda_disk_image": "FGT_VM64_KVM-v6-build1828-FORTINET.out.kvm.qcow2",
|
||||||
"hdb_disk_image": "empty30G.qcow2"
|
"hdb_disk_image": "empty30G.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "6.2.2",
|
"name": "6.2.2",
|
||||||
|
@ -179,15 +179,15 @@
|
|||||||
{
|
{
|
||||||
"name": "6.4.5",
|
"name": "6.4.5",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
"hda_disk_image": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||||
"hdb_disk_image": "empty30G.qcow2"
|
"hdb_disk_image": "empty30G.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "6.4.4",
|
"name": "6.4.4",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "FMG_VM64_KVM-v6-build2253-FORTINET.out.kvm.qcow2",
|
"hda_disk_image": "FMG_VM64_KVM-v6-build2253-FORTINET.out.kvm.qcow2",
|
||||||
"hdb_disk_image": "empty30G.qcow2"
|
"hdb_disk_image": "empty30G.qcow2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,14 @@
|
|||||||
"kvm": "allow"
|
"kvm": "allow"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "frr-7.5.1.qcow2",
|
||||||
|
"version": "7.5.1",
|
||||||
|
"md5sum": "4b3ca0932a396b282ba35f102be1ed3b",
|
||||||
|
"filesize": 51169280,
|
||||||
|
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||||
|
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/frr-7.5.1.qcow2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "frr-7.3.1.qcow2",
|
"filename": "frr-7.3.1.qcow2",
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
@ -31,6 +39,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "7.5.1",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "frr-7.5.1.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "7.3.1",
|
"name": "7.3.1",
|
||||||
"images": {
|
"images": {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"vendor_url": "https://www.huawei.com",
|
"vendor_url": "https://www.huawei.com",
|
||||||
"product_name": "HuaWei AR1000v",
|
"product_name": "HuaWei AR1000v",
|
||||||
"product_url": "https://support.huawei.com/enterprise/en/routers/ar1000v-pid-21768212",
|
"product_url": "https://support.huawei.com/enterprise/en/routers/ar1000v-pid-21768212",
|
||||||
"registry_version": 5,
|
"registry_version": 4,
|
||||||
"status": "experimental",
|
"status": "experimental",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
"maintainer": "none",
|
"maintainer": "none",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"vendor_name": "HuaWei",
|
"vendor_name": "HuaWei",
|
||||||
"vendor_url": "https://www.huawei.com",
|
"vendor_url": "https://www.huawei.com",
|
||||||
"product_name": "HuaWei CE12800",
|
"product_name": "HuaWei CE12800",
|
||||||
"registry_version": 5,
|
"registry_version": 4,
|
||||||
"status": "experimental",
|
"status": "experimental",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
"maintainer": "none",
|
"maintainer": "none",
|
||||||
@ -33,10 +33,10 @@
|
|||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
|
"name": "V200R005C10SPC607B607",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "ce12800-V200R005C10SPC607B607.qcow2"
|
"hda_disk_image": "ce12800-V200R005C10SPC607B607.qcow2"
|
||||||
},
|
}
|
||||||
"name": "V200R005C10SPC607B607"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"vendor_url": "https://www.huawei.com",
|
"vendor_url": "https://www.huawei.com",
|
||||||
"product_name": "HuaWei NE40E",
|
"product_name": "HuaWei NE40E",
|
||||||
"product_url": "https://e.huawei.com/en/products/enterprise-networking/routers/ne/ne40e",
|
"product_url": "https://e.huawei.com/en/products/enterprise-networking/routers/ne/ne40e",
|
||||||
"registry_version": 5,
|
"registry_version": 4,
|
||||||
"status": "experimental",
|
"status": "experimental",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
"maintainer": "none",
|
"maintainer": "none",
|
||||||
@ -35,10 +35,10 @@
|
|||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
|
"name": "V800R011C00SPC607B607",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "ne40e-V800R011C00SPC607B607.qcow2"
|
"hda_disk_image": "ne40e-V800R011C00SPC607B607.qcow2"
|
||||||
},
|
}
|
||||||
"name": "V800R011C00SPC607B607"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"vendor_url": "https://www.huawei.com",
|
"vendor_url": "https://www.huawei.com",
|
||||||
"product_name": "HuaWei USG6000v",
|
"product_name": "HuaWei USG6000v",
|
||||||
"product_url": "https://e.huawei.com/en/products/enterprise-networking/security/firewall-gateway/usg6000v",
|
"product_url": "https://e.huawei.com/en/products/enterprise-networking/security/firewall-gateway/usg6000v",
|
||||||
"registry_version": 5,
|
"registry_version": 4,
|
||||||
"status": "experimental",
|
"status": "experimental",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
"maintainer": "none",
|
"maintainer": "none",
|
||||||
|
@ -23,6 +23,14 @@
|
|||||||
"kvm": "require"
|
"kvm": "require"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "kali-linux-2021.1-live-amd64.iso",
|
||||||
|
"version": "2021.1",
|
||||||
|
"md5sum": "3a3716fef866e5c29a1c1ccfc94264b5",
|
||||||
|
"filesize": 3591385088,
|
||||||
|
"download_url": "http://cdimage.kali.org/kali-2021.1/",
|
||||||
|
"direct_download_url": "http://cdimage.kali.org/kali-2021.1/kali-linux-2021.1-live-amd64.iso"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "kali-linux-2019.3-amd64.iso",
|
"filename": "kali-linux-2019.3-amd64.iso",
|
||||||
"version": "2019.3",
|
"version": "2019.3",
|
||||||
@ -137,6 +145,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "2021.1",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||||
|
"cdrom_image": "kali-linux-2021.1-live-amd64.iso"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "2019.3",
|
"name": "2019.3",
|
||||||
"images": {
|
"images": {
|
||||||
|
@ -3,16 +3,18 @@
|
|||||||
"category": "guest",
|
"category": "guest",
|
||||||
"description": "ntopng is the next generation version of the original ntop, a network traffic probe that shows the network usage, similar to what the popular top Unix command does. ntopng is based on libpcap and it has been written in a portable way in order to virtually run on every Unix platform, MacOSX and on Windows as well. ntopng users can use a a web browser to navigate through ntop (that acts as a web server) traffic information and get a dump of the network status. In the latter case, ntopng can be seen as a simple RMON-like agent with an embedded web interface.",
|
"description": "ntopng is the next generation version of the original ntop, a network traffic probe that shows the network usage, similar to what the popular top Unix command does. ntopng is based on libpcap and it has been written in a portable way in order to virtually run on every Unix platform, MacOSX and on Windows as well. ntopng users can use a a web browser to navigate through ntop (that acts as a web server) traffic information and get a dump of the network status. In the latter case, ntopng can be seen as a simple RMON-like agent with an embedded web interface.",
|
||||||
"vendor_name": "ntop",
|
"vendor_name": "ntop",
|
||||||
"vendor_url": "http://www.ntop.org/",
|
"vendor_url": "https://www.ntop.org/",
|
||||||
|
"documentation_url": "https://www.ntop.org/guides/ntopng/",
|
||||||
"product_name": "ntopng",
|
"product_name": "ntopng",
|
||||||
"registry_version": 3,
|
"registry_version": 3,
|
||||||
"status": "stable",
|
"status": "stable",
|
||||||
"maintainer": "GNS3 Team",
|
"maintainer": "GNS3 Team",
|
||||||
"maintainer_email": "developers@gns3.net",
|
"maintainer_email": "developers@gns3.net",
|
||||||
"usage": "In the web interface login as admin/admin",
|
"usage": "In the web interface login as admin/admin\n\nPersistent configuration:\n- Add \"/var/lib/redis\" as an additional persistent directory.\n- Use \"redis-cli save\" in an auxiliary console to save the configuration.",
|
||||||
"docker": {
|
"docker": {
|
||||||
"adapters": 1,
|
"adapters": 1,
|
||||||
"image": "lucaderi/ntopng-docker:latest",
|
"image": "ntop/ntopng:stable",
|
||||||
|
"start_command": "--dns-mode 2 --interface eth0",
|
||||||
"console_type": "http",
|
"console_type": "http",
|
||||||
"console_http_port": 3000,
|
"console_http_port": 3000,
|
||||||
"console_http_path": "/"
|
"console_http_path": "/"
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"ram": 2048,
|
"ram": 2048,
|
||||||
"hda_disk_interface": "ide",
|
"hda_disk_interface": "ide",
|
||||||
"hdb_disk_interface": "ide",
|
"hdb_disk_interface": "ide",
|
||||||
"arch": "x86_64",
|
"arch": "x86_64",
|
||||||
"console_type": "vnc",
|
"console_type": "vnc",
|
||||||
"boot_priority": "dc",
|
"boot_priority": "dc",
|
||||||
"kvm": "require"
|
"kvm": "require"
|
||||||
@ -52,4 +52,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"kvm": "allow"
|
"kvm": "allow"
|
||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
{
|
{
|
||||||
"filename": "openwrt-19.07.7-x86-64-combined-ext4.img",
|
"filename": "openwrt-19.07.7-x86-64-combined-ext4.img",
|
||||||
"version": "19.07.7",
|
"version": "19.07.7",
|
||||||
"md5sum": "0cfa752fab87014419ab00b18a6cc5a6",
|
"md5sum": "0cfa752fab87014419ab00b18a6cc5a6",
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"filename": "fossapup64-9.5.iso",
|
"filename": "fossapup64-9.5.iso",
|
||||||
"version": "9.5",
|
"version": "9.5",
|
||||||
"md5sum": "6a45e7a305b7d3172ebd9eab5ca460e4",
|
"md5sum": "6a45e7a305b7d3172ebd9eab5ca460e4",
|
||||||
"filesize": 428867584,
|
"filesize": 428867584,
|
||||||
"download_url": "http://puppylinux.com/index.html",
|
"download_url": "http://puppylinux.com/index.html",
|
||||||
"direct_download_url": "http://distro.ibiblio.org/puppylinux/puppy-fossa/fossapup64-9.5.iso"
|
"direct_download_url": "http://distro.ibiblio.org/puppylinux/puppy-fossa/fossapup64-9.5.iso"
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"documentation_url": "https://access.redhat.com/solutions/641193",
|
"documentation_url": "https://access.redhat.com/solutions/641193",
|
||||||
"product_name": "Red Hat Enterprise Linux KVM Guest Image",
|
"product_name": "Red Hat Enterprise Linux KVM Guest Image",
|
||||||
"product_url": "https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux",
|
"product_url": "https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux",
|
||||||
"registry_version": 5,
|
"registry_version": 4,
|
||||||
"status": "stable",
|
"status": "stable",
|
||||||
"availability": "service-contract",
|
"availability": "service-contract",
|
||||||
"maintainer": "Neyder Achahuanco",
|
"maintainer": "Neyder Achahuanco",
|
||||||
@ -56,25 +56,25 @@
|
|||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
|
"name": "8.3",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "rhel-8.3-x86_64-kvm.qcow2",
|
"hda_disk_image": "rhel-8.3-x86_64-kvm.qcow2",
|
||||||
"cdrom_image": "rhel-cloud-init.iso"
|
"cdrom_image": "rhel-cloud-init.iso"
|
||||||
},
|
}
|
||||||
"name": "8.3"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "7.9",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "rhel-server-7.9-x86_64-kvm.qcow2",
|
"hda_disk_image": "rhel-server-7.9-x86_64-kvm.qcow2",
|
||||||
"cdrom_image": "rhel-cloud-init.iso"
|
"cdrom_image": "rhel-cloud-init.iso"
|
||||||
},
|
}
|
||||||
"name": "7.9"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "6.10",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "rhel-server-6.10-update-11-x86_64-kvm.qcow2",
|
"hda_disk_image": "rhel-server-6.10-update-11-x86_64-kvm.qcow2",
|
||||||
"cdrom_image": "rhel-cloud-init.iso"
|
"cdrom_image": "rhel-cloud-init.iso"
|
||||||
},
|
}
|
||||||
"name": "6.10"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
45
gns3server/appliances/rockylinux.gns3a
Normal file
45
gns3server/appliances/rockylinux.gns3a
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "RockyLinux",
|
||||||
|
"category": "guest",
|
||||||
|
"description": "Rocky Linux is a community enterprise operating system designed to be 100% bug-for-bug compatible with Red Hat Enterprise Linux (RHEL).",
|
||||||
|
"vendor_name": "Rocky Enterprise Software Foundation",
|
||||||
|
"vendor_url": "https://rockylinux.org",
|
||||||
|
"documentation_url": "https://docs.rockylinux.org",
|
||||||
|
"product_name": "Rocky Linux",
|
||||||
|
"registry_version": 4,
|
||||||
|
"status": "experimental",
|
||||||
|
"maintainer": "Bernhard Ehlers",
|
||||||
|
"maintainer_email": "none@b-ehlers.de",
|
||||||
|
"usage": "Username:\trockylinux\nPassword:\trockylinux\nTo become root, use \"sudo su\".\n\nTo improve performance, increase RAM and vCPUs in the VM settings.",
|
||||||
|
"symbol": "linux_guest.svg",
|
||||||
|
"port_name_format": "ens{port4}",
|
||||||
|
"qemu": {
|
||||||
|
"adapter_type": "virtio-net-pci",
|
||||||
|
"adapters": 1,
|
||||||
|
"ram": 1536,
|
||||||
|
"hda_disk_interface": "sata",
|
||||||
|
"arch": "x86_64",
|
||||||
|
"console_type": "vnc",
|
||||||
|
"kvm": "require",
|
||||||
|
"options": "-usbdevice tablet"
|
||||||
|
},
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "RockyLinux_8.4_VMG_LinuxVMImages.COM.vmdk",
|
||||||
|
"version": "8.4",
|
||||||
|
"md5sum": "3452d5b0fbb4cdcf3ac6fe8de8d0ac08",
|
||||||
|
"filesize": 5273878528,
|
||||||
|
"download_url": "https://www.linuxvmimages.com/images/rockylinux-8",
|
||||||
|
"direct_download_url": "https://sourceforge.net/projects/linuxvmimages/files/VMware/R/rockylinux/8/RockyLinux_8.4_VMM.7z/download",
|
||||||
|
"compression": "7z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "8.4",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "RockyLinux_8.4_VMG_LinuxVMImages.COM.vmdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Tiny Core Linux",
|
"name": "Tiny Core Linux",
|
||||||
"category": "guest",
|
"category": "guest",
|
||||||
"description": "Core Linux is a smaller variant of Tiny Core without a graphical desktop.\n\nIt provides a complete Linux system using only a few MiB." ,
|
"description": "Core Linux is a smaller variant of Tiny Core without a graphical desktop.\n\nIt provides a complete Linux system using only a few MiB.",
|
||||||
"vendor_name": "Team Tiny Core",
|
"vendor_name": "Team Tiny Core",
|
||||||
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
|
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||||
"documentation_url": "http://wiki.tinycorelinux.net/",
|
"documentation_url": "http://wiki.tinycorelinux.net/",
|
||||||
|
@ -26,12 +26,19 @@
|
|||||||
},
|
},
|
||||||
"images": [
|
"images": [
|
||||||
{
|
{
|
||||||
"filename": "vyos-1.3-rolling-202101-qemu.qcow2",
|
"filename": "vyos-1.3.0-rc5-amd64.qcow2",
|
||||||
"version": "1.3-snapshot-202101",
|
"version": "1.3.0-rc5",
|
||||||
"md5sum": "b05a1f8a879c42342ea90f65ebe62f05",
|
"md5sum": "dd704f59afc0fccdf601cc750bf2c438",
|
||||||
"filesize": 315359232,
|
"filesize": 361955328,
|
||||||
"download_url": "https://vyos.net/get/snapshots/",
|
"download_url": "https://www.b-ehlers.de/GNS3/images/",
|
||||||
"direct_download_url": "https://s3.amazonaws.com/s3-us.vyos.io/snapshot/vyos-1.3-rolling-202101/qemu/vyos-1.3-rolling-202101-qemu.qcow2"
|
"direct_download_url": "https://www.b-ehlers.de/GNS3/images/vyos-1.3.0-rc5-amd64.qcow2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "vyos-1.2.8-amd64.iso",
|
||||||
|
"version": "1.2.8",
|
||||||
|
"md5sum": "0ad879db903efdbf1c39dc945e165931",
|
||||||
|
"filesize": 429916160,
|
||||||
|
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-8-generic-iso-image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "vyos-1.2.7-amd64.iso",
|
"filename": "vyos-1.2.7-amd64.iso",
|
||||||
@ -59,9 +66,16 @@
|
|||||||
],
|
],
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"name": "1.3-snapshot-202101",
|
"name": "1.3.0-rc5",
|
||||||
"images": {
|
"images": {
|
||||||
"hda_disk_image": "vyos-1.3-rolling-202101-qemu.qcow2"
|
"hda_disk_image": "vyos-1.3.0-rc5-amd64.qcow2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1.2.8",
|
||||||
|
"images": {
|
||||||
|
"hda_disk_image": "empty8G.qcow2",
|
||||||
|
"cdrom_image": "vyos-1.2.8-amd64.iso"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -48,4 +48,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -430,6 +430,7 @@ class Cloud(BaseNode):
|
|||||||
await self._add_ubridge_connection(nio, port_number)
|
await self._add_ubridge_connection(nio, port_number)
|
||||||
self._nios[port_number] = nio
|
self._nios[port_number] = nio
|
||||||
except (NodeError, UbridgeError) as e:
|
except (NodeError, UbridgeError) as e:
|
||||||
|
log.error('Cannot add NIO on cloud "{name}": {error}'.format(name=self._name, error=e))
|
||||||
await self._stop_ubridge()
|
await self._stop_ubridge()
|
||||||
self.status = "stopped"
|
self.status = "stopped"
|
||||||
self._nios[port_number] = nio
|
self._nios[port_number] = nio
|
||||||
|
@ -669,7 +669,7 @@ class QemuVM(BaseNode):
|
|||||||
|
|
||||||
if not mac_address:
|
if not mac_address:
|
||||||
# use the node UUID to generate a random MAC address
|
# use the node UUID to generate a random MAC address
|
||||||
self._mac_address = f"0c:{self.project.id[-4:-2]}:{self.project.id[-2:]}:{self.id[-4:-2]}:{self.id[-2:]}:00"
|
self._mac_address = f"0c:{self.id[2:4]}:{self.id[4:6]}:{self.id[6:8]}:00:00"
|
||||||
else:
|
else:
|
||||||
self._mac_address = mac_address
|
self._mac_address = mac_address
|
||||||
|
|
||||||
@ -912,20 +912,26 @@ class QemuVM(BaseNode):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not sys.platform.startswith("linux"):
|
# "-no-kvm" and "-no-hax' are deprecated since Qemu v5.2
|
||||||
if "-no-kvm" in options:
|
if "-no-kvm" in options:
|
||||||
options = options.replace("-no-kvm", "")
|
options = options.replace("-no-kvm", "-machine accel=tcg")
|
||||||
if "-enable-kvm" in options:
|
if "-no-hax" in options:
|
||||||
|
options = options.replace("-no-hax", "-machine accel=tcg")
|
||||||
|
|
||||||
|
if "-enable-kvm" in options:
|
||||||
|
if not sys.platform.startswith("linux"):
|
||||||
|
# KVM can only be enabled on Linux
|
||||||
options = options.replace("-enable-kvm", "")
|
options = options.replace("-enable-kvm", "")
|
||||||
else:
|
else:
|
||||||
if "-no-hax" in options:
|
options = options.replace("-enable-kvm", "-machine accel=kvm")
|
||||||
options = options.replace("-no-hax", "")
|
|
||||||
if "-enable-hax" in options:
|
if "-enable-hax" in options:
|
||||||
|
if not sys.platform.startswith("win"):
|
||||||
|
# HAXM is only available on Windows
|
||||||
options = options.replace("-enable-hax", "")
|
options = options.replace("-enable-hax", "")
|
||||||
if "-icount" in options and ("-no-kvm" not in options):
|
else:
|
||||||
# automatically add the -no-kvm option if -icount is detected
|
options = options.replace("-enable-hax", "-machine accel=hax")
|
||||||
# to help with the migration of ASA VMs created before version 1.4
|
|
||||||
options = "-no-kvm " + options
|
|
||||||
self._options = options.strip()
|
self._options = options.strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1862,6 +1868,24 @@ class QemuVM(BaseNode):
|
|||||||
try:
|
try:
|
||||||
qemu_img_path = self._get_qemu_img()
|
qemu_img_path = self._get_qemu_img()
|
||||||
command = [qemu_img_path, "create", "-o", f"backing_file={disk_image}", "-f", "qcow2", disk]
|
command = [qemu_img_path, "create", "-o", f"backing_file={disk_image}", "-f", "qcow2", disk]
|
||||||
|
try:
|
||||||
|
base_qcow2 = Qcow2(disk_image)
|
||||||
|
if base_qcow2.crypt_method:
|
||||||
|
# Workaround for https://gitlab.com/qemu-project/qemu/-/issues/441
|
||||||
|
# Also embed a secret name so it doesn't have to be passed to qemu -drive ...
|
||||||
|
options = {
|
||||||
|
"encrypt.key-secret": os.path.basename(disk_image),
|
||||||
|
"driver": "qcow2",
|
||||||
|
"file": {
|
||||||
|
"driver": "file",
|
||||||
|
"filename": disk_image,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
command = [qemu_img_path, "create", "-b", "json:"+json.dumps(options, separators=(',', ':')),
|
||||||
|
"-f", "qcow2", "-u", disk, str(base_qcow2.size)]
|
||||||
|
except Qcow2Error:
|
||||||
|
pass # non-qcow2 base images are acceptable (e.g. vmdk, raw image)
|
||||||
|
|
||||||
retcode = await self._qemu_img_exec(command)
|
retcode = await self._qemu_img_exec(command)
|
||||||
if retcode:
|
if retcode:
|
||||||
stdout = self.read_qemu_img_stdout()
|
stdout = self.read_qemu_img_stdout()
|
||||||
@ -2070,7 +2094,7 @@ class QemuVM(BaseNode):
|
|||||||
# The disk exists we check if the clone works
|
# The disk exists we check if the clone works
|
||||||
try:
|
try:
|
||||||
qcow2 = Qcow2(disk)
|
qcow2 = Qcow2(disk)
|
||||||
await qcow2.rebase(qemu_img_path, disk_image)
|
await qcow2.validate(qemu_img_path)
|
||||||
except (Qcow2Error, OSError) as e:
|
except (Qcow2Error, OSError) as e:
|
||||||
raise QemuError(f"Could not use qcow2 disk image '{disk_image}' for {disk_name} {e}")
|
raise QemuError(f"Could not use qcow2 disk image '{disk_image}' for {disk_name} {e}")
|
||||||
|
|
||||||
@ -2310,7 +2334,7 @@ class QemuVM(BaseNode):
|
|||||||
|
|
||||||
enable_hardware_accel = self.manager.config.settings.Qemu.enable_hardware_acceleration
|
enable_hardware_accel = self.manager.config.settings.Qemu.enable_hardware_acceleration
|
||||||
require_hardware_accel = self.manager.config.settings.Qemu.require_hardware_acceleration
|
require_hardware_accel = self.manager.config.settings.Qemu.require_hardware_acceleration
|
||||||
if enable_hardware_accel and "-no-kvm" not in options and "-no-hax" not in options:
|
if enable_hardware_accel and "-machine accel=tcg" not in options:
|
||||||
# Turn OFF hardware acceleration for non x86 architectures
|
# Turn OFF hardware acceleration for non x86 architectures
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
supported_binaries = [
|
supported_binaries = [
|
||||||
|
@ -58,13 +58,12 @@ class Qcow2:
|
|||||||
# uint64_t snapshots_offset;
|
# uint64_t snapshots_offset;
|
||||||
# } QCowHeader;
|
# } QCowHeader;
|
||||||
|
|
||||||
struct_format = ">IIQi"
|
struct_format = ">IIQiiQi"
|
||||||
with open(self._path, "rb") as f:
|
with open(self._path, 'rb') as f:
|
||||||
content = f.read(struct.calcsize(struct_format))
|
content = f.read(struct.calcsize(struct_format))
|
||||||
try:
|
try:
|
||||||
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(
|
(self.magic, self.version, self.backing_file_offset, self.backing_file_size,
|
||||||
struct_format, content
|
self.cluster_bits, self.size, self.crypt_method) = struct.unpack_from(struct_format, content)
|
||||||
)
|
|
||||||
|
|
||||||
except struct.error:
|
except struct.error:
|
||||||
raise Qcow2Error(f"Invalid file header for {self._path}")
|
raise Qcow2Error(f"Invalid file header for {self._path}")
|
||||||
@ -105,3 +104,15 @@ class Qcow2:
|
|||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise Qcow2Error("Could not rebase the image")
|
raise Qcow2Error("Could not rebase the image")
|
||||||
self._reload()
|
self._reload()
|
||||||
|
|
||||||
|
async def validate(self, qemu_img):
|
||||||
|
"""
|
||||||
|
Run qemu-img info to validate the file and its backing images
|
||||||
|
|
||||||
|
:param qemu_img: Path to the qemu-img binary
|
||||||
|
"""
|
||||||
|
command = [qemu_img, "info", "--backing-chain", self._path]
|
||||||
|
process = await asyncio.create_subprocess_exec(*command)
|
||||||
|
retcode = await process.wait()
|
||||||
|
if retcode != 0:
|
||||||
|
raise Qcow2Error("Could not validate the image")
|
||||||
|
@ -26,6 +26,7 @@ import asyncio
|
|||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
import codecs
|
import codecs
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from gns3server.utils.interfaces import interfaces
|
from gns3server.utils.interfaces import interfaces
|
||||||
@ -50,6 +51,7 @@ class VMware(BaseManager):
|
|||||||
self._vmrun_path = None
|
self._vmrun_path = None
|
||||||
self._host_type = None
|
self._host_type = None
|
||||||
self._vmnets = []
|
self._vmnets = []
|
||||||
|
self._vmnets_info = {}
|
||||||
self._vmnet_start_range = 2
|
self._vmnet_start_range = 2
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
self._vmnet_end_range = 19
|
self._vmnet_end_range = 19
|
||||||
@ -277,7 +279,7 @@ class VMware(BaseManager):
|
|||||||
else:
|
else:
|
||||||
# location on Linux
|
# location on Linux
|
||||||
vmware_networking_file = "/etc/vmware/networking"
|
vmware_networking_file = "/etc/vmware/networking"
|
||||||
vmnet_interfaces = []
|
vmnet_interfaces = {}
|
||||||
try:
|
try:
|
||||||
with open(vmware_networking_file, encoding="utf-8") as f:
|
with open(vmware_networking_file, encoding="utf-8") as f:
|
||||||
for line in f.read().splitlines():
|
for line in f.read().splitlines():
|
||||||
@ -285,7 +287,20 @@ class VMware(BaseManager):
|
|||||||
if match:
|
if match:
|
||||||
vmnet = f"vmnet{match.group(1)}"
|
vmnet = f"vmnet{match.group(1)}"
|
||||||
if vmnet not in ("vmnet0", "vmnet1", "vmnet8"):
|
if vmnet not in ("vmnet0", "vmnet1", "vmnet8"):
|
||||||
vmnet_interfaces.append(vmnet)
|
vmnet_interfaces[vmnet] = {}
|
||||||
|
with open(vmware_networking_file, "r", encoding="utf-8") as f:
|
||||||
|
for line in f.read().splitlines():
|
||||||
|
match = re.search(r"VNET_([0-9]+)_HOSTONLY_SUBNET\s+(.*)", line)
|
||||||
|
if match:
|
||||||
|
vmnet = "vmnet{}".format(match.group(1))
|
||||||
|
if vmnet in vmnet_interfaces.keys():
|
||||||
|
vmnet_interfaces[vmnet]["subnet"] = match.group(2)
|
||||||
|
match = re.search(r"VNET_([0-9]+)_HOSTONLY_NETMASK\s+(.*)", line)
|
||||||
|
if match:
|
||||||
|
vmnet = "vmnet{}".format(match.group(1))
|
||||||
|
if vmnet in vmnet_interfaces.keys():
|
||||||
|
vmnet_interfaces[vmnet]["netmask"] = match.group(2)
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise VMwareError(f"Cannot open {vmware_networking_file}: {e}")
|
raise VMwareError(f"Cannot open {vmware_networking_file}: {e}")
|
||||||
return vmnet_interfaces
|
return vmnet_interfaces
|
||||||
@ -330,6 +345,25 @@ class VMware(BaseManager):
|
|||||||
)
|
)
|
||||||
return self._vmnets.pop(0)
|
return self._vmnets.pop(0)
|
||||||
|
|
||||||
|
def find_bridge_interface(self, vmnet_interface):
|
||||||
|
"""
|
||||||
|
Find the bridge interface that is used for the vmnet interface in VMware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if vmnet_interface in self._vmnets_info.keys():
|
||||||
|
subnet = self._vmnets_info[vmnet_interface].get("subnet", None)
|
||||||
|
netmask = self._vmnets_info[vmnet_interface].get("netmask", None)
|
||||||
|
if subnet and netmask:
|
||||||
|
for interface in interfaces():
|
||||||
|
try:
|
||||||
|
network = ipaddress.ip_network(f"{subnet}/{netmask}")
|
||||||
|
ip = ipaddress.ip_address(interface["ip_address"])
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if ip in network:
|
||||||
|
return interface["name"]
|
||||||
|
return None
|
||||||
|
|
||||||
def refresh_vmnet_list(self, ubridge=True):
|
def refresh_vmnet_list(self, ubridge=True):
|
||||||
|
|
||||||
if ubridge:
|
if ubridge:
|
||||||
@ -337,6 +371,8 @@ class VMware(BaseManager):
|
|||||||
vmnet_interfaces = self._get_vmnet_interfaces_ubridge()
|
vmnet_interfaces = self._get_vmnet_interfaces_ubridge()
|
||||||
else:
|
else:
|
||||||
vmnet_interfaces = self._get_vmnet_interfaces()
|
vmnet_interfaces = self._get_vmnet_interfaces()
|
||||||
|
vmnet_interfaces = list(vmnet_interfaces.keys())
|
||||||
|
self._vmnets_info = vmnet_interfaces.copy()
|
||||||
|
|
||||||
# remove vmnets already in use
|
# remove vmnets already in use
|
||||||
for vmware_vm in self._nodes.values():
|
for vmware_vm in self._nodes.values():
|
||||||
@ -754,5 +790,4 @@ class VMware(BaseManager):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
vmware = VMware.instance()
|
vmware = VMware.instance()
|
||||||
print("=> Check version")
|
|
||||||
loop.run_until_complete(asyncio.ensure_future(vmware.check_vmware_version()))
|
loop.run_until_complete(asyncio.ensure_future(vmware.check_vmware_version()))
|
||||||
|
@ -22,9 +22,11 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import platform
|
||||||
|
|
||||||
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||||
from gns3server.utils.asyncio.serial import asyncio_open_serial
|
from gns3server.utils.asyncio.serial import asyncio_open_serial
|
||||||
|
from gns3server.utils import parse_version
|
||||||
from gns3server.utils.asyncio import locking
|
from gns3server.utils.asyncio import locking
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .vmware_error import VMwareError
|
from .vmware_error import VMwareError
|
||||||
@ -260,8 +262,13 @@ class VMwareVM(BaseNode):
|
|||||||
if self._get_vmx_setting(connected):
|
if self._get_vmx_setting(connected):
|
||||||
del self._vmx_pairs[connected]
|
del self._vmx_pairs[connected]
|
||||||
|
|
||||||
|
use_ubridge = True
|
||||||
|
# use alternative method to find vmnet interfaces on macOS >= 11.0 (BigSur)
|
||||||
|
# because "bridge" interfaces are used instead and they are only created on the VM starts
|
||||||
|
if sys.platform.startswith("darwin") and parse_version(platform.mac_ver()[0]) >= parse_version("11.0.0"):
|
||||||
|
use_ubridge = False
|
||||||
|
self.manager.refresh_vmnet_list(ubridge=use_ubridge)
|
||||||
# then configure VMware network adapters
|
# then configure VMware network adapters
|
||||||
self.manager.refresh_vmnet_list()
|
|
||||||
for adapter_number in range(0, self._adapters):
|
for adapter_number in range(0, self._adapters):
|
||||||
|
|
||||||
custom_adapter = self._get_custom_adapter_settings(adapter_number)
|
custom_adapter = self._get_custom_adapter_settings(adapter_number)
|
||||||
@ -347,8 +354,17 @@ class VMwareVM(BaseNode):
|
|||||||
vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
|
vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
|
||||||
|
|
||||||
if sys.platform.startswith("darwin"):
|
if sys.platform.startswith("darwin"):
|
||||||
# special case on OSX, we cannot bind VMnet interfaces using the libpcap
|
if parse_version(platform.mac_ver()[0]) >= parse_version("11.0.0"):
|
||||||
await self._ubridge_send(f'bridge add_nio_fusion_vmnet {vnet} "{vmnet_interface}"')
|
# a bridge interface (bridge100, bridge101 etc.) is used instead of a vmnet interface
|
||||||
|
# on macOS >= 11.0 (Big Sur)
|
||||||
|
vmnet_interface = self.manager.find_bridge_interface(vmnet_interface)
|
||||||
|
if not vmnet_interface:
|
||||||
|
raise VMwareError(f"Could not find bridge interface linked with {vmnet_interface}")
|
||||||
|
block_host_traffic = self.manager.config.get_section_config("VMware").getboolean("block_host_traffic", False)
|
||||||
|
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
|
||||||
|
else:
|
||||||
|
# special case on macOS, we cannot bind VMnet interfaces using the libpcap
|
||||||
|
await self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(name=vnet, interface=vmnet_interface))
|
||||||
else:
|
else:
|
||||||
block_host_traffic = self.manager.config.VMware.block_host_traffic
|
block_host_traffic = self.manager.config.VMware.block_host_traffic
|
||||||
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
|
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
|
||||||
|
@ -39,7 +39,8 @@ Handle the import of project from a .gns3project
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
async def import_project(controller, project_id, stream, location=None, name=None, keep_compute_id=False):
|
async def import_project(controller, project_id, stream, location=None, name=None, keep_compute_id=False,
|
||||||
|
auto_start=False, auto_open=False, auto_close=True):
|
||||||
"""
|
"""
|
||||||
Import a project contain in a zip file
|
Import a project contain in a zip file
|
||||||
|
|
||||||
@ -99,9 +100,9 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
|||||||
topology = load_topology(os.path.join(path, "project.gns3"))
|
topology = load_topology(os.path.join(path, "project.gns3"))
|
||||||
topology["name"] = project_name
|
topology["name"] = project_name
|
||||||
# To avoid unexpected behavior (project start without manual operations just after import)
|
# To avoid unexpected behavior (project start without manual operations just after import)
|
||||||
topology["auto_start"] = False
|
topology["auto_start"] = auto_start
|
||||||
topology["auto_open"] = False
|
topology["auto_open"] = auto_open
|
||||||
topology["auto_close"] = True
|
topology["auto_close"] = auto_close
|
||||||
|
|
||||||
# Generate a new node id
|
# Generate a new node id
|
||||||
node_old_to_new = {}
|
node_old_to_new = {}
|
||||||
|
@ -85,6 +85,7 @@ class Link:
|
|||||||
self._link_type = "ethernet"
|
self._link_type = "ethernet"
|
||||||
self._suspended = False
|
self._suspended = False
|
||||||
self._filters = {}
|
self._filters = {}
|
||||||
|
self._link_style = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filters(self):
|
def filters(self):
|
||||||
@ -170,6 +171,13 @@ class Link:
|
|||||||
self._project.emit_notification("link.updated", self.asdict())
|
self._project.emit_notification("link.updated", self.asdict())
|
||||||
self._project.dump()
|
self._project.dump()
|
||||||
|
|
||||||
|
async def update_link_style(self, link_style):
|
||||||
|
if link_style != self._link_style:
|
||||||
|
self._link_style = link_style
|
||||||
|
await self.update()
|
||||||
|
self._project.emit_notification("link.updated", self.asdict())
|
||||||
|
self._project.dump()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created(self):
|
def created(self):
|
||||||
"""
|
"""
|
||||||
@ -432,7 +440,13 @@ class Link:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if topology_dump:
|
if topology_dump:
|
||||||
return {"nodes": res, "link_id": self._id, "filters": self._filters, "suspend": self._suspended}
|
return {
|
||||||
|
"nodes": res,
|
||||||
|
"link_id": self._id,
|
||||||
|
"filters": self._filters,
|
||||||
|
"link_style": self._link_style,
|
||||||
|
"suspend": self._suspended,
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"nodes": res,
|
"nodes": res,
|
||||||
"link_id": self._id,
|
"link_id": self._id,
|
||||||
@ -444,4 +458,5 @@ class Link:
|
|||||||
"link_type": self._link_type,
|
"link_type": self._link_type,
|
||||||
"filters": self._filters,
|
"filters": self._filters,
|
||||||
"suspend": self._suspended,
|
"suspend": self._suspended,
|
||||||
|
"link_style": self._link_style,
|
||||||
}
|
}
|
||||||
|
@ -966,6 +966,8 @@ class Project:
|
|||||||
link = await self.add_link(link_id=link_data["link_id"])
|
link = await self.add_link(link_id=link_data["link_id"])
|
||||||
if "filters" in link_data:
|
if "filters" in link_data:
|
||||||
await link.update_filters(link_data["filters"])
|
await link.update_filters(link_data["filters"])
|
||||||
|
if "link_style" in link_data:
|
||||||
|
await link.update_link_style(link_data["link_style"])
|
||||||
for node_link in link_data.get("nodes", []):
|
for node_link in link_data.get("nodes", []):
|
||||||
node = self.get_node(node_link["node_id"])
|
node = self.get_node(node_link["node_id"])
|
||||||
port = node.get_port(node_link["adapter_number"], node_link["port_number"])
|
port = node.get_port(node_link["adapter_number"], node_link["port_number"])
|
||||||
|
@ -127,9 +127,9 @@ class Snapshot:
|
|||||||
if os.path.exists(project_files_path):
|
if os.path.exists(project_files_path):
|
||||||
await wait_run_in_executor(shutil.rmtree, project_files_path)
|
await wait_run_in_executor(shutil.rmtree, project_files_path)
|
||||||
with open(self._path, "rb") as f:
|
with open(self._path, "rb") as f:
|
||||||
project = await import_project(
|
project = await import_project(self._project.controller, self._project.id, f, location=self._project.path,
|
||||||
self._project.controller, self._project.id, f, location=self._project.path
|
auto_start=self._project.auto_start, auto_open=self._project.auto_open,
|
||||||
)
|
auto_close=self._project.auto_close)
|
||||||
except (OSError, PermissionError) as e:
|
except (OSError, PermissionError) as e:
|
||||||
raise ControllerError(str(e))
|
raise ControllerError(str(e))
|
||||||
await project.open()
|
await project.open()
|
||||||
|
@ -59,7 +59,7 @@ class CrashReport:
|
|||||||
Report crash to a third party service
|
Report crash to a third party service
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DSN = "https://ccd9829f1391432c900aa835e7eb1050:83d10f4d74654e2b8428129a62cf31cf@o19455.ingest.sentry.io/38482"
|
DSN = "https://aefc1e0e41e94957936f8773071aebf9:056b5247d4854b81ac9162d9ccc5a503@o19455.ingest.sentry.io/38482"
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -38,9 +38,9 @@ class Role(BaseTable):
|
|||||||
__tablename__ = "roles"
|
__tablename__ = "roles"
|
||||||
|
|
||||||
role_id = Column(GUID, primary_key=True, default=generate_uuid)
|
role_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||||
name = Column(String)
|
name = Column(String, unique=True)
|
||||||
description = Column(String)
|
description = Column(String)
|
||||||
builtin = Column(Boolean, default=False)
|
is_builtin = Column(Boolean, default=False)
|
||||||
permissions = relationship("Permission", secondary=permission_role_link, back_populates="roles")
|
permissions = relationship("Permission", secondary=permission_role_link, back_populates="roles")
|
||||||
groups = relationship("UserGroup", secondary=role_group_link, back_populates="roles")
|
groups = relationship("UserGroup", secondary=role_group_link, back_populates="roles")
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ class Role(BaseTable):
|
|||||||
def create_default_roles(target, connection, **kw):
|
def create_default_roles(target, connection, **kw):
|
||||||
|
|
||||||
default_roles = [
|
default_roles = [
|
||||||
{"name": "Administrator", "description": "Administrator role", "builtin": True},
|
{"name": "Administrator", "description": "Administrator role", "is_builtin": True},
|
||||||
{"name": "User", "description": "User role", "builtin": True},
|
{"name": "User", "description": "User role", "is_builtin": True},
|
||||||
]
|
]
|
||||||
|
|
||||||
stmt = target.insert().values(default_roles)
|
stmt = target.insert().values(default_roles)
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from sqlalchemy import Table, Boolean, Column, String, ForeignKey, event
|
from sqlalchemy import Table, Boolean, Column, String, DateTime, ForeignKey, event
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from .base import Base, BaseTable, generate_uuid, GUID
|
from .base import Base, BaseTable, generate_uuid, GUID
|
||||||
@ -45,6 +45,7 @@ class User(BaseTable):
|
|||||||
email = Column(String, unique=True, index=True)
|
email = Column(String, unique=True, index=True)
|
||||||
full_name = Column(String)
|
full_name = Column(String)
|
||||||
hashed_password = Column(String)
|
hashed_password = Column(String)
|
||||||
|
last_login = Column(DateTime)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superadmin = Column(Boolean, default=False)
|
is_superadmin = Column(Boolean, default=False)
|
||||||
groups = relationship("UserGroup", secondary=user_group_link, back_populates="users")
|
groups = relationship("UserGroup", secondary=user_group_link, back_populates="users")
|
||||||
@ -75,7 +76,7 @@ class UserGroup(BaseTable):
|
|||||||
|
|
||||||
user_group_id = Column(GUID, primary_key=True, default=generate_uuid)
|
user_group_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||||
name = Column(String, unique=True, index=True)
|
name = Column(String, unique=True, index=True)
|
||||||
builtin = Column(Boolean, default=False)
|
is_builtin = Column(Boolean, default=False)
|
||||||
users = relationship("User", secondary=user_group_link, back_populates="groups")
|
users = relationship("User", secondary=user_group_link, back_populates="groups")
|
||||||
roles = relationship("Role", secondary=role_group_link, back_populates="groups")
|
roles = relationship("Role", secondary=role_group_link, back_populates="groups")
|
||||||
|
|
||||||
@ -84,8 +85,8 @@ class UserGroup(BaseTable):
|
|||||||
def create_default_user_groups(target, connection, **kw):
|
def create_default_user_groups(target, connection, **kw):
|
||||||
|
|
||||||
default_groups = [
|
default_groups = [
|
||||||
{"name": "Administrators", "builtin": True},
|
{"name": "Administrators", "is_builtin": True},
|
||||||
{"name": "Users", "builtin": True}
|
{"name": "Users", "is_builtin": True}
|
||||||
]
|
]
|
||||||
|
|
||||||
stmt = target.insert().values(default_groups)
|
stmt = target.insert().values(default_groups)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import Optional, List, Union
|
from typing import Optional, List, Union
|
||||||
from sqlalchemy import select, update, delete
|
from sqlalchemy import select, update, delete, func
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
@ -140,6 +140,15 @@ class UsersRepository(BaseRepository):
|
|||||||
return user
|
return user
|
||||||
if not self._auth_service.verify_password(password, user.hashed_password):
|
if not self._auth_service.verify_password(password, user.hashed_password):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Backup the updated_at value
|
||||||
|
updated_at = user.updated_at
|
||||||
|
user.last_login = func.current_timestamp()
|
||||||
|
await self._db_session.commit()
|
||||||
|
# Restore the original updated_at value
|
||||||
|
# so it is not affected by the last login update
|
||||||
|
user.updated_at = updated_at
|
||||||
|
await self._db_session.commit()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
async def get_user_memberships(self, user_id: UUID) -> List[models.UserGroup]:
|
async def get_user_memberships(self, user_id: UUID) -> List[models.UserGroup]:
|
||||||
|
0
gns3server/handlers/api/compute/qemu_handler.py
Normal file
0
gns3server/handlers/api/compute/qemu_handler.py
Normal file
@ -27,7 +27,7 @@ from .controller.drawings import Drawing
|
|||||||
from .controller.gns3vm import GNS3VM
|
from .controller.gns3vm import GNS3VM
|
||||||
from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
|
from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
|
||||||
from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
|
from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
|
||||||
from .controller.users import UserCreate, UserUpdate, User, Credentials, UserGroupCreate, UserGroupUpdate, UserGroup
|
from .controller.users import UserCreate, UserUpdate, LoggedInUserUpdate, User, Credentials, UserGroupCreate, UserGroupUpdate, UserGroup
|
||||||
from .controller.rbac import RoleCreate, RoleUpdate, Role, PermissionCreate, PermissionUpdate, Permission
|
from .controller.rbac import RoleCreate, RoleUpdate, Role, PermissionCreate, PermissionUpdate, Permission
|
||||||
from .controller.tokens import Token
|
from .controller.tokens import Token
|
||||||
from .controller.snapshots import SnapshotCreate, Snapshot
|
from .controller.snapshots import SnapshotCreate, Snapshot
|
||||||
|
@ -42,6 +42,13 @@ class LinkType(str, Enum):
|
|||||||
serial = "serial"
|
serial = "serial"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkStyle(BaseModel):
|
||||||
|
|
||||||
|
color: Optional[str] = None
|
||||||
|
width: Optional[int] = None
|
||||||
|
type: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class LinkBase(BaseModel):
|
class LinkBase(BaseModel):
|
||||||
"""
|
"""
|
||||||
Link data.
|
Link data.
|
||||||
@ -49,6 +56,7 @@ class LinkBase(BaseModel):
|
|||||||
|
|
||||||
nodes: Optional[List[LinkNode]] = Field(None, min_items=0, max_items=2)
|
nodes: Optional[List[LinkNode]] = Field(None, min_items=0, max_items=2)
|
||||||
suspend: Optional[bool] = None
|
suspend: Optional[bool] = None
|
||||||
|
link_style: Optional[LinkStyle] = None
|
||||||
filters: Optional[dict] = None
|
filters: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class RoleUpdate(RoleBase):
|
|||||||
class Role(DateTimeModelMixin, RoleBase):
|
class Role(DateTimeModelMixin, RoleBase):
|
||||||
|
|
||||||
role_id: UUID
|
role_id: UUID
|
||||||
builtin: bool
|
is_builtin: bool
|
||||||
permissions: List[Permission]
|
permissions: List[Permission]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import EmailStr, BaseModel, Field, SecretStr
|
from pydantic import EmailStr, BaseModel, Field, SecretStr
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -27,6 +28,7 @@ class UserBase(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
username: Optional[str] = Field(None, min_length=3, regex="[a-zA-Z0-9_-]+$")
|
username: Optional[str] = Field(None, min_length=3, regex="[a-zA-Z0-9_-]+$")
|
||||||
|
is_active: bool = True
|
||||||
email: Optional[EmailStr]
|
email: Optional[EmailStr]
|
||||||
full_name: Optional[str]
|
full_name: Optional[str]
|
||||||
|
|
||||||
@ -48,10 +50,20 @@ class UserUpdate(UserBase):
|
|||||||
password: Optional[SecretStr] = Field(None, min_length=6, max_length=100)
|
password: Optional[SecretStr] = Field(None, min_length=6, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggedInUserUpdate(BaseModel):
|
||||||
|
"""
|
||||||
|
Properties to update a logged in user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
password: Optional[SecretStr] = Field(None, min_length=6, max_length=100)
|
||||||
|
email: Optional[EmailStr]
|
||||||
|
full_name: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class User(DateTimeModelMixin, UserBase):
|
class User(DateTimeModelMixin, UserBase):
|
||||||
|
|
||||||
user_id: UUID
|
user_id: UUID
|
||||||
is_active: bool = True
|
last_login: Optional[datetime] = None
|
||||||
is_superadmin: bool = False
|
is_superadmin: bool = False
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -85,7 +97,7 @@ class UserGroupUpdate(UserGroupBase):
|
|||||||
class UserGroup(DateTimeModelMixin, UserGroupBase):
|
class UserGroup(DateTimeModelMixin, UserGroupBase):
|
||||||
|
|
||||||
user_group_id: UUID
|
user_group_id: UUID
|
||||||
builtin: bool
|
is_builtin: bool
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
0
gns3server/schemas/link.py
Normal file
0
gns3server/schemas/link.py
Normal file
1
gns3server/static/web-ui/26.a7470e50128ddf7860c4.js
Normal file
1
gns3server/static/web-ui/26.a7470e50128ddf7860c4.js
Normal file
File diff suppressed because one or more lines are too long
@ -23,9 +23,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular-react/core
|
|
||||||
MIT
|
|
||||||
|
|
||||||
@angular/animations
|
@angular/animations
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
@ -371,31 +368,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
angular-persistence
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 Scott O'Bryan
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
angular-resizable-element
|
angular-resizable-element
|
||||||
MIT
|
MIT
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
@ -421,30 +393,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
angular2-indexeddb
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 Gil Fink and contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
bootstrap
|
bootstrap
|
||||||
MIT
|
MIT
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
@ -471,31 +419,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
classnames
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2017 Jed Watson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
core-js
|
core-js
|
||||||
MIT
|
MIT
|
||||||
Copyright (c) 2014-2021 Denis Pushkarev
|
Copyright (c) 2014-2021 Denis Pushkarev
|
||||||
@ -519,31 +442,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
css-to-style
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2020 Jacob Buck
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
css-tree
|
css-tree
|
||||||
MIT
|
MIT
|
||||||
Copyright (C) 2016-2019 by Roman Dvornov
|
Copyright (C) 2016-2019 by Roman Dvornov
|
||||||
@ -567,25 +465,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
d
|
|
||||||
ISC
|
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2013-2019, Mariusz Nowak, @medikoo, medikoo.com
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
d3-array
|
d3-array
|
||||||
BSD-3-Clause
|
BSD-3-Clause
|
||||||
Copyright 2010-2016 Mike Bostock
|
Copyright 2010-2016 Mike Bostock
|
||||||
@ -1663,88 +1542,124 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
dom-helpers
|
mdn-data
|
||||||
MIT
|
CC0-1.0
|
||||||
The MIT License (MIT)
|
CC0 1.0 Universal
|
||||||
|
|
||||||
Copyright (c) 2015 Jason Quense
|
Statement of Purpose
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||||
in the Software without restriction, including without limitation the rights
|
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
authorship and/or a database (each, a "Work").
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||||
copies or substantial portions of the Software.
|
purpose of contributing to a commons of creative, cultural and scientific
|
||||||
|
works ("Commons") that the public can reliably and without fear of later
|
||||||
|
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||||
|
and redistribute as freely as possible in any form whatsoever and for any
|
||||||
|
purposes, including without limitation commercial purposes. These owners may
|
||||||
|
contribute to the Commons to promote the ideal of a free culture and the
|
||||||
|
further production of creative, cultural and scientific works, or to gain
|
||||||
|
reputation or greater distribution for their Work in part through the use and
|
||||||
|
efforts of others.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
For these and/or other purposes and motivations, and without any expectation
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
of additional consideration or compensation, the person associating CC0 with a
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||||
SOFTWARE.
|
effect of CC0 on those rights.
|
||||||
|
|
||||||
eev
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
MIT
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||||
|
to, the following:
|
||||||
|
|
||||||
es5-ext
|
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||||
ISC
|
and translate a Work;
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2011-2019, Mariusz Nowak, @medikoo, medikoo.com
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
depicted in a Work;
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
subject to the limitations in paragraph 4(a), below;
|
||||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||||
|
a Work;
|
||||||
|
|
||||||
es6-symbol
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
ISC
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
ISC License
|
protection of databases, and under any national implementation thereof,
|
||||||
|
including any amended or successor version of such directive); and
|
||||||
|
|
||||||
Copyright (c) 2013-2019, Mariusz Nowak, @medikoo, medikoo.com
|
vii. other similar, equivalent or corresponding rights throughout the world
|
||||||
|
based on applicable law or treaty, and any national implementations thereof.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||||
copyright notice and this permission notice appear in all copies.
|
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||||
|
and Related Rights and associated claims and causes of action, whether now
|
||||||
|
known or unknown (including existing as well as future claims and causes of
|
||||||
|
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||||
|
duration provided by applicable law or treaty (including future time
|
||||||
|
extensions), (iii) in any current or future medium and for any number of
|
||||||
|
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||||
|
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||||
|
the Waiver for the benefit of each member of the public at large and to the
|
||||||
|
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||||
|
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||||
|
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||||
|
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
shall be preserved to the maximum extent permitted taking into account
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||||
|
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||||
|
provided by applicable law or treaty (including future time extensions), (iii)
|
||||||
|
in any current or future medium and for any number of copies, and (iv) for any
|
||||||
|
purpose whatsoever, including without limitation commercial, advertising or
|
||||||
|
promotional purposes (the "License"). The License shall be deemed effective as
|
||||||
|
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||||
|
License for any reason be judged legally invalid or ineffective under
|
||||||
|
applicable law, such partial invalidity or ineffectiveness shall not
|
||||||
|
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||||
|
affirms that he or she will not (i) exercise any of his or her remaining
|
||||||
|
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||||
|
and causes of action with respect to the Work, in either case contrary to
|
||||||
|
Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
ext
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
ISC
|
surrendered, licensed or otherwise affected by this document.
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2011-2019, Mariusz Nowak, @medikoo, medikoo.com
|
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||||
|
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||||
|
including without limitation warranties of title, merchantability, fitness
|
||||||
|
for a particular purpose, non infringement, or the absence of latent or
|
||||||
|
other defects, accuracy, or the present or absence of errors, whether or not
|
||||||
|
discoverable, all to the greatest extent permissible under applicable law.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
that may apply to the Work or any use thereof, including without limitation
|
||||||
copyright notice and this permission notice appear in all copies.
|
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||||
|
disclaims responsibility for obtaining any necessary consents, permissions
|
||||||
|
or other rights required for any use of the Work.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
party to this document and has no duty or obligation with respect to this
|
||||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
CC0 or use of the Work.
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
For more information, please see
|
||||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
mousetrap
|
mousetrap
|
||||||
@ -1953,6 +1868,7 @@ ngx-childprocess
|
|||||||
MIT
|
MIT
|
||||||
|
|
||||||
ngx-device-detector
|
ngx-device-detector
|
||||||
|
MIT
|
||||||
|
|
||||||
ngx-electron
|
ngx-electron
|
||||||
MIT
|
MIT
|
||||||
@ -2163,157 +2079,6 @@ Apache-2.0
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
object-assign
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
prop-types
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2013-present, Facebook, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
prop-types-extra
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 react-bootstrap
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
react
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
react-bootstrap
|
|
||||||
MIT
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014-present Stephen J. Collings, Matthew Honnibal, Pieter Vanderwerff
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
react-dom
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
regenerator-runtime
|
regenerator-runtime
|
||||||
MIT
|
MIT
|
||||||
MIT License
|
MIT License
|
||||||
@ -2777,31 +2542,6 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
scheduler
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
source-map
|
source-map
|
||||||
BSD-3-Clause
|
BSD-3-Clause
|
||||||
|
|
||||||
@ -2851,31 +2591,6 @@ WTFPL
|
|||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||||
|
|
||||||
|
|
||||||
stylenames
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Kevin
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
svg-crowbar
|
svg-crowbar
|
||||||
MIT
|
MIT
|
||||||
Copyright (c) 2013 The New York Times
|
Copyright (c) 2013 The New York Times
|
||||||
@ -2902,97 +2617,17 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|||||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
type
|
|
||||||
ISC
|
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2019, Mariusz Nowak, @medikoo, medikoo.com
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
uuid
|
uuid
|
||||||
MIT
|
MIT
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2010-2016 Robert Kieffer and other contributors
|
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
warning
|
|
||||||
MIT
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2013-present, Facebook, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
webpack
|
|
||||||
MIT
|
|
||||||
Copyright JS Foundation and other contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
'Software'), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
xterm
|
xterm
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||||
|
|
||||||
Current version: 2.2.19
|
Current version: 2.2.22
|
||||||
|
|
||||||
Current version: 2020.4.0-beta.1
|
Current version: 2020.4.0-beta.1
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
gns3server/static/web-ui/main.4d8bd51ab8b8682cb3f0.js
Normal file
1
gns3server/static/web-ui/main.4d8bd51ab8b8682cb3f0.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
!function(e){function r(r){for(var n,l,f=r[0],i=r[1],p=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(a&&a(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++)0!==o[t[f]]&&(n=!1);n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={0:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,(function(r){return e[r]}).bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="";var f=window.webpackJsonp=window.webpackJsonp||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var p=0;p<f.length;p++)r(f[p]);var a=i;t()}([]);
|
|
1
gns3server/static/web-ui/runtime.8a05010e91b5b34a5511.js
Normal file
1
gns3server/static/web-ui/runtime.8a05010e91b5b34a5511.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(){"use strict";var e,t,r,n,o={},u={};function i(e){var t=u[e];if(void 0!==t)return t.exports;var r=u[e]={id:e,loaded:!1,exports:{}};return o[e](r,r.exports,i),r.loaded=!0,r.exports}i.m=o,e=[],i.O=function(t,r,n,o){if(!r){var u=1/0;for(d=0;d<e.length;d++){r=e[d][0],n=e[d][1],o=e[d][2];for(var a=!0,c=0;c<r.length;c++)(!1&o||u>=o)&&Object.keys(i.O).every(function(e){return i.O[e](r[c])})?r.splice(c--,1):(a=!1,o<u&&(u=o));a&&(e.splice(d--,1),t=n())}return t}o=o||0;for(var d=e.length;d>0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[r,n,o]},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,{a:t}),t},i.d=function(e,t){for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.f={},i.e=function(e){return Promise.all(Object.keys(i.f).reduce(function(t,r){return i.f[r](e,t),t},[]))},i.u=function(e){return e+".a7470e50128ddf7860c4.js"},i.miniCssF=function(e){return"styles.c514ad565d1e615b86db.css"},i.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t={},r="gns3-web-ui:",i.l=function(e,n,o,u){if(t[e])t[e].push(n);else{var a,c;if(void 0!==o)for(var d=document.getElementsByTagName("script"),f=0;f<d.length;f++){var s=d[f];if(s.getAttribute("src")==e||s.getAttribute("data-webpack")==r+o){a=s;break}}a||(c=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.setAttribute("data-webpack",r+o),a.src=i.tu(e)),t[e]=[n];var l=function(r,n){a.onerror=a.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach(function(e){return e(n)}),r)return r(n)},p=setTimeout(l.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=l.bind(null,a.onerror),a.onload=l.bind(null,a.onload),c&&document.head.appendChild(a)}},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.tu=function(e){return void 0===n&&(n={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(n=trustedTypes.createPolicy("angular#bundler",n))),n.createScriptURL(e)},i.p="",function(){var e={666:0};i.f.j=function(t,r){var n=i.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else if(666!=t){var o=new Promise(function(r,o){n=e[t]=[r,o]});r.push(n[2]=o);var u=i.p+i.u(t),a=new Error;i.l(u,function(r){if(i.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var o=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;a.message="Loading chunk "+t+" failed.\n("+o+": "+u+")",a.name="ChunkLoadError",a.type=o,a.request=u,n[1](a)}},"chunk-"+t,t)}else e[t]=0},i.O.j=function(t){return 0===e[t]};var t=function(t,r){var n,o,u=r[0],a=r[1],c=r[2],d=0;for(n in a)i.o(a,n)&&(i.m[n]=a[n]);if(c)var f=c(i);for(t&&t(r);d<u.length;d++)i.o(e,o=u[d])&&e[o]&&e[o][0](),e[u[d]]=0;return i.O(f)},r=self.webpackChunkgns3_web_ui=self.webpackChunkgns3_web_ui||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))}()}();
|
File diff suppressed because one or more lines are too long
11
gns3server/static/web-ui/styles.c514ad565d1e615b86db.css
Normal file
11
gns3server/static/web-ui/styles.c514ad565d1e615b86db.css
Normal file
File diff suppressed because one or more lines are too long
@ -1,19 +1,19 @@
|
|||||||
uvicorn==0.13.4
|
uvicorn==0.14.0
|
||||||
fastapi==0.65.1
|
fastapi==0.68.0
|
||||||
websockets==9.1
|
websockets==9.1
|
||||||
python-multipart==0.0.5
|
python-multipart==0.0.5
|
||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
aiofiles==0.7.0
|
aiofiles==0.7.0
|
||||||
Jinja2==3.0.1
|
Jinja2==3.0.1
|
||||||
sentry-sdk==1.1.0
|
sentry-sdk==1.3.1
|
||||||
psutil==5.8.0
|
psutil==5.8.0
|
||||||
async-timeout==3.0.1
|
async-timeout==3.0.1
|
||||||
distro==1.5.0
|
distro==1.6.0
|
||||||
py-cpuinfo==8.0.0
|
py-cpuinfo==8.0.0
|
||||||
sqlalchemy==1.4.17
|
sqlalchemy==1.4.22
|
||||||
aiosqlite===0.17.0
|
aiosqlite===0.17.0
|
||||||
passlib[bcrypt]==1.7.4
|
passlib[bcrypt]==1.7.4
|
||||||
python-jose==3.2.0
|
python-jose==3.3.0
|
||||||
email-validator==1.1.2
|
email-validator==1.1.3
|
||||||
async-exit-stack==1.0.1 ; python_version < "3.7"
|
async-exit-stack==1.0.1 ; python_version < "3.7"
|
||||||
async-generator==1.10 ; python_version < "3.7"
|
async-generator==1.10 ; python_version < "3.7"
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
|
|
||||||
function help {
|
function help {
|
||||||
echo "Usage:" >&2
|
echo "Usage:" >&2
|
||||||
echo "--with-openvpn: Install Open VPN" >&2
|
echo "--with-openvpn: Install OpenVPN" >&2
|
||||||
echo "--with-iou: Install IOU" >&2
|
echo "--with-iou: Install IOU" >&2
|
||||||
echo "--with-i386-repository: Add the i386 repositories required by IOU if they are not already available on the system. Warning: this will replace your source.list in order to use the official Ubuntu mirror" >&2
|
echo "--with-i386-repository: Add the i386 repositories required by IOU if they are not already available on the system. Warning: this will replace your source.list in order to use the official Ubuntu mirror" >&2
|
||||||
|
echo "--without-kvm: Disable KVM, required if system do not support it (limitation in some hypervisors and cloud providers). Warning: only disable KVM if strictly necessary as this will degrade performance" >&2
|
||||||
echo "--unstable: Use the GNS3 unstable repository"
|
echo "--unstable: Use the GNS3 unstable repository"
|
||||||
echo "--help: This help" >&2
|
echo "--help: This help" >&2
|
||||||
}
|
}
|
||||||
@ -45,9 +46,10 @@ fi
|
|||||||
USE_VPN=0
|
USE_VPN=0
|
||||||
USE_IOU=0
|
USE_IOU=0
|
||||||
I386_REPO=0
|
I386_REPO=0
|
||||||
|
DISABLE_KVM=0
|
||||||
UNSTABLE=0
|
UNSTABLE=0
|
||||||
|
|
||||||
TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,unstable,help -n 'gns3-remote-install.sh' -- "$@"`
|
TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,without-kvm,unstable,help -n 'gns3-remote-install.sh' -- "$@"`
|
||||||
if [ $? != 0 ]
|
if [ $? != 0 ]
|
||||||
then
|
then
|
||||||
help
|
help
|
||||||
@ -70,6 +72,10 @@ while true ; do
|
|||||||
I386_REPO=1
|
I386_REPO=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--without-kvm)
|
||||||
|
DISABLE_KVM=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--unstable)
|
--unstable)
|
||||||
UNSTABLE=1
|
UNSTABLE=1
|
||||||
shift
|
shift
|
||||||
@ -147,7 +153,7 @@ apt-get update
|
|||||||
log "Upgrade packages"
|
log "Upgrade packages"
|
||||||
apt-get upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"
|
apt-get upgrade --yes --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"
|
||||||
|
|
||||||
log " Install GNS3 packages"
|
log "Install GNS3 packages"
|
||||||
apt-get install -y gns3-server
|
apt-get install -y gns3-server
|
||||||
|
|
||||||
log "Create user GNS3 with /opt/gns3 as home directory"
|
log "Create user GNS3 with /opt/gns3 as home directory"
|
||||||
@ -171,7 +177,7 @@ usermod -aG docker gns3
|
|||||||
|
|
||||||
if [ $USE_IOU == 1 ]
|
if [ $USE_IOU == 1 ]
|
||||||
then
|
then
|
||||||
log "IOU setup"
|
log "Setup IOU"
|
||||||
dpkg --add-architecture i386
|
dpkg --add-architecture i386
|
||||||
apt-get update
|
apt-get update
|
||||||
|
|
||||||
@ -179,6 +185,8 @@ then
|
|||||||
|
|
||||||
# Force the host name to gns3vm
|
# Force the host name to gns3vm
|
||||||
echo gns3vm > /etc/hostname
|
echo gns3vm > /etc/hostname
|
||||||
|
hostname gns3vm
|
||||||
|
HOSTNAME=$(hostname)
|
||||||
|
|
||||||
# Force hostid for IOU
|
# Force hostid for IOU
|
||||||
dd if=/dev/zero bs=4 count=1 of=/etc/hostid
|
dd if=/dev/zero bs=4 count=1 of=/etc/hostid
|
||||||
@ -204,10 +212,16 @@ configs_path = /opt/gns3/configs
|
|||||||
report_errors = True
|
report_errors = True
|
||||||
|
|
||||||
[Qemu]
|
[Qemu]
|
||||||
enable_kvm = True
|
enable_hardware_acceleration = True
|
||||||
require_kvm = True
|
require_hardware_acceleration = True
|
||||||
EOFC
|
EOFC
|
||||||
|
|
||||||
|
if [ $DISABLE_KVM == 1 ]
|
||||||
|
then
|
||||||
|
log "Disable KVM support"
|
||||||
|
sed -i 's/hardware_acceleration = True/hardware_acceleration = False/g' /etc/gns3/gns3_server.conf
|
||||||
|
fi
|
||||||
|
|
||||||
chown -R gns3:gns3 /etc/gns3
|
chown -R gns3:gns3 /etc/gns3
|
||||||
chmod -R 700 /etc/gns3
|
chmod -R 700 /etc/gns3
|
||||||
|
|
||||||
@ -286,24 +300,15 @@ if [ $USE_VPN == 1 ]
|
|||||||
then
|
then
|
||||||
log "Setup VPN"
|
log "Setup VPN"
|
||||||
|
|
||||||
cat <<EOFSERVER > /etc/gns3/gns3_server.conf
|
log "Change GNS3 to listen on VPN interface"
|
||||||
[Server]
|
|
||||||
host = 172.16.253.1
|
|
||||||
port = 3080
|
|
||||||
images_path = /opt/gns3/images
|
|
||||||
projects_path = /opt/gns3/projects
|
|
||||||
report_errors = True
|
|
||||||
|
|
||||||
[Qemu]
|
sed -i 's/host = 0.0.0.0/host = 172.16.253.1/' /etc/gns3/gns3_server.conf
|
||||||
enable_kvm = True
|
|
||||||
require_kvm = True
|
|
||||||
EOFSERVER
|
|
||||||
|
|
||||||
log "Install packages for Open VPN"
|
log "Install packages for OpenVPN"
|
||||||
|
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
openvpn \
|
openvpn \
|
||||||
uuid \
|
uuid \
|
||||||
dnsutils \
|
dnsutils \
|
||||||
nginx-light
|
nginx-light
|
||||||
|
|
||||||
@ -329,7 +334,6 @@ echo "And remove this file with rm /etc/update-motd.d/70-openvpn"
|
|||||||
EOFMOTD
|
EOFMOTD
|
||||||
chmod 755 /etc/update-motd.d/70-openvpn
|
chmod 755 /etc/update-motd.d/70-openvpn
|
||||||
|
|
||||||
|
|
||||||
mkdir -p /etc/openvpn/
|
mkdir -p /etc/openvpn/
|
||||||
|
|
||||||
[ -d /dev/net ] || mkdir -p /dev/net
|
[ -d /dev/net ] || mkdir -p /dev/net
|
||||||
@ -385,7 +389,7 @@ status openvpn-status-1194.log
|
|||||||
log-append /var/log/openvpn-udp1194.log
|
log-append /var/log/openvpn-udp1194.log
|
||||||
EOFUDP
|
EOFUDP
|
||||||
|
|
||||||
echo "Setup HTTP server for serving client certificate"
|
log "Setup HTTP server for serving client certificate"
|
||||||
mkdir -p /usr/share/nginx/openvpn/$UUID
|
mkdir -p /usr/share/nginx/openvpn/$UUID
|
||||||
cp /root/client.ovpn /usr/share/nginx/openvpn/$UUID/$HOSTNAME.ovpn
|
cp /root/client.ovpn /usr/share/nginx/openvpn/$UUID/$HOSTNAME.ovpn
|
||||||
touch /usr/share/nginx/openvpn/$UUID/index.html
|
touch /usr/share/nginx/openvpn/$UUID/index.html
|
||||||
@ -393,7 +397,7 @@ touch /usr/share/nginx/openvpn/index.html
|
|||||||
|
|
||||||
cat <<EOFNGINX > /etc/nginx/sites-available/openvpn
|
cat <<EOFNGINX > /etc/nginx/sites-available/openvpn
|
||||||
server {
|
server {
|
||||||
listen 8003;
|
listen 8003;
|
||||||
root /usr/share/nginx/openvpn;
|
root /usr/share/nginx/openvpn;
|
||||||
}
|
}
|
||||||
EOFNGINX
|
EOFNGINX
|
||||||
@ -402,11 +406,13 @@ EOFNGINX
|
|||||||
service nginx stop
|
service nginx stop
|
||||||
service nginx start
|
service nginx start
|
||||||
|
|
||||||
log "Restart OpenVPN"
|
log "Restart OpenVPN and GNS3"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
service openvpn stop
|
service openvpn stop
|
||||||
service openvpn start
|
service openvpn start
|
||||||
|
service gns3 stop
|
||||||
|
service gns3 start
|
||||||
|
|
||||||
log "Download http://$MY_IP_ADDR:8003/$UUID/$HOSTNAME.ovpn to setup your OpenVPN client after rebooting the server"
|
log "Download http://$MY_IP_ADDR:8003/$UUID/$HOSTNAME.ovpn to setup your OpenVPN client after rebooting the server"
|
||||||
|
|
||||||
|
2
setup.py
2
setup.py
@ -57,7 +57,7 @@ setup(
|
|||||||
"gns3loopback = gns3server.utils.windows_loopback:main"
|
"gns3loopback = gns3server.utils.windows_loopback:main"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
packages=find_packages(".", exclude=["docs", "tests"]),
|
packages=find_packages(".", exclude=["docs", "tests*"]),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms="any",
|
platforms="any",
|
||||||
|
@ -208,7 +208,7 @@ class TestGroupRolesRoutes:
|
|||||||
roles = await user_repo.get_user_group_roles(group_in_db.user_group_id)
|
roles = await user_repo.get_user_group_roles(group_in_db.user_group_id)
|
||||||
assert len(roles) == 2 # 1 default role + 1 custom role
|
assert len(roles) == 2 # 1 default role + 1 custom role
|
||||||
for role in roles:
|
for role in roles:
|
||||||
if not role.builtin:
|
if not role.is_builtin:
|
||||||
assert role.name == test_role.name
|
assert role.name == test_role.name
|
||||||
|
|
||||||
async def test_get_user_group_roles(
|
async def test_get_user_group_roles(
|
||||||
|
@ -97,6 +97,38 @@ class TestUserRoutes:
|
|||||||
response = await client.post(app.url_path_for("create_user"), json=new_user)
|
response = await client.post(app.url_path_for("create_user"), json=new_user)
|
||||||
assert response.status_code == status_code
|
assert response.status_code == status_code
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"attr, value, status_code",
|
||||||
|
(
|
||||||
|
("email", "user@email.com", status.HTTP_200_OK),
|
||||||
|
("email", "user@email.com", status.HTTP_400_BAD_REQUEST),
|
||||||
|
("username", "user2", status.HTTP_400_BAD_REQUEST),
|
||||||
|
("email", "invalid_email@one@two.io", status.HTTP_422_UNPROCESSABLE_ENTITY),
|
||||||
|
("password", "short", status.HTTP_422_UNPROCESSABLE_ENTITY),
|
||||||
|
("username", "user2@#$%^<>", status.HTTP_422_UNPROCESSABLE_ENTITY),
|
||||||
|
("username", "ab", status.HTTP_422_UNPROCESSABLE_ENTITY),
|
||||||
|
("full_name", "John Doe", status.HTTP_200_OK),
|
||||||
|
("password", "password123", status.HTTP_200_OK),
|
||||||
|
("is_active", True, status.HTTP_200_OK),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
async def test_update_user(
|
||||||
|
self,
|
||||||
|
app: FastAPI,
|
||||||
|
client: AsyncClient,
|
||||||
|
db_session: AsyncSession,
|
||||||
|
attr: str,
|
||||||
|
value: str,
|
||||||
|
status_code: int,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
user_repo = UsersRepository(db_session)
|
||||||
|
user_in_db = await user_repo.get_user_by_username("user2")
|
||||||
|
update_user = {}
|
||||||
|
update_user[attr] = value
|
||||||
|
response = await client.put(app.url_path_for("update_user", user_id=user_in_db.user_id), json=update_user)
|
||||||
|
assert response.status_code == status_code
|
||||||
|
|
||||||
async def test_users_saved_password_is_hashed(
|
async def test_users_saved_password_is_hashed(
|
||||||
self,
|
self,
|
||||||
app: FastAPI,
|
app: FastAPI,
|
||||||
@ -285,6 +317,46 @@ class TestUserMe:
|
|||||||
response = await unauthorized_client.get(app.url_path_for("get_logged_in_user"))
|
response = await unauthorized_client.get(app.url_path_for("get_logged_in_user"))
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
|
||||||
|
async def test_authenticated_user_can_update_own_data(
|
||||||
|
self,
|
||||||
|
app: FastAPI,
|
||||||
|
authorized_client: AsyncClient,
|
||||||
|
test_user: User,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
response = await authorized_client.get(app.url_path_for("get_logged_in_user"))
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
user = User(**response.json())
|
||||||
|
assert user.username == test_user.username
|
||||||
|
assert user.email == test_user.email
|
||||||
|
assert user.user_id == test_user.user_id
|
||||||
|
|
||||||
|
# logged in users can only change their email, full name and password
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"attr, value, status_code",
|
||||||
|
(
|
||||||
|
("email", "user42@email.com", status.HTTP_200_OK),
|
||||||
|
("email", "user42@email.com", status.HTTP_400_BAD_REQUEST),
|
||||||
|
("full_name", "John Doe", status.HTTP_200_OK),
|
||||||
|
("password", "password123", status.HTTP_200_OK),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
async def test_authenticated_user_can_update_own_data(
|
||||||
|
self,
|
||||||
|
app: FastAPI,
|
||||||
|
authorized_client: AsyncClient,
|
||||||
|
attr: str,
|
||||||
|
value: str,
|
||||||
|
status_code: int,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
update_user = {}
|
||||||
|
update_user[attr] = value
|
||||||
|
response = await authorized_client.put(
|
||||||
|
app.url_path_for("update_logged_in_user"),
|
||||||
|
json=update_user
|
||||||
|
)
|
||||||
|
assert response.status_code == status_code
|
||||||
|
|
||||||
class TestSuperAdmin:
|
class TestSuperAdmin:
|
||||||
|
|
||||||
|
@ -855,21 +855,15 @@ def test_options(linux_platform, vm):
|
|||||||
assert vm.kvm is False
|
assert vm.kvm is False
|
||||||
|
|
||||||
vm.options = "-no-kvm"
|
vm.options = "-no-kvm"
|
||||||
assert vm.options == "-no-kvm"
|
assert vm.options == "-machine accel=tcg"
|
||||||
|
|
||||||
vm.options = "-enable-kvm"
|
vm.options = "-enable-kvm"
|
||||||
assert vm.options == "-enable-kvm"
|
assert vm.options == "-machine accel=kvm"
|
||||||
|
|
||||||
vm.options = "-icount 12"
|
|
||||||
assert vm.options == "-no-kvm -icount 12"
|
|
||||||
|
|
||||||
vm.options = "-icount 12 -no-kvm"
|
|
||||||
assert vm.options == "-icount 12 -no-kvm"
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_windows(windows_platform, vm):
|
def test_options_windows(windows_platform, vm):
|
||||||
vm.options = "-no-kvm"
|
vm.options = "-no-kvm"
|
||||||
assert vm.options == ""
|
assert vm.options == "-machine accel=tcg"
|
||||||
|
|
||||||
vm.options = "-enable-kvm"
|
vm.options = "-enable-kvm"
|
||||||
assert vm.options == ""
|
assert vm.options == ""
|
||||||
@ -919,7 +913,7 @@ async def test_run_with_kvm_linux_options_no_kvm(linux_platform, vm):
|
|||||||
|
|
||||||
with patch("os.path.exists", return_value=True) as os_path:
|
with patch("os.path.exists", return_value=True) as os_path:
|
||||||
vm.manager.config.settings.Qemu.enable_hardware_acceleration = True
|
vm.manager.config.settings.Qemu.enable_hardware_acceleration = True
|
||||||
assert await vm._run_with_hardware_acceleration("qemu-system-x86_64", "-no-kvm") is False
|
assert await vm._run_with_hardware_acceleration("qemu-system-x86_64", "-machine accel=tcg") is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user