Merge pull request #2091 from GNS3/use-themed-symbols

Let the controller allocate symbols
This commit is contained in:
Jeremy Grossmann 2022-07-25 20:45:04 +02:00 committed by GitHub
commit bd9af3fe90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 96 additions and 48 deletions

View File

@ -49,6 +49,11 @@ symbols_path = /home/gns3/GNS3/symbols
; Path where custom configs are stored ; Path where custom configs are stored
configs_path = /home/gns3/GNS3/configs configs_path = /home/gns3/GNS3/configs
; Default symbol theme
; Currently available themes are "Classic", Affinity-square-blue", "Affinity-square-red"
; "Affinity-square-gray", "Affinity-circle-blue", "Affinity-circle-red" and "Affinity-circle-gray"
default_symbol_theme = Affinity-square-blue
; Option to automatically send crash reports to the GNS3 team ; Option to automatically send crash reports to the GNS3 team
report_errors = True report_errors = True

View File

@ -47,7 +47,7 @@ router = APIRouter()
@router.get("") @router.get("")
async def get_appliances( async def get_appliances(
update: Optional[bool] = False, update: Optional[bool] = False,
symbol_theme: Optional[str] = "Classic" symbol_theme: Optional[str] = None
) -> List[schemas.Appliance]: ) -> List[schemas.Appliance]:
""" """
Return all appliances known by the controller. Return all appliances known by the controller.
@ -56,7 +56,7 @@ async def get_appliances(
controller = Controller.instance() controller = Controller.instance()
if update: if update:
await controller.appliance_manager.download_appliances() await controller.appliance_manager.download_appliances()
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme) controller.appliance_manager.load_appliances(symbol_theme)
return [c.asdict() for c in controller.appliance_manager.appliances.values()] return [c.asdict() for c in controller.appliance_manager.appliances.values()]

View File

@ -281,7 +281,7 @@ class ApplianceManager:
template_data = await self._appliance_to_template(appliance) template_data = await self._appliance_to_template(appliance)
await self._create_template(template_data, templates_repo, rbac_repo, current_user) await self._create_template(template_data, templates_repo, rbac_repo, current_user)
def load_appliances(self, symbol_theme: str = "Classic") -> None: def load_appliances(self, symbol_theme: str = None) -> None:
""" """
Loads appliance files from disk. Loads appliance files from disk.
""" """
@ -326,6 +326,8 @@ class ApplianceManager:
from . import Controller from . import Controller
controller = Controller.instance() controller = Controller.instance()
if not symbol_theme:
symbol_theme = controller.symbols.theme
category = appliance["category"] category = appliance["category"]
if category == "guest": if category == "guest":
if "docker" in appliance: if "docker" in appliance:

View File

