diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 6763ce51..e75eb4c2 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -13,10 +13,10 @@ on:
jobs:
build:
- runs-on: ubuntu-20.04 # Downgrade Ubuntu to 20.04 to fix missing Python 3.6
+ runs-on: ubuntu-22.04
strategy:
matrix:
- python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
diff --git a/CHANGELOG b/CHANGELOG
index 269b28c9..53d4cff6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,18 @@
# Change Log
+## 2.2.46 26/02/2024
+
+* Bundle web-ui v2.2.46
+* Save empty directories when exporting a project
+* Backport from v3: install Docker resources in a writable location at runtime.
+* Use Docker API v1.24 to get version.
+* Drop support for Python 3.6
+* Address the telnet console bug.
+* Update welcome.py
+* Update remote-install.sh
+* Use Python 3.8 to publish API doc
+* Upgrade sentry-sdk, psutil and distro dependencies
+
## 2.2.45 12/01/2024
* Bundle web-ui v2.2.45
diff --git a/gns3server/appliances/bird2.gns3a b/gns3server/appliances/bird2.gns3a
index 4a076d78..670d56a8 100644
--- a/gns3server/appliances/bird2.gns3a
+++ b/gns3server/appliances/bird2.gns3a
@@ -23,6 +23,14 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "bird2-debian-2.14.qcow2",
+ "version": "2.14",
+ "md5sum": "029cf1756201ee79497c169502b08b88",
+ "filesize": 303717376,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/bird2-debian-2.14.qcow2"
+ },
{
"filename": "bird2-debian-2.0.12.qcow2",
"version": "2.0.12",
@@ -33,6 +41,12 @@
}
],
"versions": [
+ {
+ "name": "2.14",
+ "images": {
+ "hda_disk_image": "bird2-debian-2.14.qcow2"
+ }
+ },
{
"name": "2.0.12",
"images": {
diff --git a/gns3server/appliances/cisco-nxosv9k.gns3a b/gns3server/appliances/cisco-nxosv9k.gns3a
index 00120391..951697fb 100644
--- a/gns3server/appliances/cisco-nxosv9k.gns3a
+++ b/gns3server/appliances/cisco-nxosv9k.gns3a
@@ -47,6 +47,34 @@
"filesize": 1990983680,
"download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/10.1(1)"
},
+ {
+ "filename": "nexus9500v.9.3.13.qcow2",
+ "version": "9500v 9.3.13",
+ "md5sum": "bacf0f664ee34625c85a9f278b2466a2",
+ "filesize": 2248409088,
+ "download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/9.3(13)"
+ },
+ {
+ "filename": "nexus9300v.9.3.13.qcow2",
+ "version": "9300v 9.3.13",
+ "md5sum": "d8ce30cb762df02d77ec27786a2435ad",
+ "filesize": 2248343552,
+ "download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/9.3(13)"
+ },
+ {
+ "filename": "nexus9500v.9.3.12.qcow2",
+ "version": "9500v 9.3.12",
+ "md5sum": "452e5cb2a7a25feaa3ba0624a82ff9ca",
+ "filesize": 1997996032,
+ "download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/9.3(12)"
+ },
+ {
+ "filename": "nexus9300v.9.3.12.qcow2",
+ "version": "9300v 9.3.12",
+ "md5sum": "7b6b5dad1001e11d6ebb54662616e9f2",
+ "filesize": 1997930496,
+ "download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/9.3(12)"
+ },
{
"filename": "nexus9500v.9.3.9.qcow2",
"version": "9500v 9.3.9",
@@ -226,6 +254,34 @@
"hda_disk_image": "nexus9300v.10.1.1.qcow2"
}
},
+ {
+ "name": "9500v 9.3.13",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "nexus9500v.9.3.13.qcow2"
+ }
+ },
+ {
+ "name": "9300v 9.3.13",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "nexus9300v.9.3.13.qcow2"
+ }
+ },
+ {
+ "name": "9500v 9.3.12",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "nexus9500v.9.3.12.qcow2"
+ }
+ },
+ {
+ "name": "9300v 9.3.12",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "nexus9300v.9.3.12.qcow2"
+ }
+ },
{
"name": "9500v 9.3.9",
"images": {
diff --git a/gns3server/appliances/fortianalyzer.gns3a b/gns3server/appliances/fortianalyzer.gns3a
index fb0aed44..db14866d 100644
--- a/gns3server/appliances/fortianalyzer.gns3a
+++ b/gns3server/appliances/fortianalyzer.gns3a
@@ -29,6 +29,13 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FAZ_VM64_KVM-v7.4.2-build2397-FORTINET.out.kvm.qcow2",
+ "version": "7.4.2",
+ "md5sum": "33b2938e19a6cb61d4d64e6998a54d23",
+ "filesize": 480096256,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
"version": "7.4.1",
@@ -57,6 +64,13 @@
"filesize": 340631552,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FAZ_VM64_KVM-v7.0.11-build0595-FORTINET.out.kvm.qcow2",
+ "version": "7.0.11",
+ "md5sum": "ae7ff37d00d0b518e9982a0785665fc2",
+ "filesize": 349257728,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
"version": "7.0.9",
@@ -78,6 +92,13 @@
"filesize": 334184448,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FAZ_VM64_KVM-v6.4.14-build2660-FORTINET.out.kvm.qcow2",
+ "version": "6.4.14",
+ "md5sum": "1629d76776c9d625faee0304b5840c02",
+ "filesize": 300683264,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
"version": "6.4.12",
@@ -235,6 +256,13 @@
}
],
"versions": [
+ {
+ "name": "7.4.2",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.4.2-build2397-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.4.1",
"images": {
@@ -263,6 +291,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.11",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.0.11-build0595-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.9",
"images": {
@@ -284,6 +319,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.14",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v6.4.14-build2660-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.12",
"images": {
diff --git a/gns3server/appliances/fortigate.gns3a b/gns3server/appliances/fortigate.gns3a
index bdb2ab95..77f2c7d7 100644
--- a/gns3server/appliances/fortigate.gns3a
+++ b/gns3server/appliances/fortigate.gns3a
@@ -28,6 +28,13 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FGT_VM64_KVM-v7.4.3.F-build2573-FORTINET.out.kvm.qcow2",
+ "version": "7.4.3",
+ "md5sum": "e7517095c91dce1a76a9bcf114b5fa15",
+ "filesize": 100728832,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.4.1.F-build2463-FORTINET.out.kvm.qcow2",
"version": "7.4.1",
@@ -35,6 +42,13 @@
"filesize": 116064256,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v7.2.7.M-build1577-FORTINET.out.kvm.qcow2",
+ "version": "7.2.7",
+ "md5sum": "752b56844e464b0b135e57f72681c288",
+ "filesize": 102760448,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.2.6.F-build1575-FORTINET.out.kvm.qcow2",
"version": "7.2.6",
@@ -63,6 +77,13 @@
"filesize": 86704128,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v7.0.14.M-build0601-FORTINET.out.kvm.qcow2",
+ "version": "7.0.14",
+ "md5sum": "20c855889e1117902bcf448b30746d30",
+ "filesize": 77398016,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.0.13.M-build0566-FORTINET.out.kvm.qcow2",
"version": "7.0.13",
@@ -91,6 +112,13 @@
"filesize": 77135872,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v6.4.15.M-build2095-FORTINET.out.kvm.qcow2",
+ "version": "6.4.15",
+ "md5sum": "a0306a3905877de654dab7e1bb67089e",
+ "filesize": 81592320,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v6.4.14.M-build2093-FORTINET.out.kvm.qcow2",
"version": "6.4.14",
@@ -339,6 +367,13 @@
}
],
"versions": [
+ {
+ "name": "7.4.3",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.4.3.F-build2573-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.4.1",
"images": {
@@ -346,6 +381,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.2.7",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.2.7.M-build1577-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.2.6",
"images": {
@@ -374,6 +416,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.14",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.0.14.M-build0601-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.13",
"images": {
@@ -402,6 +451,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.15",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v6.4.15.M-build2095-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.14",
"images": {
diff --git a/gns3server/appliances/fortimanager.gns3a b/gns3server/appliances/fortimanager.gns3a
index cc35e900..28c53e0b 100644
--- a/gns3server/appliances/fortimanager.gns3a
+++ b/gns3server/appliances/fortimanager.gns3a
@@ -13,7 +13,7 @@
"status": "stable",
"maintainer": "Ean Towne",
"maintainer_email": "ean.fortinet@gmail.com",
- "usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x require less CPU/RAM",
+ "usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x require less CPU/RAM\n7.4.x may require 16GB RAM.",
"symbol": "fortinet.svg",
"port_name_format": "Port{port1}",
"qemu": {
@@ -29,6 +29,13 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FMG_VM64_KVM-v7.4.2-build2397-FORTINET.out.kvm.qcow2",
+ "version": "7.4.2",
+ "md5sum": "36371fbf06210ded57c00b2ff290f2c5",
+ "filesize": 322514944,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
"version": "7.4.1",
@@ -57,6 +64,13 @@
"filesize": 242814976,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FMG_VM64_KVM-v7.0.11-build0595-FORTINET.out.kvm.qcow2",
+ "version": "7.0.11",
+ "md5sum": "7b166222136e26190159f37cccbaab6e",
+ "filesize": 249360384,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
"version": "7.0.9",
@@ -78,6 +92,13 @@
"filesize": 237535232,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FMG_VM64_KVM-v6.4.14-build2660-FORTINET.out.kvm.qcow2",
+ "version": "6.4.14",
+ "md5sum": "0fe56e363b166c07b710bde795e36049",
+ "filesize": 219430912,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
"version": "6.4.12",
@@ -235,6 +256,13 @@
}
],
"versions": [
+ {
+ "name": "7.4.2",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.4.2-build2397-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.4.1",
"images": {
@@ -263,6 +291,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.11",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.0.11-build0595-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.0.9",
"images": {
@@ -284,6 +319,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "6.4.14",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v6.4.14-build2660-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.12",
"images": {
diff --git a/gns3server/appliances/mikrotik-rb1100ahx4-dude-edition.gns3a b/gns3server/appliances/mikrotik-rb1100ahx4-dude-edition.gns3a
index 247943fb..9dd6a665 100644
--- a/gns3server/appliances/mikrotik-rb1100ahx4-dude-edition.gns3a
+++ b/gns3server/appliances/mikrotik-rb1100ahx4-dude-edition.gns3a
@@ -8,7 +8,7 @@
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
"product_name": "MikroTik RouterBOARD 1100AHx4 Dude Edition",
"product_url": "http://www.mikrotik.com/download",
- "registry_version": 5,
+ "registry_version": 4,
"status": "stable",
"maintainer": "Azorian Solutions",
"maintainer_email": "help@azorian.solutions",
diff --git a/gns3server/appliances/mikrotik-rb450g.gns3a b/gns3server/appliances/mikrotik-rb450g.gns3a
index b547386b..4573efb7 100644
--- a/gns3server/appliances/mikrotik-rb450g.gns3a
+++ b/gns3server/appliances/mikrotik-rb450g.gns3a
@@ -8,7 +8,7 @@
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
"product_name": "MikroTik RouterBOARD 450G",
"product_url": "http://www.mikrotik.com/download",
- "registry_version": 5,
+ "registry_version": 4,
"status": "stable",
"maintainer": "Azorian Solutions",
"maintainer_email": "help@azorian.solutions",
diff --git a/gns3server/appliances/mikrotik-rb450gx4.gns3a b/gns3server/appliances/mikrotik-rb450gx4.gns3a
index 385d412e..0cafd227 100644
--- a/gns3server/appliances/mikrotik-rb450gx4.gns3a
+++ b/gns3server/appliances/mikrotik-rb450gx4.gns3a
@@ -8,7 +8,7 @@
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
"product_name": "MikroTik RouterBOARD 450Gx4",
"product_url": "http://www.mikrotik.com/download",
- "registry_version": 5,
+ "registry_version": 4,
"status": "stable",
"maintainer": "Azorian Solutions",
"maintainer_email": "help@azorian.solutions",
diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py
index cc82daf9..7a31fb3b 100644
--- a/gns3server/compute/docker/__init__.py
+++ b/gns3server/compute/docker/__init__.py
@@ -19,11 +19,15 @@
Docker server module.
"""
+import os
import sys
import json
import asyncio
import logging
import aiohttp
+import shutil
+import platformdirs
+
from gns3server.utils import parse_version
from gns3server.utils.asyncio import locking
from gns3server.compute.base_manager import BaseManager
@@ -55,6 +59,62 @@ class Docker(BaseManager):
self._session = None
self._api_version = DOCKER_MINIMUM_API_VERSION
+ @staticmethod
+ async def install_busybox(dst_dir):
+
+ dst_busybox = os.path.join(dst_dir, "bin", "busybox")
+ if os.path.isfile(dst_busybox):
+ return
+ for busybox_exec in ("busybox-static", "busybox.static", "busybox"):
+ busybox_path = shutil.which(busybox_exec)
+ if busybox_path:
+ try:
+ # check that busybox is statically linked
+ # (dynamically linked busybox will fail to run in a container)
+ proc = await asyncio.create_subprocess_exec(
+ "ldd",
+ busybox_path,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.DEVNULL
+ )
+ stdout, _ = await proc.communicate()
+ if proc.returncode == 1:
+ # ldd returns 1 if the file is not a dynamic executable
+ log.info(f"Installing busybox from '{busybox_path}' to '{dst_busybox}'")
+ shutil.copy2(busybox_path, dst_busybox, follow_symlinks=True)
+ return
+ else:
+ log.warning(f"Busybox '{busybox_path}' is dynamically linked\n"
+ f"{stdout.decode('utf-8', errors='ignore').strip()}")
+ except OSError as e:
+ raise DockerError(f"Could not install busybox: {e}")
+ raise DockerError("No busybox executable could be found")
+
+ @staticmethod
+ def resources_path():
+ """
+ Get the Docker resources storage directory
+ """
+
+ appname = vendor = "GNS3"
+ docker_resources_dir = os.path.join(platformdirs.user_data_dir(appname, vendor, roaming=True), "docker", "resources")
+ os.makedirs(docker_resources_dir, exist_ok=True)
+ return docker_resources_dir
+
+ async def install_resources(self):
+ """
+ Copy the necessary resources to a writable location and install busybox
+ """
+
+ try:
+ dst_path = self.resources_path()
+ log.info(f"Installing Docker resources in '{dst_path}'")
+ from gns3server.controller import Controller
+ Controller.instance().install_resource_files(dst_path, "compute/docker/resources")
+ await self.install_busybox(dst_path)
+ except OSError as e:
+ raise DockerError(f"Could not install Docker resources to {dst_path}: {e}")
+
async def _check_connection(self):
if not self._connected:
@@ -135,7 +195,7 @@ class Docker(BaseManager):
timeout = 60 * 60 * 24 * 31 # One month timeout
if path == 'version':
- url = "http://docker/v1.12/" + path # API of docker v1.0
+ url = "http://docker/v1.24/" + path
else:
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
try:
diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py
index 500e526d..d1ad1456 100644
--- a/gns3server/compute/docker/docker_vm.py
+++ b/gns3server/compute/docker/docker_vm.py
@@ -242,10 +242,13 @@ class DockerVM(BaseNode):
:returns: Return the path that we need to map to local folders
"""
- resources = get_resource("compute/docker/resources")
- if not os.path.exists(resources):
- raise DockerError("{} is missing can't start Docker containers".format(resources))
- binds = ["{}:/gns3:ro".format(resources)]
+ try:
+ resources_path = self.manager.resources_path()
+ except OSError as e:
+ raise DockerError(f"Cannot access resources: {e}")
+
+ log.info(f'Mount resources from "{resources_path}"')
+ binds = ["{}:/gns3:ro".format(resources_path)]
# We mount our own etc/network
try:
@@ -460,6 +463,8 @@ class DockerVM(BaseNode):
Starts this Docker container.
"""
+ await self.manager.install_resources()
+
try:
state = await self._get_container_state()
except DockerHttp404Error:
diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py
index 4a66fe2d..adaefe67 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -300,6 +300,9 @@ class Controller:
if entry.is_file() and not os.path.exists(full_path):
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
+ elif entry.is_dir():
+ os.makedirs(full_path, exist_ok=True)
+ Controller.install_resource_files(full_path, os.path.join(resource_name, entry.name))
def _install_base_configs(self):
"""
diff --git a/gns3server/controller/export_project.py b/gns3server/controller/export_project.py
index fc7226fe..a1652f19 100644
--- a/gns3server/controller/export_project.py
+++ b/gns3server/controller/export_project.py
@@ -83,6 +83,11 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
continue
_patch_mtime(path)
zstream.write(path, os.path.relpath(path, project._path))
+ # save empty directories
+ for directory in dirs:
+ path = os.path.join(root, directory)
+ if not os.listdir(path):
+ zstream.write(path, os.path.relpath(path, project._path))
except FileNotFoundError as e:
log.warning("Cannot export local file: {}".format(e))
continue
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index 1c39f29a..2351e2d4 100644
--- a/gns3server/crash_report.py
+++ b/gns3server/crash_report.py
@@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service
"""
- DSN = "https://ffea69bec1b4f934d815234ea0046382@o19455.ingest.sentry.io/38482"
+ DSN = "https://535981c5fae1a87b7e8d993590c07c27@o19455.ingest.sentry.io/38482"
_instance = None
def __init__(self):
diff --git a/gns3server/run.py b/gns3server/run.py
index 672cc357..c15d976e 100644
--- a/gns3server/run.py
+++ b/gns3server/run.py
@@ -235,9 +235,9 @@ def run():
return
log.info("HTTP authentication is enabled with username '{}'".format(user))
- # we only support Python 3 version >= 3.6
- if sys.version_info < (3, 6, 0):
- raise SystemExit("Python 3.6 or higher is required")
+ # we only support Python 3 version >= 3.7
+ if sys.version_info < (3, 7, 0):
+ raise SystemExit("Python 3.7 or higher is required")
user_log.info("Running with Python {major}.{minor}.{micro} and has PID {pid}".format(major=sys.version_info[0], minor=sys.version_info[1],
micro=sys.version_info[2], pid=os.getpid()))
diff --git a/gns3server/static/web-ui/index.html b/gns3server/static/web-ui/index.html
index 2990d54f..b4f696d3 100644
--- a/gns3server/static/web-ui/index.html
+++ b/gns3server/static/web-ui/index.html
@@ -46,6 +46,6 @@
gtag('config', 'G-5D6FZL9923');
-
+