@ -19,7 +19,7 @@
CLASSIC_SYMBOL_THEME = { CLASSIC_SYMBOL_THEME = {
"cloud": ":/symbols/classic/cloud.svg", "cloud": ":/symbols/classic/cloud.svg",
"ethernet_switch": ":/symbols/classic/ethernet_switch.svg", "ethernet_switch": ":/symbols/classic/ethernet_switch.svg",
"ethernet_hub": ":/symbols/classic/hub.svg", "hub": ":/symbols/classic/hub.svg",
"frame_relay_switch": ":/symbols/classic/frame_relay_switch.svg", "frame_relay_switch": ":/symbols/classic/frame_relay_switch.svg",
"atm_switch": ":/symbols/classic/atm_switch.svg", "atm_switch": ":/symbols/classic/atm_switch.svg",
"router": ":/symbols/classic/router.svg", "router": ":/symbols/classic/router.svg",
@ -36,8 +36,8 @@ CLASSIC_SYMBOL_THEME = {
AFFINITY_SQUARE_BLUE_SYMBOL_THEME = { AFFINITY_SQUARE_BLUE_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/square/blue/cloud.svg", "cloud": ":/symbols/affinity/square/blue/cloud.svg",
"ethernet_switch": ":/symbols/affinity/square/blue/switch.svg", "ethernet_switch": ":/symbols/affinity/square/blue/switch.svg",
"ethernet_hub": ":/symbols/affinity/square/blue/hub.svg", "hub": ":/symbols/affinity/square/blue/hub.svg",
"frame_relay_switch.svg": ":/symbols/affinity/square/blue/isdn.svg", "frame_relay_switch": ":/symbols/affinity/square/blue/isdn.svg",
"atm_switch": ":/symbols/affinity/square/blue/atm.svg", "atm_switch": ":/symbols/affinity/square/blue/atm.svg",
"router": ":/symbols/affinity/square/blue/router.svg", "router": ":/symbols/affinity/square/blue/router.svg",
"multilayer_switch": ":/symbols/affinity/square/blue/switch_multilayer.svg", "multilayer_switch": ":/symbols/affinity/square/blue/switch_multilayer.svg",
@ -53,7 +53,7 @@ AFFINITY_SQUARE_BLUE_SYMBOL_THEME = {
AFFINITY_SQUARE_RED_SYMBOL_THEME = { AFFINITY_SQUARE_RED_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/square/red/cloud.svg", "cloud": ":/symbols/affinity/square/red/cloud.svg",
"ethernet_switch": ":/symbols/affinity/square/red/switch.svg", "ethernet_switch": ":/symbols/affinity/square/red/switch.svg",
"ethernet_hub": ":/symbols/affinity/square/red/hub.svg", "hub": ":/symbols/affinity/square/red/hub.svg",
"frame_relay_switch": ":/symbols/affinity/square/red/isdn.svg", "frame_relay_switch": ":/symbols/affinity/square/red/isdn.svg",
"atm_switch": ":/symbols/affinity/square/red/atm.svg", "atm_switch": ":/symbols/affinity/square/red/atm.svg",
"router": ":/symbols/affinity/square/red/router.svg", "router": ":/symbols/affinity/square/red/router.svg",
@ -70,7 +70,7 @@ AFFINITY_SQUARE_RED_SYMBOL_THEME = {
AFFINITY_SQUARE_GRAY_SYMBOL_THEME = { AFFINITY_SQUARE_GRAY_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/square/gray/cloud.svg", "cloud": ":/symbols/affinity/square/gray/cloud.svg",
"ethernet_switch": ":/symbols/affinity/square/gray/switch.svg", "ethernet_switch": ":/symbols/affinity/square/gray/switch.svg",
"ethernet_hub": ":/symbols/affinity/square/gray/hub.svg", "hub": ":/symbols/affinity/square/gray/hub.svg",
"frame_relay_switch": ":/symbols/affinity/square/gray/isdn.svg", "frame_relay_switch": ":/symbols/affinity/square/gray/isdn.svg",
"atm_switch": ":/symbols/affinity/square/gray/atm.svg", "atm_switch": ":/symbols/affinity/square/gray/atm.svg",
"router": ":/symbols/affinity/square/gray/router.svg", "router": ":/symbols/affinity/square/gray/router.svg",
@ -87,7 +87,7 @@ AFFINITY_SQUARE_GRAY_SYMBOL_THEME = {
AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = { AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/circle/blue/cloud.svg", "cloud": ":/symbols/affinity/circle/blue/cloud.svg",
"ethernet_switch": ":/symbols/affinity/circle/blue/switch.svg", "ethernet_switch": ":/symbols/affinity/circle/blue/switch.svg",
"ethernet_hub": ":/symbols/affinity/circle/blue/hub.svg", "hub": ":/symbols/affinity/circle/blue/hub.svg",
"frame_relay_switch": ":/symbols/affinity/circle/blue/isdn.svg", "frame_relay_switch": ":/symbols/affinity/circle/blue/isdn.svg",
"atm_switch": ":/symbols/affinity/circle/blue/atm.svg", "atm_switch": ":/symbols/affinity/circle/blue/atm.svg",
"router": ":/symbols/affinity/circle/blue/router.svg", "router": ":/symbols/affinity/circle/blue/router.svg",
@ -104,7 +104,7 @@ AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = {
AFFINITY_CIRCLE_RED_SYMBOL_THEME = { AFFINITY_CIRCLE_RED_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/circle/red/cloud.svg", "cloud": ":/symbols/affinity/circle/red/cloud.svg",
"ethernet_switch": ":/symbols/affinity/circle/red/switch.svg", "ethernet_switch": ":/symbols/affinity/circle/red/switch.svg",
"ethernet_hub": ":/symbols/affinity/circle/red/hub.svg", "hub": ":/symbols/affinity/circle/red/hub.svg",
"frame_relay_switch": ":/symbols/affinity/circle/red/isdn.svg", "frame_relay_switch": ":/symbols/affinity/circle/red/isdn.svg",
"atm_switch": ":/symbols/affinity/circle/red/atm.svg", "atm_switch": ":/symbols/affinity/circle/red/atm.svg",
"router": ":/symbols/affinity/circle/red/router.svg", "router": ":/symbols/affinity/circle/red/router.svg",
@ -121,7 +121,7 @@ AFFINITY_CIRCLE_RED_SYMBOL_THEME = {
AFFINITY_CIRCLE_GRAY_SYMBOL_THEME = { AFFINITY_CIRCLE_GRAY_SYMBOL_THEME = {
"cloud": ":/symbols/affinity/circle/gray/cloud.svg", "cloud": ":/symbols/affinity/circle/gray/cloud.svg",
"ethernet_switch": ":/symbols/affinity/circle/gray/switch.svg", "ethernet_switch": ":/symbols/affinity/circle/gray/switch.svg",
"ethernet_hub": ":/symbols/affinity/circle/gray/hub.svg", "hub": ":/symbols/affinity/circle/gray/hub.svg",
"frame_relay_switch": ":/symbols/affinity/circle/gray/isdn.svg", "frame_relay_switch": ":/symbols/affinity/circle/gray/isdn.svg",
"atm_switch": ":/symbols/affinity/circle/gray/atm.svg", "atm_switch": ":/symbols/affinity/circle/gray/atm.svg",
"router": ":/symbols/affinity/circle/gray/router.svg", "router": ":/symbols/affinity/circle/gray/router.svg",

View File

@ -43,7 +43,9 @@ class Symbols:
# Keep a cache of symbols size # Keep a cache of symbols size
self._symbol_size_cache = {} self._symbol_size_cache = {}
self._current_theme = "Classic"
self._server_config = Config.instance().settings.Server
self._current_theme = self._server_config.default_symbol_theme
self._themes = BUILTIN_SYMBOL_THEMES self._themes = BUILTIN_SYMBOL_THEMES
@property @property
@ -66,10 +68,11 @@ class Symbols:
theme = self._themes.get(symbol_theme, None) theme = self._themes.get(symbol_theme, None)
if not theme: if not theme:
raise ControllerNotFoundError(f"Could not find symbol theme '{symbol_theme}'") log.warning(f"Could not find symbol theme '{symbol_theme}'")
return None
symbol_path = theme.get(symbol) symbol_path = theme.get(symbol)
if symbol_path not in self._symbols_path: if symbol_path not in self._symbols_path:
log.warning(f"Default symbol {symbol_path} was not found") log.warning(f"Default symbol {symbol} was not found")
return None return None
return symbol_path return symbol_path
@ -125,7 +128,17 @@ class Symbols:
return self._symbols_path.get(symbol_id) return self._symbols_path.get(symbol_id)
def resolve_symbol(self, symbol_name):
if not symbol_name.startswith(":/"):
symbol = self.get_default_symbol(symbol_name, self._current_theme)
if symbol:
return symbol
return symbol_name
def get_path(self, symbol_id): def get_path(self, symbol_id):
symbol_id = self.resolve_symbol(symbol_id)
try: try:
return self._symbols_path[symbol_id] return self._symbols_path[symbol_id]
except KeyError: except KeyError:

View File

@ -109,6 +109,17 @@ class ServerProtocol(str, Enum):
https = "https" https = "https"
class BuiltinSymbolTheme(str, Enum):
classic = "Classic"
affinity_square_blue = "Affinity-square-blue"
affinity_square_red = "Affinity-square-red"
affinity_square_gray = "Affinity-square-gray"
affinity_circle_blue = "Affinity-circle-blue"
affinity_circle_red = "Affinity-circle-red"
affinity_circle_gray = "Affinity-circle-gray"
class ServerSettings(BaseModel): class ServerSettings(BaseModel):
local: bool = False local: bool = False
@ -124,6 +135,7 @@ class ServerSettings(BaseModel):
appliances_path: str = "~/GNS3/appliances" appliances_path: str = "~/GNS3/appliances"
symbols_path: str = "~/GNS3/symbols" symbols_path: str = "~/GNS3/symbols"
configs_path: str = "~/GNS3/configs" configs_path: str = "~/GNS3/configs"
default_symbol_theme: BuiltinSymbolTheme = BuiltinSymbolTheme.affinity_square_blue
report_errors: bool = True report_errors: bool = True
additional_images_paths: List[str] = Field(default_factory=list) additional_images_paths: List[str] = Field(default_factory=list)
console_start_port_range: int = Field(5000, gt=0, le=65535) console_start_port_range: int = Field(5000, gt=0, le=65535)

View File

@ -31,7 +31,7 @@ class CloudTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "Cloud{0}" default_name_format: Optional[str] = "Cloud{0}"
symbol: Optional[str] = ":/symbols/cloud.svg" symbol: Optional[str] = "cloud"
ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] = Field(default_factory=list) ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] = Field(default_factory=list)
remote_console_host: Optional[str] = Field("127.0.0.1", description="Remote console host or IP") remote_console_host: Optional[str] = Field("127.0.0.1", description="Remote console host or IP")
remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port") remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port")

View File

@ -26,7 +26,7 @@ class DockerTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "{name}-{0}" default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = ":/symbols/docker_guest.svg" symbol: Optional[str] = "docker_guest"
image: str = Field(..., description="Docker image name") image: str = Field(..., description="Docker image name")
adapters: Optional[int] = Field(1, ge=0, le=100, description="Number of adapters") adapters: Optional[int] = Field(1, ge=0, le=100, description="Number of adapters")
start_command: Optional[str] = Field("", description="Docker CMD entry") start_command: Optional[str] = Field("", description="Docker CMD entry")

View File

@ -34,7 +34,7 @@ class DynamipsTemplate(TemplateBase):
category: Optional[Category] = "router" category: Optional[Category] = "router"
default_name_format: Optional[str] = "R{0}" default_name_format: Optional[str] = "R{0}"
symbol: Optional[str] = ":/symbols/router.svg" symbol: Optional[str] = "router"
platform: DynamipsPlatform = Field(..., description="Cisco router platform") platform: DynamipsPlatform = Field(..., description="Cisco router platform")
image: str = Field(..., description="Path to the IOS image") image: str = Field(..., description="Path to the IOS image")
exec_area: Optional[int] = Field(64, description="Exec area value") exec_area: Optional[int] = Field(64, description="Exec area value")

View File

@ -37,7 +37,7 @@ class EthernetHubTemplate(TemplateBase):
category: Optional[Category] = "switch" category: Optional[Category] = "switch"
default_name_format: Optional[str] = "Hub{0}" default_name_format: Optional[str] = "Hub{0}"
symbol: Optional[str] = ":/symbols/hub.svg" symbol: Optional[str] = "hub"
ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports") ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports")

View File

@ -47,7 +47,7 @@ class EthernetSwitchTemplate(TemplateBase):
category: Optional[Category] = "switch" category: Optional[Category] = "switch"
default_name_format: Optional[str] = "Switch{0}" default_name_format: Optional[str] = "Switch{0}"
symbol: Optional[str] = ":/symbols/ethernet_switch.svg" symbol: Optional[str] = "ethernet_switch"
ports_mapping: Optional[List[EthernetSwitchPort]] = Field(DEFAULT_PORTS, description="Ports") ports_mapping: Optional[List[EthernetSwitchPort]] = Field(DEFAULT_PORTS, description="Ports")
console_type: Optional[ConsoleType] = Field("none", description="Console type") console_type: Optional[ConsoleType] = Field("none", description="Console type")

View File

@ -26,7 +26,7 @@ class IOUTemplate(TemplateBase):
category: Optional[Category] = "router" category: Optional[Category] = "router"
default_name_format: Optional[str] = "IOU{0}" default_name_format: Optional[str] = "IOU{0}"
symbol: Optional[str] = ":/symbols/multilayer_switch.svg" symbol: Optional[str] = "multilayer_switch"
path: str = Field(..., description="Path of IOU executable") path: str = Field(..., description="Path of IOU executable")
ethernet_adapters: Optional[int] = Field(2, description="Number of ethernet adapters") ethernet_adapters: Optional[int] = Field(2, description="Number of ethernet adapters")
serial_adapters: Optional[int] = Field(2, description="Number of serial adapters") serial_adapters: Optional[int] = Field(2, description="Number of serial adapters")

View File

@ -35,7 +35,7 @@ class QemuTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "{name}-{0}" default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = ":/symbols/qemu_guest.svg" symbol: Optional[str] = "qemu_guest"
qemu_path: Optional[str] = Field("", description="Qemu executable path") qemu_path: Optional[str] = Field("", description="Qemu executable path")
platform: Optional[QemuPlatform] = Field("x86_64", description="Platform to emulate") platform: Optional[QemuPlatform] = Field("x86_64", description="Platform to emulate")
linked_clone: Optional[bool] = Field(True, description="Whether the VM is a linked clone or not") linked_clone: Optional[bool] = Field(True, description="Whether the VM is a linked clone or not")

View File

@ -30,7 +30,7 @@ class VirtualBoxTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "{name}-{0}" default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = ":/symbols/vbox_guest.svg" symbol: Optional[str] = "vbox_guest"
vmname: str = Field(..., description="VirtualBox VM name (in VirtualBox itself)") vmname: str = Field(..., description="VirtualBox VM name (in VirtualBox itself)")
ram: Optional[int] = Field(256, gt=0, description="Amount of RAM in MB") ram: Optional[int] = Field(256, gt=0, description="Amount of RAM in MB")
linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not") linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not")

View File

@ -31,7 +31,7 @@ class VMwareTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "{name}-{0}" default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = ":/symbols/vmware_guest.svg" symbol: Optional[str] = "vmware_guest"
vmx_path: str = Field(..., description="Path to the vmx file") vmx_path: str = Field(..., description="Path to the vmx file")
linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not") linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not")
first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0") first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0")

View File

@ -26,7 +26,7 @@ class VPCSTemplate(TemplateBase):
category: Optional[Category] = "guest" category: Optional[Category] = "guest"
default_name_format: Optional[str] = "PC{0}" default_name_format: Optional[str] = "PC{0}"
symbol: Optional[str] = ":/symbols/vpcs_guest.svg" symbol: Optional[str] = "vpcs_guest"
base_script_file: Optional[str] = Field("vpcs_base_config.txt", description="Script file") base_script_file: Optional[str] = Field("vpcs_base_config.txt", description="Script file")
console_type: Optional[ConsoleType] = Field("telnet", description="Console type") console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
console_auto_start: Optional[bool] = Field( console_auto_start: Optional[bool] = Field(

View File

@ -86,7 +86,7 @@ BUILTIN_TEMPLATES = [
"name": "Cloud", "name": "Cloud",
"default_name_format": "Cloud{0}", "default_name_format": "Cloud{0}",
"category": "guest", "category": "guest",
"symbol": ":/symbols/cloud.svg", "symbol": "cloud",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -96,7 +96,7 @@ BUILTIN_TEMPLATES = [
"name": "NAT", "name": "NAT",
"default_name_format": "NAT{0}", "default_name_format": "NAT{0}",
"category": "guest", "category": "guest",
"symbol": ":/symbols/cloud.svg", "symbol": "cloud",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -106,7 +106,7 @@ BUILTIN_TEMPLATES = [
"name": "VPCS", "name": "VPCS",
"default_name_format": "PC{0}", "default_name_format": "PC{0}",
"category": "guest", "category": "guest",
"symbol": ":/symbols/vpcs_guest.svg", "symbol": "vpcs_guest",
"base_script_file": "vpcs_base_config.txt", "base_script_file": "vpcs_base_config.txt",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
@ -118,7 +118,7 @@ BUILTIN_TEMPLATES = [
"console_type": "none", "console_type": "none",
"default_name_format": "Switch{0}", "default_name_format": "Switch{0}",
"category": "switch", "category": "switch",
"symbol": ":/symbols/ethernet_switch.svg", "symbol": "ethernet_switch",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -128,7 +128,7 @@ BUILTIN_TEMPLATES = [
"name": "Ethernet hub", "name": "Ethernet hub",
"default_name_format": "Hub{0}", "default_name_format": "Hub{0}",
"category": "switch", "category": "switch",
"symbol": ":/symbols/hub.svg", "symbol": "hub",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -138,7 +138,7 @@ BUILTIN_TEMPLATES = [
"name": "Frame Relay switch", "name": "Frame Relay switch",
"default_name_format": "FRSW{0}", "default_name_format": "FRSW{0}",
"category": "switch", "category": "switch",
"symbol": ":/symbols/frame_relay_switch.svg", "symbol": "frame_relay_switch",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -148,7 +148,7 @@ BUILTIN_TEMPLATES = [
"name": "ATM switch", "name": "ATM switch",
"default_name_format": "ATMSW{0}", "default_name_format": "ATMSW{0}",
"category": "switch", "category": "switch",
"symbol": ":/symbols/atm_switch.svg", "symbol": "atm_switch",
"compute_id": None, "compute_id": None,
"builtin": True, "builtin": True,
}, },
@ -163,6 +163,10 @@ class TemplatesService:
from gns3server.controller import Controller from gns3server.controller import Controller
self._controller = Controller.instance() self._controller = Controller.instance()
# resolve built-in template symbols
for builtin_template in BUILTIN_TEMPLATES:
builtin_template["symbol"] = self._controller.symbols.resolve_symbol(builtin_template["symbol"])
def get_builtin_template(self, template_id: UUID) -> dict: def get_builtin_template(self, template_id: UUID) -> dict:
for builtin_template in BUILTIN_TEMPLATES: for builtin_template in BUILTIN_TEMPLATES:
@ -241,6 +245,8 @@ class TemplatesService:
except pydantic.ValidationError as e: except pydantic.ValidationError as e:
raise ControllerBadRequestError(f"JSON schema error received while creating new template: {e}") raise ControllerBadRequestError(f"JSON schema error received while creating new template: {e}")
# resolve the template symbol
template_settings["symbol"] = self._controller.symbols.resolve_symbol(template_settings["symbol"])
images_to_add_to_template = await self._find_images(template_create.template_type, template_settings) images_to_add_to_template = await self._find_images(template_create.template_type, template_settings)
db_template = await self._templates_repo.create_template(template_create.template_type, template_settings) db_template = await self._templates_repo.create_template(template_create.template_type, template_settings)
for image in images_to_add_to_template: for image in images_to_add_to_template:

View File

@ -18,6 +18,7 @@
import os import os
import pytest import pytest
import uuid import uuid
import unittest.mock
from pathlib import Path from pathlib import Path
from fastapi import FastAPI, status from fastapi import FastAPI, status
@ -313,7 +314,7 @@ class TestDynamipsTemplate:
"ram": 512, "ram": 512,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -358,7 +359,7 @@ class TestDynamipsTemplate:
"ram": 256, "ram": 256,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -403,7 +404,7 @@ class TestDynamipsTemplate:
"ram": 128, "ram": 128,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -450,7 +451,7 @@ class TestDynamipsTemplate:
"ram": 192, "ram": 192,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -507,7 +508,7 @@ class TestDynamipsTemplate:
"ram": 192, "ram": 192,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -554,7 +555,7 @@ class TestDynamipsTemplate:
"ram": 160, "ram": 160,
"sparsemem": True, "sparsemem": True,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -613,7 +614,7 @@ class TestDynamipsTemplate:
"ram": 160, "ram": 160,
"sparsemem": False, "sparsemem": False,
"startup_config": "ios_base_startup-config.txt", "startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg", "symbol": unittest.mock.ANY,
"system_id": "FTX0945W0MY"} "system_id": "FTX0945W0MY"}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -674,7 +675,7 @@ class TestIOUTemplate:
"ram": 256, "ram": 256,
"serial_adapters": 2, "serial_adapters": 2,
"startup_config": "iou_l3_base_startup-config.txt", "startup_config": "iou_l3_base_startup-config.txt",
"symbol": ":/symbols/multilayer_switch.svg", "symbol": unittest.mock.ANY,
"use_default_iou_values": True, "use_default_iou_values": True,
"l1_keepalives": False} "l1_keepalives": False}
@ -711,7 +712,7 @@ class TestDockerTemplate:
"image": "gns3/endhost:latest", "image": "gns3/endhost:latest",
"name": "Docker template", "name": "Docker template",
"start_command": "", "start_command": "",
"symbol": ":/symbols/docker_guest.svg", "symbol": unittest.mock.ANY,
"custom_adapters": []} "custom_adapters": []}
for item, value in expected_response.items(): for item, value in expected_response.items():
@ -772,7 +773,7 @@ class TestQemuTemplate:
"process_priority": "normal", "process_priority": "normal",
"qemu_path": "", "qemu_path": "",
"ram": 512, "ram": 512,
"symbol": ":/symbols/qemu_guest.svg", "symbol": unittest.mock.ANY,
"usage": "", "usage": "",
"custom_adapters": []} "custom_adapters": []}
@ -810,7 +811,7 @@ class TestVMwareTemplate:
"on_close": "power_off", "on_close": "power_off",
"port_name_format": "Ethernet{0}", "port_name_format": "Ethernet{0}",
"port_segment_size": 0, "port_segment_size": 0,
"symbol": ":/symbols/vmware_guest.svg", "symbol": unittest.mock.ANY,
"use_any_adapter": False, "use_any_adapter": False,
"vmx_path": vmx_path, "vmx_path": vmx_path,
"custom_adapters": []} "custom_adapters": []}
@ -849,7 +850,7 @@ class TestVirtualBoxTemplate:
"port_name_format": "Ethernet{0}", "port_name_format": "Ethernet{0}",
"port_segment_size": 0, "port_segment_size": 0,
"ram": 256, "ram": 256,
"symbol": ":/symbols/vbox_guest.svg", "symbol": unittest.mock.ANY,
"use_any_adapter": False, "use_any_adapter": False,
"vmname": "My VirtualBox VM", "vmname": "My VirtualBox VM",
"custom_adapters": []} "custom_adapters": []}
@ -879,7 +880,7 @@ class TestVPCSTemplate:
"console_type": "telnet", "console_type": "telnet",
"default_name_format": "PC{0}", "default_name_format": "PC{0}",
"name": "VPCS template", "name": "VPCS template",
"symbol": ":/symbols/vpcs_guest.svg"} "symbol": unittest.mock.ANY}
for item, value in expected_response.items(): for item, value in expected_response.items():
assert response.json().get(item) == value assert response.json().get(item) == value
@ -952,7 +953,7 @@ class TestEthernetSwitchTemplate:
"type": "access", "type": "access",
"vlan": 1 "vlan": 1
}], }],
"symbol": ":/symbols/ethernet_switch.svg"} "symbol": unittest.mock.ANY}
for item, value in expected_response.items(): for item, value in expected_response.items():
assert response.json().get(item) == value assert response.json().get(item) == value
@ -995,7 +996,7 @@ class TestHubTemplate:
}], }],
"compute_id": "local", "compute_id": "local",
"name": "Ethernet hub template", "name": "Ethernet hub template",
"symbol": ":/symbols/hub.svg", "symbol": unittest.mock.ANY,
"default_name_format": "Hub{0}", "default_name_format": "Hub{0}",
"template_type": "ethernet_hub", "template_type": "ethernet_hub",
"category": "switch", "category": "switch",
@ -1024,7 +1025,7 @@ class TestCloudTemplate:
"default_name_format": "Cloud{0}", "default_name_format": "Cloud{0}",
"name": "Cloud template", "name": "Cloud template",
"ports_mapping": [], "ports_mapping": [],
"symbol": ":/symbols/cloud.svg", "symbol": unittest.mock.ANY,
"remote_console_host": "127.0.0.1", "remote_console_host": "127.0.0.1",
"remote_console_port": 23, "remote_console_port": 23,
"remote_console_type": "none", "remote_console_type": "none",

View File

@ -17,8 +17,8 @@
import os import os
from gns3server.controller.symbols import Symbols from gns3server.controller.symbols import Symbols
from gns3server.controller.symbol_themes import BUILTIN_SYMBOL_THEMES
from gns3server.utils.get_resource import get_resource from gns3server.utils.get_resource import get_resource
@ -49,6 +49,15 @@ def test_get_path():
assert symbols.get_path(':/symbols/classic/firewall.svg') == get_resource("symbols/classic/firewall.svg") assert symbols.get_path(':/symbols/classic/firewall.svg') == get_resource("symbols/classic/firewall.svg")
def test_get_path_with_themed_symbols():
symbols = Symbols()
for symbol_theme, symbols_table in BUILTIN_SYMBOL_THEMES.items():
symbols.theme = symbol_theme
for symbol_name, symbol_path in symbols_table.items():
assert symbols.get_path(symbol_name) == get_resource(symbol_path[2:])
def test_get_size(): def test_get_size():
symbols = Symbols() symbols = Symbols()