mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 08:56:06 +03:00
Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3b3d7565d | ||
|
|
1d0a173689 | ||
|
|
b2363434a2 | ||
|
|
ca3e6f0472 | ||
|
|
f1b19f4633 | ||
|
|
ed57ac3de5 | ||
|
|
6b35992e5a | ||
|
|
5f97d7891f | ||
|
|
6a46c26a37 | ||
|
|
d43411dafd | ||
|
|
0427383457 | ||
|
|
991387f483 | ||
|
|
8ff966dd54 | ||
|
|
271850cfec | ||
|
|
f09c67dd3d | ||
|
|
aea8e01d13 | ||
|
|
5615141ed7 | ||
|
|
c06491b112 | ||
|
|
b01c03855e | ||
|
|
01e101beac | ||
|
|
18eba37b85 | ||
|
|
3b7fb3329f | ||
|
|
5b901fa115 | ||
|
|
25bd1aa974 | ||
|
|
b0c374a043 | ||
|
|
548663c964 | ||
|
|
567cd6485f | ||
|
|
d69f00a370 | ||
|
|
1c6eb5eb4d | ||
|
|
d3c3ae5143 | ||
|
|
f2b2e6f618 | ||
|
|
a617d5aedf | ||
|
|
9e33cd24bb | ||
|
|
6f767d7455 | ||
|
|
f3ba40de43 | ||
|
|
7e60e4021b | ||
|
|
557c47d995 | ||
|
|
ce8f9206d9 | ||
|
|
1ac14e0f6d | ||
|
|
e0651e349c | ||
|
|
15889a0ac5 | ||
|
|
f38ab34ea0 | ||
|
|
087172d024 | ||
|
|
a46b08bea1 | ||
|
|
a6c3e2a4bb | ||
|
|
f89ff86808 | ||
|
|
50cdb2432d | ||
|
|
04ea58395a | ||
|
|
6331f54b88 | ||
|
|
a91683f6ff | ||
|
|
62b7d29e4c | ||
|
|
926aec9089 | ||
|
|
586b640967 | ||
|
|
52322eb982 | ||
|
|
340ec2d543 | ||
|
|
2d3c7ac0c2 | ||
|
|
3cd15e5f1a | ||
|
|
1a2d3d65c1 | ||
|
|
c93a543ccb | ||
|
|
579fb6ceb8 | ||
|
|
391ac73f1a | ||
|
|
eedec3f999 | ||
|
|
c103e2deba | ||
|
|
48e7ef07cf | ||
|
|
c6f8198974 | ||
|
|
c4561e81eb | ||
|
|
b3603ea364 | ||
|
|
dd44582347 | ||
|
|
270301d9b5 | ||
|
|
4614543edf | ||
|
|
80b7ece5a8 | ||
|
|
54511b2c45 | ||
|
|
ed36917cf3 | ||
|
|
7545c600bc | ||
|
|
4dd7db5a86 | ||
|
|
6d2ffc4614 | ||
|
|
bc14f15a61 | ||
|
|
2c7de627f7 | ||
|
|
76d9fcf60e | ||
|
|
c3049ea843 | ||
|
|
d6432c2e88 | ||
|
|
89b9e6c332 | ||
|
|
54b5d8f347 | ||
|
|
237ef785ad | ||
|
|
0ddd91af43 | ||
|
|
7f5db61722 | ||
|
|
fcc5c4c114 | ||
|
|
68f3dc763d | ||
|
|
786306304b | ||
|
|
4f631669e5 | ||
|
|
0b8fb93752 | ||
|
|
422f6004b1 | ||
|
|
717d683b44 | ||
|
|
932f737ed9 | ||
|
|
64a0ee37de | ||
|
|
ec8d214c08 | ||
|
|
880ac5e8c3 | ||
|
|
feb40a6250 | ||
|
|
fd7b915e96 | ||
|
|
5fbb6cbf61 | ||
|
|
fda948cc5b | ||
|
|
a9b18f1771 | ||
|
|
04f9a1cf8c | ||
|
|
af79471afd | ||
|
|
6067786783 | ||
|
|
090fc63bb6 | ||
|
|
029a1df7f7 | ||
|
|
b47aa95b3e | ||
|
|
ffb364591f | ||
|
|
6a9440c978 | ||
|
|
758054cfd3 | ||
|
|
39e6a6e2ab | ||
|
|
7a74685c0a | ||
|
|
f48eff2344 | ||
|
|
b8a583d3f6 | ||
|
|
d01f15c4df | ||
|
|
3cbad22a04 | ||
|
|
c61a99e78d | ||
|
|
e318610983 | ||
|
|
ab7cc29fa2 | ||
|
|
2788019e17 | ||
|
|
819bb1c58e | ||
|
|
723f806a52 | ||
|
|
a5093e06d1 | ||
|
|
f33c01ac58 | ||
|
|
8c382e5b7d |
49
CHANGELOG
49
CHANGELOG
@@ -1,5 +1,54 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.0a1 04/08/2022
|
||||
|
||||
* Set default symbol theme to "Affinity-square-blue"
|
||||
* Fix creating a custom Ethernet switch template
|
||||
* Update decorative symbols (for Wizards etc.)
|
||||
* Use generic symbol names
|
||||
* Set raw image param when uploading an image from the appliance wizard
|
||||
* Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes
|
||||
* Support compression levels
|
||||
* Add zstandard compression
|
||||
* Remove Qemu binary requirement
|
||||
* Use controller API to list images
|
||||
* Use new API endpoints to create/resize Qemu disk images.
|
||||
* Image management dialog
|
||||
* Drop Python 3.6 support and require Python >= 3.7
|
||||
* Improvements when connecting and updating computes
|
||||
* Use current directory when searching for images. Fixes #3198
|
||||
* Refactor server settings and wizard
|
||||
* Disable local server and GNS3 VM preferences
|
||||
* Image uploading to controller and project export
|
||||
* HTTP client refactoring
|
||||
* Handle empty compute_id in preferences. Ref #3265
|
||||
* Remove direct upload to compute
|
||||
* Send JWT token in query string when connecting to websocket. Ref https://github.com/GNS3/gns3-server/pull/1992
|
||||
* Remove traceng code
|
||||
* Option to delete orphaned image files from disk when template is removed. Fixes #3249
|
||||
* Remove Qemu legacy networking code
|
||||
* Isolate and unisolate support. Fixes https://github.com/GNS3/gns3-gui/issues/3190
|
||||
* Support authentication using JWT tokens
|
||||
* Providing the path to create a project is now deprecated.
|
||||
* Client to use version 3 of the API.
|
||||
* Change Qemu disk descriptions. Fixes #3035
|
||||
* Edit only text mode config files
|
||||
* Hide config import/export when configFiles attribute is empty
|
||||
* Qemu disk interfaces must be set to "none" by default. Ref #3035
|
||||
* Do not allow image to be configured on Qemu VM secondary slave disk if create config disk option is enabled.
|
||||
* Add explicit option to automatically create or not the config disk. Off by default.
|
||||
* Auxiliary console support for Qemu. Ref #2873 Improvements for auxiliary console support for Docker and Dynamips.
|
||||
* Support to reset all console connections. Ref https://github.com/GNS3/gns3-server/issues/1619
|
||||
* Support to reset links. Fixes https://github.com/GNS3/gns3-server/issues/1620
|
||||
* Fix bug when recent files cannot be seen in the new project dialog.
|
||||
* Wait for the controller to be online before allowing actions like creating or opening a project. Fixes #2907
|
||||
* Show progress dialog immediately when connecting to server. Ref #2907
|
||||
* QEMU config disk - enable QEMU config import/export
|
||||
* Add total RAM, CPUs and disk size to servers summary as well as disk usage in percent. Fixes https://github.com/GNS3/gns3-server/issues/1532
|
||||
* Resource constraints for Docker VMs.
|
||||
* Support for "usage" for "Cloud" nodes. Fixes https://github.com/GNS3/gns3-gui/issues/2887 Allow "usage" for all builtin nodes (not exposed in Ui).
|
||||
* Markdown support in project Readme. Fixes #2550 #2289 Allow project README to be edited from "File->Edit project". Fixes #2829
|
||||
|
||||
## 2.2.33.1 21/06/2022
|
||||
|
||||
* Match GNS3 server version
|
||||
|
||||
@@ -1,59 +1,45 @@
|
||||
GNS3-gui
|
||||
========
|
||||
# GNS3-gui
|
||||
|
||||
.. image:: https://github.com/GNS3/gns3-gui/workflows/testing/badge.svg
|
||||
:target: https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting
|
||||
[](https://github.com/GNS3/gns3-gui/actions?query=workflow%3Atesting)
|
||||
[](https://pypi.python.org/pypi/gns3-gui)
|
||||
[](https://snyk.io/test/github/GNS3/gns3-gui)
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
|
||||
:target: https://pypi.python.org/pypi/gns3-gui
|
||||
## Installation
|
||||
|
||||
.. image:: https://snyk.io/test/github/GNS3/gns3-gui/badge.svg
|
||||
:target: https://snyk.io/test/github/GNS3/gns3-gui
|
||||
Please see the documentation on our [website](https://docs.gns3.com)
|
||||
|
||||
## Software dependencies
|
||||
|
||||
GNS3 GUI repository.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Please see https://docs.gns3.com/
|
||||
|
||||
Software dependencies
|
||||
---------------------
|
||||
|
||||
PyQt5 which is either part of the Linux distribution or installable from PyPi. The other Python dependencies are automatically installed during the GNS3 GUI installation and are listed `here <https://github.com/GNS3/gns3-gui/blob/master/requirements.txt>`_
|
||||
PyQt5 which is either part of the Linux distribution or installable from PyPi. The other Python dependencies are automatically installed during the GNS3 GUI installation and are listed in [requirements.txt](https://github.com/GNS3/gns3-gui/blob/3.0/requirements.txt>)
|
||||
|
||||
For connecting to nodes using Telnet, a Telnet client is required. On Linux that's a terminal emulator like xterm, gnome-terminal, konsole plus the telnet program. For connecting to nodes with a GUI, a VNC client is required, optionally a SPICE client can be used for Qemu nodes.
|
||||
|
||||
For using packet captures within GNS3, Wireshark should be installed. It's recommended, but if you don't need that functionality you can go without it.
|
||||
|
||||
Development
|
||||
-------------
|
||||
## Development
|
||||
|
||||
If you want to update the interface, modify the .ui files using QT tools. And:
|
||||
|
||||
.. code:: bash
|
||||
```shell
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
```
|
||||
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
|
||||
Debug
|
||||
"""""
|
||||
### Debugging
|
||||
|
||||
If you want to see the full logs in the internal shell you can type:
|
||||
|
||||
.. code:: bash
|
||||
```shell
|
||||
debug 2
|
||||
```
|
||||
|
||||
debug 2
|
||||
|
||||
|
||||
Or start the app with --debug flag.
|
||||
|
||||
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
|
||||
https://github.com/Kozea/wdb
|
||||
|
||||
Security issues
|
||||
----------------
|
||||
## Security issues
|
||||
|
||||
Please contact us at security@gns3.net
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-rrequirements.txt
|
||||
-r requirements.txt
|
||||
|
||||
pytest==6.2.4
|
||||
flake8==3.9.2
|
||||
pytest-timeout==1.4.2
|
||||
pytest==6.2.5
|
||||
flake8==4.0.1
|
||||
pytest-timeout==2.0.1
|
||||
|
||||
@@ -49,7 +49,12 @@ class ApplianceManager(QtCore.QObject):
|
||||
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
|
||||
symbol_theme = settings["symbol_theme"]
|
||||
if update is True:
|
||||
self._controller.get("/appliances?update=yes&symbol_theme={}".format(symbol_theme), self._listAppliancesCallback, progressText="Downloading appliances from online registry...")
|
||||
self._controller.get(
|
||||
"/appliances?update=yes&symbol_theme={}".format(symbol_theme),
|
||||
self._listAppliancesCallback,
|
||||
progress_text="Downloading appliances from online registry...",
|
||||
wait=True
|
||||
)
|
||||
else:
|
||||
self._controller.get("/appliances?symbol_theme={}".format(symbol_theme), self._listAppliancesCallback)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Compute:
|
||||
self._password = None
|
||||
self._cpu_usage_percent = None
|
||||
self._memory_usage_percent = None
|
||||
self._disk_usage_percent = None
|
||||
self._capabilities = {"node_types": []}
|
||||
self._last_error = None
|
||||
|
||||
@@ -202,6 +203,24 @@ class Compute:
|
||||
|
||||
return self._memory_usage_percent
|
||||
|
||||
def setDiskUsagePercent(self, usage):
|
||||
"""
|
||||
Sets the compute disk usage.
|
||||
|
||||
:returns: disk usage (integer)
|
||||
"""
|
||||
|
||||
self._disk_usage_percent = usage
|
||||
|
||||
def diskUsagePercent(self):
|
||||
"""
|
||||
Returns the compute disk usage.
|
||||
|
||||
:param usage: disk usage (integer)
|
||||
"""
|
||||
|
||||
return self._disk_usage_percent
|
||||
|
||||
def capabilities(self):
|
||||
"""
|
||||
Returns the compute capabilities
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .qt import QtCore
|
||||
from .qt import QtCore, QtWidgets
|
||||
from .compute import Compute
|
||||
from .controller import Controller
|
||||
|
||||
@@ -50,11 +50,11 @@ class ComputeManager(QtCore.QObject):
|
||||
# No need to refresh via an API call if we received fresh data from the notification feed
|
||||
self._last_computes_refresh = datetime.datetime.now().timestamp()
|
||||
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.setInterval(1000)
|
||||
self._refreshingComputes = False
|
||||
self._timer.timeout.connect(self._refreshComputesSlot)
|
||||
self._timer.start()
|
||||
# self._timer = QtCore.QTimer()
|
||||
# self._timer.setInterval(1000)
|
||||
# self._timer.timeout.connect(self._refreshComputesSlot)
|
||||
# self._timer.start()
|
||||
|
||||
def _refreshComputesSlot(self):
|
||||
"""
|
||||
@@ -66,7 +66,7 @@ class ComputeManager(QtCore.QObject):
|
||||
if self._controller.connected() and datetime.datetime.now().timestamp() - self._last_computes_refresh > 1:
|
||||
self._last_computes_refresh = datetime.datetime.now().timestamp()
|
||||
self._refreshingComputes = True
|
||||
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=30)
|
||||
self._controller.get("/computes", self._listComputesCallback, show_progress=False, timeout=30)
|
||||
|
||||
def _controllerConnectedSlot(self):
|
||||
"""
|
||||
@@ -75,11 +75,11 @@ class ComputeManager(QtCore.QObject):
|
||||
|
||||
if self._controller.connected():
|
||||
self._refreshingComputes = True
|
||||
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=30)
|
||||
self._controller.get("/computes", self._listComputesCallback, show_progress=False, timeout=30)
|
||||
|
||||
def _controllerDisconnectedSlot(self):
|
||||
"""
|
||||
Called when disconnected from a compute.
|
||||
Called when disconnected from the controller.
|
||||
"""
|
||||
|
||||
for compute_id in list(self._computes):
|
||||
@@ -96,8 +96,9 @@ class ComputeManager(QtCore.QObject):
|
||||
log.error("Error while getting compute list: {}".format(result["message"]))
|
||||
return
|
||||
|
||||
for compute in result:
|
||||
self.computeDataReceivedCallback(compute)
|
||||
if result:
|
||||
for compute in result:
|
||||
self.computeDataReceivedCallback(compute)
|
||||
|
||||
def computeDataReceivedCallback(self, compute):
|
||||
"""
|
||||
@@ -113,15 +114,17 @@ class ComputeManager(QtCore.QObject):
|
||||
self._computes[compute_id] = Compute(compute_id)
|
||||
|
||||
self._computes[compute_id].setName(compute["name"])
|
||||
self._computes[compute_id].setConnected(compute["connected"])
|
||||
self._computes[compute_id].setProtocol(compute["protocol"])
|
||||
self._computes[compute_id].setHost(compute["host"])
|
||||
self._computes[compute_id].setPort(compute["port"])
|
||||
self._computes[compute_id].setUser(compute["user"])
|
||||
self._computes[compute_id].setCpuUsagePercent(compute["cpu_usage_percent"])
|
||||
self._computes[compute_id].setMemoryUsagePercent(compute["memory_usage_percent"])
|
||||
self._computes[compute_id].setCapabilities(compute["capabilities"])
|
||||
self._computes[compute_id].setLastError(compute.get("last_error"))
|
||||
if "connected" in compute:
|
||||
self._computes[compute_id].setConnected(compute["connected"])
|
||||
self._computes[compute_id].setCpuUsagePercent(compute["cpu_usage_percent"])
|
||||
self._computes[compute_id].setMemoryUsagePercent(compute["memory_usage_percent"])
|
||||
self._computes[compute_id].setDiskUsagePercent(compute["disk_usage_percent"])
|
||||
self._computes[compute_id].setCapabilities(compute["capabilities"])
|
||||
self._computes[compute_id].setLastError(compute.get("last_error"))
|
||||
|
||||
if new_node:
|
||||
self.created_signal.emit(compute_id)
|
||||
@@ -208,6 +211,20 @@ class ComputeManager(QtCore.QObject):
|
||||
self.created_signal.emit(compute_id)
|
||||
return self._computes[compute_id]
|
||||
|
||||
def connectToCompute(self, compute_id):
|
||||
"""
|
||||
Connect to a compute
|
||||
"""
|
||||
|
||||
self._controller.post(f"/computes/{compute_id}/connect", callback=self._computeConnectCallback)
|
||||
|
||||
def _computeConnectCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if error and "message" in result:
|
||||
from gns3.main_window import MainWindow
|
||||
parent = MainWindow.instance()
|
||||
QtWidgets.QMessageBox.critical(parent, "Remote compute", result.get("message"))
|
||||
|
||||
def deleteCompute(self, compute_id):
|
||||
"""
|
||||
Deletes a compute by ID
|
||||
@@ -245,10 +262,16 @@ class ComputeManager(QtCore.QObject):
|
||||
for compute in computes:
|
||||
if compute.id() not in self._computes:
|
||||
log.debug("Create compute %s", compute.id())
|
||||
self._controller.post("/computes", None, body=compute.__json__())
|
||||
params = {"connect": True}
|
||||
self._controller.post("/computes", callback=self._computeCreatedCallback, body=compute.__json__(), params=params)
|
||||
self._computes[compute.id()] = compute
|
||||
self.created_signal.emit(compute.id())
|
||||
|
||||
def _computeCreatedCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if error:
|
||||
log.error(result.get("message"))
|
||||
|
||||
@staticmethod
|
||||
def reset():
|
||||
ComputeManager._instance = None
|
||||
|
||||
@@ -23,6 +23,8 @@ from .qt import QtGui, QtCore, QtWidgets
|
||||
from .compute_manager import ComputeManager
|
||||
from .topology import Topology
|
||||
from .node import Node
|
||||
from .utils import human_size
|
||||
from .utils.get_icon import get_icon
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -43,9 +45,16 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
|
||||
self._compute = compute
|
||||
self._parent = parent
|
||||
self._status = "unknown"
|
||||
|
||||
self._refreshStatusSlot()
|
||||
|
||||
def getCompute(self):
|
||||
|
||||
return self._compute
|
||||
|
||||
def setCompute(self, compute):
|
||||
|
||||
self._compute = compute
|
||||
|
||||
def _refreshStatusSlot(self):
|
||||
"""
|
||||
Changes the icon to show the node status (started, stopped etc.)
|
||||
@@ -58,14 +67,20 @@ class ComputeItem(QtWidgets.QTreeWidgetItem):
|
||||
text = self._compute.name()
|
||||
|
||||
if self._compute.cpuUsagePercent() is not None:
|
||||
text = "{} CPU {}%, RAM {}%".format(text, self._compute.cpuUsagePercent(), self._compute.memoryUsagePercent())
|
||||
text = "{} CPU {}%, RAM {}%, DISK {}%".format(text,
|
||||
self._compute.cpuUsagePercent(),
|
||||
self._compute.memoryUsagePercent(),
|
||||
self._compute.diskUsagePercent())
|
||||
|
||||
self.setText(0, text)
|
||||
if self._compute.connected():
|
||||
self._status = "connected"
|
||||
self.setToolTip(0, "Server {} version {} running on {}".format(self._compute.name(),
|
||||
self._compute.capabilities().get("version", "n/a"),
|
||||
self._compute.capabilities().get("platform", "")))
|
||||
self.setToolTip(0, "Server {} v{} running on {} (CPUs={} / RAM={} / DISK={})".format(self._compute.name(),
|
||||
self._compute.capabilities().get("version", "n/a"),
|
||||
self._compute.capabilities().get("platform", ""),
|
||||
self._compute.capabilities().get("cpus", 0),
|
||||
human_size(self._compute.capabilities().get("memory", 0)),
|
||||
human_size(self._compute.capabilities().get("disk_size", 0))))
|
||||
if usage is None or (self._compute.cpuUsagePercent() < 90 and self._compute.memoryUsagePercent() < 90):
|
||||
self.setIcon(0, QtGui.QIcon(':/icons/led_green.svg'))
|
||||
else:
|
||||
@@ -111,13 +126,44 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
|
||||
def __init__(self, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
self._computes = {}
|
||||
self._compute_items = {}
|
||||
ComputeManager.instance().created_signal.connect(self._computeAddedSlot)
|
||||
ComputeManager.instance().updated_signal.connect(self._computeUpdatedSlot)
|
||||
ComputeManager.instance().deleted_signal.connect(self._computeRemovedSlot)
|
||||
for compute in ComputeManager.instance().computes():
|
||||
self._computeAddedSlot(compute.id())
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""
|
||||
Handles all context menu events.
|
||||
|
||||
:param event: QContextMenuEvent instance
|
||||
"""
|
||||
|
||||
self._showContextualMenu(event.globalPos())
|
||||
|
||||
def _showContextualMenu(self, pos):
|
||||
|
||||
item = self.currentItem()
|
||||
if item:
|
||||
compute = item.getCompute()
|
||||
if not compute.connected():
|
||||
menu = QtWidgets.QMenu()
|
||||
connect_action = QtWidgets.QAction("Connect to server", menu)
|
||||
connect_action.setIcon(get_icon("start.svg"))
|
||||
connect_action.triggered.connect(lambda: ComputeManager.instance().connectToCompute(compute.id()))
|
||||
menu.addAction(connect_action)
|
||||
menu.exec_(pos)
|
||||
|
||||
def _computeConnectSlot(self, compute_id):
|
||||
"""
|
||||
|
||||
:param compute_id:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ComputeManager.instance().connectToCompute(compute_id)
|
||||
|
||||
def _computeAddedSlot(self, compute_id):
|
||||
"""
|
||||
Called when a compute is added to the list of computes
|
||||
@@ -128,7 +174,7 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
|
||||
compute = ComputeManager.instance().getCompute(compute_id)
|
||||
if ComputeManager.instance().computeIsTheRemoteGNS3VM(compute):
|
||||
return
|
||||
self._computes[compute_id] = ComputeItem(self, compute)
|
||||
self._compute_items[compute_id] = ComputeItem(self, compute)
|
||||
|
||||
def _computeUpdatedSlot(self, compute_id):
|
||||
"""
|
||||
@@ -137,13 +183,14 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
|
||||
:params url: URL of the compute
|
||||
"""
|
||||
|
||||
if compute_id in self._computes:
|
||||
if compute_id in self._compute_items:
|
||||
compute = ComputeManager.instance().getCompute(compute_id)
|
||||
# We hide the remote GNS3 VM
|
||||
if ComputeManager.instance().computeIsTheRemoteGNS3VM(compute):
|
||||
self._computeRemovedSlot(compute_id)
|
||||
else:
|
||||
self._computes[compute_id]._refreshStatusSlot()
|
||||
self._compute_items[compute_id].setCompute(compute)
|
||||
self._compute_items[compute_id]._refreshStatusSlot()
|
||||
else:
|
||||
self._computeAddedSlot(compute_id)
|
||||
|
||||
@@ -154,6 +201,6 @@ class ComputeSummaryView(QtWidgets.QTreeWidget):
|
||||
:params url: URL of the compute
|
||||
"""
|
||||
|
||||
if compute_id in self._computes:
|
||||
self.takeTopLevelItem(self.indexOfTopLevelItem(self._computes[compute_id]))
|
||||
del self._computes[compute_id]
|
||||
if compute_id in self._compute_items:
|
||||
self.takeTopLevelItem(self.indexOfTopLevelItem(self._compute_items[compute_id]))
|
||||
del self._compute_items[compute_id]
|
||||
|
||||
@@ -21,11 +21,12 @@ import tempfile
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
from .qt import QtCore, QtNetwork, QtGui, QtWidgets, QtWebSockets, qpartial, qslot
|
||||
from .qt import QtCore, QtGui, QtWebSockets, qpartial, qslot
|
||||
from .symbol import Symbol
|
||||
from .local_server_config import LocalServerConfig
|
||||
from .settings import LOCAL_SERVER_SETTINGS
|
||||
from .local_config import LocalConfig
|
||||
from .settings import CONTROLLER_SETTINGS
|
||||
from gns3.utils import parse_version
|
||||
from gns3.http_client import HTTPClient
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -40,6 +41,7 @@ class Controller(QtCore.QObject):
|
||||
disconnected_signal = QtCore.Signal()
|
||||
connection_failed_signal = QtCore.Signal()
|
||||
project_list_updated_signal = QtCore.Signal()
|
||||
image_list_updated_signal = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -48,22 +50,54 @@ class Controller(QtCore.QObject):
|
||||
self._connecting = False
|
||||
self._notification_stream = None
|
||||
self._version = None
|
||||
self._http_client = None
|
||||
self._cache_directory = tempfile.TemporaryDirectory(suffix="-gns3")
|
||||
self._http_client = None
|
||||
self._first_error = True
|
||||
self._error_dialog = None
|
||||
self._display_error = True
|
||||
self._projects = []
|
||||
self._images = []
|
||||
self._websocket = QtWebSockets.QWebSocket()
|
||||
|
||||
# If we do multiple call in order to download the same symbol we queue them
|
||||
self._static_asset_download_queue = {}
|
||||
|
||||
self._loadSettings()
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Returns the graphics view settings.
|
||||
|
||||
:returns: settings dictionary
|
||||
"""
|
||||
|
||||
return self._settings
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
Loads the settings from the persistent settings file.
|
||||
"""
|
||||
|
||||
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, CONTROLLER_SETTINGS)
|
||||
|
||||
def setSettings(self, new_settings):
|
||||
"""
|
||||
Set new controller settings.
|
||||
|
||||
:param new_settings: settings dictionary
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
self._settings.update(new_settings)
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
def host(self):
|
||||
|
||||
return self._http_client.host()
|
||||
|
||||
def version(self):
|
||||
|
||||
return self._version
|
||||
|
||||
def isRemote(self):
|
||||
@@ -71,8 +105,7 @@ class Controller(QtCore.QObject):
|
||||
:returns Boolean: True if the controller is remote
|
||||
"""
|
||||
|
||||
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
|
||||
return not settings["auto_start"]
|
||||
return self._settings["remote"]
|
||||
|
||||
def connecting(self):
|
||||
"""
|
||||
@@ -102,10 +135,8 @@ class Controller(QtCore.QObject):
|
||||
|
||||
self._http_client = http_client
|
||||
if self._http_client:
|
||||
if self.isRemote():
|
||||
self._http_client.setMaxTimeDifferenceBetweenQueries(120)
|
||||
self._http_client.connection_connected_signal.connect(self._httpClientConnectedSlot)
|
||||
self._http_client.connection_disconnected_signal.connect(self._httpClientDisconnectedSlot)
|
||||
self._http_client.connected_signal.connect(self._httpClientConnectedSlot)
|
||||
self._http_client.disconnected_signal.connect(self._httpClientDisconnectedSlot)
|
||||
self._connectingToServer()
|
||||
|
||||
def getHttpClient(self):
|
||||
@@ -123,6 +154,14 @@ class Controller(QtCore.QObject):
|
||||
self._display_error = val
|
||||
self._first_error = True
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connect to controller
|
||||
"""
|
||||
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
|
||||
def _connectingToServer(self):
|
||||
"""
|
||||
Connection process as started
|
||||
@@ -130,46 +169,16 @@ class Controller(QtCore.QObject):
|
||||
|
||||
self._connected = False
|
||||
self._connecting = True
|
||||
status, json_data = self.httpClient().getSynchronous('GET', '/version', timeout=60)
|
||||
self._versionGetSlot(json_data, status is None or status >= 300)
|
||||
self.httpClient().connectToServer()
|
||||
|
||||
def _httpClientDisconnectedSlot(self):
|
||||
|
||||
if self._connected:
|
||||
self._connected = False
|
||||
self.disconnected_signal.emit()
|
||||
self._connectingToServer()
|
||||
self.stopListenNotifications()
|
||||
|
||||
def _versionGetSlot(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Called after the initial version get
|
||||
"""
|
||||
|
||||
if error:
|
||||
if self._first_error:
|
||||
self._connecting = False
|
||||
self.connection_failed_signal.emit()
|
||||
if self._display_error:
|
||||
self._error_dialog = QtWidgets.QMessageBox(self.parent())
|
||||
self._error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
self._error_dialog.setWindowTitle("Connection to server")
|
||||
if result and "message" in result:
|
||||
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
|
||||
else:
|
||||
self._error_dialog.setText("Cannot connect to the GNS3 server")
|
||||
self._error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
self._error_dialog.show()
|
||||
# Try to connect again in 5 seconds
|
||||
QtCore.QTimer.singleShot(5000, qpartial(self.get, '/version', self._versionGetSlot, showProgress=self._first_error))
|
||||
self._first_error = False
|
||||
else:
|
||||
self._first_error = True
|
||||
if self._error_dialog:
|
||||
self._error_dialog.reject()
|
||||
self._error_dialog = None
|
||||
self._version = result.get("version")
|
||||
self._http_client.connection_connected_signal.emit()
|
||||
|
||||
def _httpClientConnectedSlot(self):
|
||||
|
||||
if not self._connected:
|
||||
@@ -180,16 +189,16 @@ class Controller(QtCore.QObject):
|
||||
self._startListenNotifications()
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.createHTTPQuery("POST", *args, **kwargs)
|
||||
return self.request("POST", *args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.createHTTPQuery("GET", *args, **kwargs)
|
||||
return self.request("GET", *args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
return self.createHTTPQuery("PUT", *args, **kwargs)
|
||||
return self.request("PUT", *args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
return self.createHTTPQuery("DELETE", *args, **kwargs)
|
||||
return self.request("DELETE", *args, **kwargs)
|
||||
|
||||
def getCompute(self, path, compute_id, *args, **kwargs):
|
||||
"""
|
||||
@@ -225,31 +234,13 @@ class Controller(QtCore.QObject):
|
||||
return compute_id
|
||||
return compute_id
|
||||
|
||||
def getEndpoint(self, path, compute_id, *args, **kwargs):
|
||||
"""
|
||||
API post on a specific compute
|
||||
"""
|
||||
|
||||
compute_id = self.__fix_compute_id(compute_id)
|
||||
path = "/computes/endpoint/{}{}".format(compute_id, path)
|
||||
return self.get(path, *args, **kwargs)
|
||||
|
||||
def putCompute(self, path, compute_id, *args, **kwargs):
|
||||
"""
|
||||
API put on a specific compute
|
||||
"""
|
||||
|
||||
compute_id = self.__fix_compute_id(compute_id)
|
||||
path = "/computes/{}{}".format(compute_id, path)
|
||||
return self.put(path, *args, **kwargs)
|
||||
|
||||
def createHTTPQuery(self, method, path, *args, **kwargs):
|
||||
def request(self, method, path, *args, **kwargs):
|
||||
"""
|
||||
Forward the query to the HTTP client or controller depending of the path
|
||||
"""
|
||||
|
||||
if self._http_client:
|
||||
return self._http_client.createHTTPQuery(method, path, *args, **kwargs)
|
||||
return self._http_client.sendRequest(method, path, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
@@ -282,9 +273,10 @@ class Controller(QtCore.QObject):
|
||||
self._static_asset_download_queue[path].append((callback, fallback, ))
|
||||
else:
|
||||
self._static_asset_download_queue[path] = [(callback, fallback, )]
|
||||
self._http_client.createHTTPQuery("GET", url, qpartial(self._getStaticCallback, url, path))
|
||||
self._http_client.sendRequest("GET", url, qpartial(self._getStaticCallback, url, path), raw=True)
|
||||
|
||||
def _getStaticCallback(self, url, path, result, error=False, **kwargs):
|
||||
|
||||
def _getStaticCallback(self, url, path, result, error=False, raw_body=None, **kwargs):
|
||||
if path not in self._static_asset_download_queue:
|
||||
return
|
||||
|
||||
@@ -300,7 +292,7 @@ class Controller(QtCore.QObject):
|
||||
return
|
||||
try:
|
||||
with open(path, "wb+") as f:
|
||||
f.write(raw_body)
|
||||
f.write(result)
|
||||
except OSError as e:
|
||||
log.error("Can't write to {}: {}".format(path, str(e)))
|
||||
return
|
||||
@@ -364,9 +356,14 @@ class Controller(QtCore.QObject):
|
||||
|
||||
def uploadSymbol(self, symbol_id, path):
|
||||
|
||||
self.post("/symbols/" + symbol_id + "/raw",
|
||||
qpartial(self._finishSymbolUpload, path),
|
||||
body=pathlib.Path(path), progressText="Uploading {}".format(symbol_id), timeout=None)
|
||||
self.post(
|
||||
"/symbols/" + symbol_id + "/raw",
|
||||
qpartial(self._finishSymbolUpload, path),
|
||||
body=pathlib.Path(path),
|
||||
progress_text="Uploading {}".format(symbol_id),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _finishSymbolUpload(self, path, result, error=False, **kwargs):
|
||||
|
||||
@@ -407,23 +404,34 @@ class Controller(QtCore.QObject):
|
||||
def projects(self):
|
||||
return self._projects
|
||||
|
||||
@qslot
|
||||
def refreshImageList(self, *args):
|
||||
self.get("/images", self._imageListCallback)
|
||||
|
||||
def _imageListCallback(self, result, error=False, **kwargs):
|
||||
if not error:
|
||||
self._images = result
|
||||
self.image_list_updated_signal.emit()
|
||||
|
||||
def images(self):
|
||||
return self._images
|
||||
|
||||
def _startListenNotifications(self):
|
||||
if not self.connected():
|
||||
return
|
||||
|
||||
# Due to bug in Qt on some version we need a dedicated network manager
|
||||
self._notification_network_manager = QtNetwork.QNetworkAccessManager()
|
||||
self._notification_stream = None
|
||||
|
||||
# Qt websocket before Qt 5.6 doesn't support auth
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
|
||||
self._notification_stream = Controller.instance().createHTTPQuery("GET", "/notifications", self._endListenNotificationCallback,
|
||||
downloadProgressCallback=self._event_received,
|
||||
networkManager=self._notification_network_manager,
|
||||
timeout=None,
|
||||
showProgress=False,
|
||||
ignoreErrors=True)
|
||||
|
||||
self._notification_stream = Controller.instance().request(
|
||||
"GET",
|
||||
"/notifications",
|
||||
self._endListenNotificationCallback,
|
||||
download_progress_callback=self._event_received,
|
||||
timeout=None,
|
||||
show_progress=False
|
||||
)
|
||||
else:
|
||||
self._notification_stream = self._http_client.connectWebSocket(self._websocket, "/notifications/ws")
|
||||
self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
|
||||
@@ -436,7 +444,6 @@ class Controller(QtCore.QObject):
|
||||
stream = self._notification_stream
|
||||
self._notification_stream = None
|
||||
stream.abort()
|
||||
self._notification_network_manager = None
|
||||
|
||||
def _endListenNotificationCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -450,8 +457,7 @@ class Controller(QtCore.QObject):
|
||||
def _websocket_error(self, error):
|
||||
if self._notification_stream:
|
||||
log.error("Websocket notification stream error: {}".format(self._notification_stream.errorString()))
|
||||
self._notification_stream = None
|
||||
self._startListenNotifications()
|
||||
self.stopListenNotifications()
|
||||
|
||||
@qslot
|
||||
def _sslErrorsSlot(self, ssl_errors):
|
||||
|
||||
@@ -51,7 +51,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://439b7b27129f4c46971fe7bdc38738de@o19455.ingest.sentry.io/38506"
|
||||
DSN = "https://c2d4584e0a7b41bb8139956a84967e35@o19455.ingest.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from ..qt import sip
|
||||
import shutil
|
||||
|
||||
@@ -29,7 +30,7 @@ from ..registry.registry import Registry
|
||||
from ..registry.config import Config
|
||||
from ..registry.appliance_to_template import ApplianceToTemplate
|
||||
from ..registry.image import Image
|
||||
from ..utils import human_filesize
|
||||
from ..utils import human_size
|
||||
from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..compute_manager import ComputeManager
|
||||
@@ -52,7 +53,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
self.setupUi(self)
|
||||
self._refreshing = False
|
||||
self._server_check = False
|
||||
self._template_created = False
|
||||
self._path = path
|
||||
|
||||
@@ -75,6 +75,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
# directories where to search for images
|
||||
images_directories = list()
|
||||
|
||||
# add the current directory
|
||||
if hasattr(sys, "frozen"):
|
||||
images_directories.append(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
images_directories.append(os.path.abspath(os.curdir))
|
||||
|
||||
for emulator in ("QEMU", "IOU", "DYNAMIPS"):
|
||||
emulator_images_dir = ImageManager.instance().getDirectoryForType(emulator)
|
||||
if os.path.exists(emulator_images_dir):
|
||||
@@ -100,9 +106,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
# customize the server selection
|
||||
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
|
||||
if hasattr(self, "uiVMRadioButton"):
|
||||
self.uiVMRadioButton.toggled.connect(self._vmToggledSlot)
|
||||
|
||||
self.uiLocalRadioButton.toggled.connect(self._localToggledSlot)
|
||||
if Controller.instance().isRemote():
|
||||
self.uiLocalRadioButton.setText("Install the appliance on the main server")
|
||||
@@ -136,7 +139,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
# add symbol
|
||||
if self._appliance["category"] == "guest":
|
||||
symbol = ":/symbols/computer.svg"
|
||||
if self._appliance.emulator() == "docker":
|
||||
symbol = ":/symbols/docker_guest.svg"
|
||||
elif self._appliance.emulator() == "qemu":
|
||||
symbol = ":/symbols/qemu_guest.svg"
|
||||
else:
|
||||
symbol = ":/symbols/computer.svg"
|
||||
else:
|
||||
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
|
||||
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
|
||||
@@ -144,21 +152,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if self.page(page_id) == self.uiServerWizardPage:
|
||||
|
||||
Controller.instance().getSymbols(self._getSymbolsCallback)
|
||||
|
||||
if "qemu" in self._appliance:
|
||||
emulator_type = "qemu"
|
||||
elif "iou" in self._appliance:
|
||||
emulator_type = "iou"
|
||||
elif "docker" in self._appliance:
|
||||
emulator_type = "docker"
|
||||
elif "dynamips" in self._appliance:
|
||||
emulator_type = "dynamips"
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "Appliance", "Could not determine the emulator type")
|
||||
|
||||
is_mac = ComputeManager.instance().localPlatform().startswith("darwin")
|
||||
is_win = ComputeManager.instance().localPlatform().startswith("win")
|
||||
|
||||
self.uiRemoteServersComboBox.clear()
|
||||
if len(ComputeManager.instance().remoteComputes()) == 0:
|
||||
self.uiRemoteRadioButton.setEnabled(False)
|
||||
@@ -167,66 +160,27 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
for compute in ComputeManager.instance().remoteComputes():
|
||||
self.uiRemoteServersComboBox.addItem(compute.name(), compute)
|
||||
|
||||
if not ComputeManager.instance().vmCompute():
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
#if ComputeManager.instance().localPlatform() is None:
|
||||
# self.uiLocalRadioButton.setEnabled(False)
|
||||
|
||||
if ComputeManager.instance().localPlatform() is None:
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif is_mac or is_win:
|
||||
if emulator_type == "qemu":
|
||||
# disallow usage of the local server because Qemu has issues on OSX and Windows
|
||||
if not LocalConfig.instance().experimental():
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif emulator_type != "dynamips":
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
|
||||
if ComputeManager.instance().vmCompute():
|
||||
self.uiVMRadioButton.setChecked(True)
|
||||
elif ComputeManager.instance().localCompute() and self.uiLocalRadioButton.isEnabled():
|
||||
if ComputeManager.instance().localCompute() and self.uiLocalRadioButton.isEnabled():
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
elif self.uiRemoteRadioButton.isEnabled():
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
else:
|
||||
self.uiRemoteRadioButton.setChecked(False)
|
||||
|
||||
if is_mac or is_win:
|
||||
if not self.uiRemoteRadioButton.isEnabled() and not self.uiVMRadioButton.isEnabled() and not self.uiLocalRadioButton.isEnabled():
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "The GNS3 VM is not available, please configure the GNS3 VM before adding a new appliance.")
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
if Controller.instance().isRemote() or self._compute_id != "local":
|
||||
self._registry.getRemoteImageList(self._appliance.emulator(), self._compute_id)
|
||||
else:
|
||||
self.images_changed_signal.emit()
|
||||
|
||||
elif self.page(page_id) == self.uiQemuWizardPage:
|
||||
if self._appliance['qemu'].get('kvm', 'require') == 'require':
|
||||
self._server_check = False
|
||||
Qemu.instance().getQemuCapabilitiesFromServer(self._compute_id, qpartial(self._qemuServerCapabilitiesCallback))
|
||||
else:
|
||||
self._server_check = True
|
||||
Qemu.instance().getQemuBinariesFromServer(self._compute_id, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
|
||||
|
||||
elif self.page(page_id) == self.uiUsageWizardPage:
|
||||
self.uiUsageTextEdit.setText("The template will be available in the {} category.\n\n{}".format(self._appliance["category"].replace("_", " "), self._appliance.get("usage", "")))
|
||||
|
||||
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
|
||||
"""
|
||||
Check if the server supports KVM or not
|
||||
"""
|
||||
|
||||
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
|
||||
self._server_check = True
|
||||
else:
|
||||
if error:
|
||||
msg = result["message"]
|
||||
else:
|
||||
msg = "The selected server does not support KVM. A Linux server or the GNS3 VM running in VMware is required."
|
||||
QtWidgets.QMessageBox.critical(self, "KVM support", msg)
|
||||
self._server_check = False
|
||||
|
||||
def _uiServerWizardPage_isComplete(self):
|
||||
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
|
||||
return self.uiRemoteRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
|
||||
|
||||
def _imageUploadedCallback(self, result, error=False, context=None, **kwargs):
|
||||
if context is None:
|
||||
@@ -347,7 +301,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
size += image.get("filesize", 0)
|
||||
image_widget = QtWidgets.QTreeWidgetItem([image["filename"],
|
||||
human_filesize(image.get("filesize", 0)),
|
||||
human_size(image.get("filesize", 0)),
|
||||
image["status"]])
|
||||
if image["status"] == "Missing":
|
||||
image_widget.setForeground(2, QtGui.QBrush(QtGui.QColor("red")))
|
||||
@@ -372,7 +326,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
expand = False
|
||||
top.setForeground(2, QtGui.QBrush(QtGui.QColor("green")))
|
||||
|
||||
top.setData(1, QtCore.Qt.DisplayRole, human_filesize(size))
|
||||
top.setData(1, QtCore.Qt.DisplayRole, human_size(size))
|
||||
top.setData(2, QtCore.Qt.DisplayRole, status)
|
||||
top.setData(0, QtCore.Qt.UserRole, version)
|
||||
top.setData(2, QtCore.Qt.UserRole, self._appliance)
|
||||
@@ -531,33 +485,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Can't access to the image file {}: {}.".format(path, str(e)))
|
||||
return
|
||||
|
||||
image_upload_manger = ImageUploadManager(image, Controller.instance(), self._compute_id, self._imageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manger = ImageUploadManager(image, Controller.instance(), self.parent())
|
||||
image_upload_manger.upload()
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getQemuBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiQemuListComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
|
||||
else:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
if self.uiQemuListComboBox.count() == 1:
|
||||
self.next()
|
||||
else:
|
||||
i = self.uiQemuListComboBox.findData(self._appliance["qemu"]["arch"], flags=QtCore.Qt.MatchEndsWith)
|
||||
if i != -1:
|
||||
self.uiQemuListComboBox.setCurrentIndex(i)
|
||||
|
||||
def _install(self, version):
|
||||
"""
|
||||
Install the appliance in GNS3
|
||||
@@ -585,9 +515,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
return False
|
||||
appliance_configuration["name"] = appliance_configuration["name"].strip()
|
||||
|
||||
if "qemu" in appliance_configuration:
|
||||
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
|
||||
new_template = ApplianceToTemplate().new_template(appliance_configuration, self._compute_id, self._symbols, parent=self)
|
||||
TemplateManager.instance().createTemplate(Template(new_template), callback=self._templateCreatedCallback)
|
||||
return False
|
||||
@@ -626,34 +553,22 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
appliance_configuration = self._appliance.search_images_for_version(version)
|
||||
except ApplianceError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Appliance","Cannot install {} version {}: {}".format(name, version, e))
|
||||
return
|
||||
return False
|
||||
for image in appliance_configuration["images"]:
|
||||
if image["location"] == "local":
|
||||
if not Controller.instance().isRemote() and self._compute_id == "local" and image["path"].startswith(ImageManager.instance().getDirectory()):
|
||||
log.debug("{} is already on the local server".format(image["path"]))
|
||||
return
|
||||
image = Image(self._appliance.emulator(), image["path"], filename=image["filename"])
|
||||
image_upload_manager = ImageUploadManager(image, Controller.instance(), self._compute_id, self._applianceImageUploadedCallback, LocalConfig.instance().directFileUpload())
|
||||
image_upload_manager.upload()
|
||||
self._image_uploading_count += 1
|
||||
|
||||
def _applianceImageUploadedCallback(self, result, error=False, context=None, **kwargs):
|
||||
if context is None:
|
||||
context = {}
|
||||
image_path = context.get("image_path", "unknown")
|
||||
if error:
|
||||
log.error("Error while uploading image '{}': {}".format(image_path, result["message"]))
|
||||
else:
|
||||
log.info("Image '{}' has been successfully uploaded".format(image_path))
|
||||
self._image_uploading_count -= 1
|
||||
image_upload_manager = ImageUploadManager(image, Controller.instance(), self.parent())
|
||||
if not image_upload_manager.upload():
|
||||
return False
|
||||
return True
|
||||
|
||||
def nextId(self):
|
||||
if self.currentPage() == self.uiServerWizardPage:
|
||||
if "docker" in self._appliance:
|
||||
# skip Qemu binary selection and files pages if this is a Docker appliance
|
||||
return super().nextId() + 2
|
||||
elif "qemu" not in self._appliance:
|
||||
# skip the Qemu binary selection page if not a Qemu appliance
|
||||
return super().nextId() + 1
|
||||
return super().nextId()
|
||||
|
||||
@@ -684,7 +599,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return False
|
||||
|
||||
self._uploadImages(appliance["name"], version["name"])
|
||||
return self._uploadImages(appliance["name"], version["name"])
|
||||
|
||||
elif self.currentPage() == self.uiUsageWizardPage:
|
||||
# validate the usage page
|
||||
@@ -709,8 +624,6 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
QtWidgets.QMessageBox.critical(self, "Remote server", "There is no remote servers configured in your preferences")
|
||||
return False
|
||||
self._compute_id = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex()).id()
|
||||
elif hasattr(self, "uiVMRadioButton") and self.uiVMRadioButton.isChecked():
|
||||
self._compute_id = "vm"
|
||||
else:
|
||||
if ComputeManager.instance().localPlatform():
|
||||
if (ComputeManager.instance().localPlatform().startswith("darwin") or ComputeManager.instance().localPlatform().startswith("win")):
|
||||
@@ -720,29 +633,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
return False
|
||||
self._compute_id = "local"
|
||||
|
||||
elif self.currentPage() == self.uiQemuWizardPage:
|
||||
# validate the Qemu
|
||||
|
||||
if self._server_check is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Checking for KVM support", "Please wait for the server to reply...")
|
||||
return False
|
||||
if self.uiQemuListComboBox.currentIndex() == -1:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
|
||||
return False
|
||||
return True
|
||||
|
||||
@qslot
|
||||
def _vmToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the VM radio button is toggled.
|
||||
|
||||
:param checked: either the button is checked or not
|
||||
"""
|
||||
|
||||
if checked:
|
||||
self.uiRemoteServersGroupBox.setEnabled(False)
|
||||
self.uiRemoteServersGroupBox.hide()
|
||||
|
||||
@qslot
|
||||
def _remoteServerToggledSlot(self, checked):
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,6 @@ import psutil
|
||||
import platform
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
@@ -92,19 +91,6 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
|
||||
return (1, "Experimental features are enabled. Turn them off by going to Preferences -> General -> Miscellaneous.")
|
||||
return (0, None)
|
||||
|
||||
def checkAVGInstalled(self):
|
||||
"""Checking if AVG software is not installed"""
|
||||
|
||||
if sys.platform.startswith("win32"):
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
psinfo = proc.as_dict(["exe"])
|
||||
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
|
||||
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
def checkFreeRam(self):
|
||||
"""Checking for amount of free virtual memory"""
|
||||
|
||||
@@ -186,36 +172,6 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
def _checkWindowsService(self, service_name):
|
||||
|
||||
import pywintypes
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
|
||||
try:
|
||||
if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING:
|
||||
return False
|
||||
except pywintypes.error as e:
|
||||
if e.winerror == 1060:
|
||||
return False
|
||||
else:
|
||||
raise
|
||||
return True
|
||||
|
||||
def checkRPFServiceIsRunning(self):
|
||||
"""Check if the RPF service is running (required to use Ethernet NIOs)"""
|
||||
|
||||
if not sys.platform.startswith("win"):
|
||||
return (0, None)
|
||||
|
||||
import pywintypes
|
||||
try:
|
||||
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
|
||||
return (2, "The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot")
|
||||
except pywintypes.error as e:
|
||||
return (2, "Could not check if the NPF or Npcap service is running: {}".format(e.strerror))
|
||||
|
||||
return (0, None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
@@ -34,42 +34,15 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.uiEnableAuthenticationCheckBox.toggled.connect(self._enableAuthenticationSlot)
|
||||
self._compute = compute
|
||||
|
||||
if self._compute:
|
||||
self.uiServerNameLineEdit.setText(self._compute.name())
|
||||
self.uiServerHostLineEdit.setText(self._compute.host())
|
||||
self.uiServerPortSpinBox.setValue(self._compute.port())
|
||||
|
||||
index = self.uiServerProtocolComboBox.findText(self._compute.protocol().upper())
|
||||
self.uiServerProtocolComboBox.setCurrentIndex(index)
|
||||
|
||||
if self._compute.user():
|
||||
self.uiEnableAuthenticationCheckBox.setChecked(True)
|
||||
self.uiServerUserLineEdit.setText(self._compute.user())
|
||||
else:
|
||||
self.uiEnableAuthenticationCheckBox.setChecked(False)
|
||||
self.uiWarningLabel.setVisible(False)
|
||||
else:
|
||||
self.uiEnableAuthenticationCheckBox.setChecked(False)
|
||||
self.uiWarningLabel.setVisible(False)
|
||||
self._enableAuthenticationSlot(self.uiEnableAuthenticationCheckBox.isChecked())
|
||||
|
||||
def _enableAuthenticationSlot(self, state):
|
||||
"""
|
||||
Slot to enable or not the authentication.
|
||||
"""
|
||||
|
||||
if self.uiEnableAuthenticationCheckBox.isChecked():
|
||||
self.uiServerUserLineEdit.setVisible(True)
|
||||
self.uiServerPasswordLineEdit.setVisible(True)
|
||||
self.uiServerUserLabel.setVisible(True)
|
||||
self.uiServerPasswordLabel.setVisible(True)
|
||||
else:
|
||||
self.uiServerUserLineEdit.setVisible(False)
|
||||
self.uiServerPasswordLineEdit.setVisible(False)
|
||||
self.uiServerUserLabel.setVisible(False)
|
||||
self.uiServerPasswordLabel.setVisible(False)
|
||||
self.uiServerUserLineEdit.setText(self._compute.user())
|
||||
|
||||
def compute(self):
|
||||
return self._compute
|
||||
@@ -87,16 +60,16 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
|
||||
password = self.uiServerPasswordLineEdit.text().strip()
|
||||
|
||||
if not re.match(r"^[a-zA-Z0-9\.{}-]+$".format("\u0370-\u1CDF\u2C00-\u30FF\u4E00-\u9FBF"), host):
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server hostname {}".format(host))
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute hostname {}".format(host))
|
||||
return
|
||||
if name == "gns3vm":
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "{} is a reserved name".format(name))
|
||||
return
|
||||
if len(name) == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server name {}".format(name))
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute name {}".format(name))
|
||||
return
|
||||
if port is None or port < 1:
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server port {}".format(port))
|
||||
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid compute port {}".format(port))
|
||||
return
|
||||
|
||||
if not self._compute:
|
||||
@@ -105,12 +78,8 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
|
||||
self._compute.setProtocol(protocol)
|
||||
self._compute.setHost(host)
|
||||
self._compute.setPort(port)
|
||||
if self.uiEnableAuthenticationCheckBox.isChecked():
|
||||
self._compute.setUser(user)
|
||||
self._compute.setPassword(password)
|
||||
else:
|
||||
self._compute.setUser(None)
|
||||
self._compute.setPassword(None)
|
||||
self._compute.setUser(user)
|
||||
self._compute.setPassword(password)
|
||||
|
||||
super().accept()
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..qt import QtWidgets, QtCore, qslot, qpartial
|
||||
from gns3.utils import parse_version
|
||||
|
||||
from ..qt import QtGui, QtWidgets, QtCore, qslot, qpartial
|
||||
from ..topology import Topology
|
||||
from ..ui.edit_project_dialog_ui import Ui_EditProjectDialog
|
||||
|
||||
@@ -46,9 +48,38 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
|
||||
self.uiNewVarButton.clicked.connect(self.onAddNewVariable)
|
||||
self.uiGlobalVariablesGrid.addWidget(self.uiNewVarButton, 0, 3, QtCore.Qt.AlignRight)
|
||||
|
||||
self._readme_filename = "README.txt"
|
||||
self.uiTabWidget.currentChanged.connect(self._previewMarkdownSlot)
|
||||
self._loadReadme()
|
||||
|
||||
self._variables = self.setUpVariables()
|
||||
self.updateGlobalVariables()
|
||||
|
||||
def _loadReadme(self):
|
||||
|
||||
self._project.get("/files/{}".format(self._readme_filename), self._loadedReadme, raw=True)
|
||||
|
||||
def _loadedReadme(self, result, error=False, context={}, **kwargs):
|
||||
|
||||
if not error:
|
||||
content = result.decode("utf-8", errors="replace")
|
||||
self.uiReadmeTextEdit.setPlainText(content)
|
||||
|
||||
def _previewMarkdownSlot(self, index):
|
||||
|
||||
# index 1 is preview tab
|
||||
if index == 1:
|
||||
|
||||
# QTextDocument before Qt version 5.14 doesn't support Markdown
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
|
||||
QtWidgets.QMessageBox.critical(self, "Markdown preview", "Markdown preview is only support with Qt version 5.14.0 or above")
|
||||
return
|
||||
|
||||
# show Markdown preview
|
||||
document = QtGui.QTextDocument()
|
||||
self.uiReadmePreview.setDocument(document)
|
||||
document.setMarkdown(self.uiReadmeTextEdit.toPlainText())
|
||||
|
||||
def setUpVariables(self):
|
||||
new_variable = {"name": "", "value": ""}
|
||||
variables = self._project.variables()
|
||||
@@ -123,4 +154,12 @@ class EditProjectDialog(QtWidgets.QDialog, Ui_EditProjectDialog):
|
||||
self._project.setSceneWidth(self.uiSceneWidthSpinBox.value())
|
||||
self._project.setVariables(self._cleanVariables())
|
||||
self._project.update()
|
||||
content = self.uiReadmeTextEdit.toPlainText()
|
||||
if content:
|
||||
self._project.post("/files/{}".format(self._readme_filename), self._saveReadmeCallback, body=content)
|
||||
super().done(result)
|
||||
|
||||
def _saveReadmeCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Edit project", "Could not created readme file")
|
||||
|
||||
@@ -64,9 +64,9 @@ class FileEditorDialog(QtWidgets.QDialog, Ui_FileEditorDialog):
|
||||
def _refreshSlot(self):
|
||||
self._target.get("/files/" + self._path, self._getCallback)
|
||||
|
||||
def _getCallback(self, result, error=False, raw_body=None, **kwargs):
|
||||
def _getCallback(self, result, error=False, **kwargs):
|
||||
if not error:
|
||||
self.uiFileTextEdit.setText(raw_body.decode("utf-8", errors="ignore"))
|
||||
self.uiFileTextEdit.setText(result.decode("utf-8", errors="ignore"))
|
||||
elif result.get("status") == 404:
|
||||
if self._default:
|
||||
self.uiFileTextEdit.setText(self._default)
|
||||
|
||||
168
gns3/dialogs/image_dialog.py
Normal file
168
gns3/dialogs/image_dialog.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2022 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
|
||||
from ..qt import QtCore, QtWidgets, qslot, sip_is_deleted
|
||||
from ..ui.image_dialog_ui import Ui_ImageDialog
|
||||
from ..utils import human_size
|
||||
from ..controller import Controller
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
|
||||
"""
|
||||
Image management dialog.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
:param parent: parent widget.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.uiUploadImagePushButton.clicked.connect(self._uploadImageSlot)
|
||||
self.uiDeleteImagePushButton.clicked.connect(self._deleteImageSlot)
|
||||
self.uiRefreshImagesPushButton.clicked.connect(Controller.instance().refreshImageList)
|
||||
Controller.instance().image_list_updated_signal.connect(self._updateImageListSlot)
|
||||
self._updateImageListSlot()
|
||||
Controller.instance().refreshImageList()
|
||||
|
||||
@qslot
|
||||
def _uploadImageSlot(self, *args):
|
||||
|
||||
files, _ = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
"Select one or more images to upload",
|
||||
".",
|
||||
"Images (*.bin *.image, *.qcow2, *.vmdk);;All files (*.*)"
|
||||
)
|
||||
error_msgs = ""
|
||||
for path in files:
|
||||
log.debug("Uploading image '{}' to controller".format(path))
|
||||
image_filename = os.path.basename(path)
|
||||
install_appliances = self.uiInstallApplianceCheckBox.isChecked()
|
||||
try:
|
||||
Controller.instance().post(
|
||||
f"/images/upload/{image_filename}",
|
||||
params={"install_appliances": install_appliances},
|
||||
body=pathlib.Path(path),
|
||||
context={"image_path": path},
|
||||
progress_text="Uploading {}".format(image_filename),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
return
|
||||
except HttpClientError as e:
|
||||
error_msgs += f"{e}\n"
|
||||
|
||||
if error_msgs:
|
||||
error_dialog = QtWidgets.QMessageBox(self)
|
||||
error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
error_dialog.setWindowTitle("Image upload")
|
||||
error_dialog.setText(f"Error while uploading images to the controller")
|
||||
error_dialog.setDetailedText(error_msgs)
|
||||
error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
error_dialog.show()
|
||||
|
||||
Controller.instance().refreshImageList()
|
||||
|
||||
@qslot
|
||||
def _deleteImageSlot(self, *args):
|
||||
|
||||
if len(self.uiImagesTreeWidget.selectedItems()) == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "Delete image", "No images selected")
|
||||
return
|
||||
|
||||
reply = QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Delete image(s)",
|
||||
"Delete the selected images?\nThis cannot be reverted.",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return
|
||||
|
||||
images_to_delete = set()
|
||||
for image in self.uiImagesTreeWidget.selectedItems():
|
||||
if sip_is_deleted(image):
|
||||
continue
|
||||
image_filename = image.data(0, QtCore.Qt.UserRole)
|
||||
images_to_delete.add(image_filename)
|
||||
|
||||
error_msgs = ""
|
||||
for image_filename in images_to_delete:
|
||||
try:
|
||||
Controller.instance().delete(
|
||||
f"/images/{image_filename}",
|
||||
progress_text=f"Deleting {image_filename}",
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
return
|
||||
except HttpClientError as e:
|
||||
error_msgs += f"{e}\n"
|
||||
|
||||
if error_msgs:
|
||||
error_dialog = QtWidgets.QMessageBox(self)
|
||||
error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
error_dialog.setWindowTitle("Image deletion")
|
||||
error_dialog.setText(f"Error while deleting images on the controller")
|
||||
error_dialog.setDetailedText(error_msgs)
|
||||
error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
error_dialog.show()
|
||||
|
||||
Controller.instance().refreshImageList()
|
||||
|
||||
@qslot
|
||||
def _updateImageListSlot(self, *args):
|
||||
|
||||
self.uiImagesTreeWidget.clear()
|
||||
self.uiDeleteImagePushButton.setEnabled(False)
|
||||
self.uiImagesTreeWidget.setUpdatesEnabled(False)
|
||||
items = []
|
||||
for image in Controller.instance().images():
|
||||
item = QtWidgets.QTreeWidgetItem([image["filename"], image["image_type"], human_size(image["image_size"])])
|
||||
item.setData(0, QtCore.Qt.UserRole, image["filename"])
|
||||
items.append(item)
|
||||
|
||||
self.uiImagesTreeWidget.addTopLevelItems(items)
|
||||
if len(Controller.instance().images()):
|
||||
self.uiDeleteImagePushButton.setEnabled(True)
|
||||
|
||||
self.uiImagesTreeWidget.header().setResizeContentsPrecision(100) # How many rows are checked for the resize for performance reason
|
||||
self.uiImagesTreeWidget.resizeColumnToContents(0)
|
||||
self.uiImagesTreeWidget.resizeColumnToContents(1)
|
||||
self.uiImagesTreeWidget.resizeColumnToContents(2)
|
||||
self.uiImagesTreeWidget.sortItems(0, QtCore.Qt.AscendingOrder)
|
||||
self.uiImagesTreeWidget.setUpdatesEnabled(True)
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
"""
|
||||
Event handler in order to properly handle escape.
|
||||
"""
|
||||
|
||||
if e.key() == QtCore.Qt.Key_Escape:
|
||||
self.close()
|
||||
60
gns3/dialogs/login_dialog.py
Normal file
60
gns3/dialogs/login_dialog.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..qt import QtWidgets
|
||||
from ..ui.login_dialog_ui import Ui_LoginDialog
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoginDialog(QtWidgets.QDialog, Ui_LoginDialog):
|
||||
|
||||
"""
|
||||
Login dialog.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
:param parent: parent widget.
|
||||
"""
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self._parent = parent
|
||||
self._username = None
|
||||
self._password = None
|
||||
|
||||
def getUsername(self):
|
||||
|
||||
return self._username
|
||||
|
||||
def setUsername(self, username):
|
||||
|
||||
self.uiUsernameLineEdit.setText(username)
|
||||
|
||||
def getPassword(self):
|
||||
|
||||
return self._password
|
||||
|
||||
def done(self, result):
|
||||
|
||||
if result:
|
||||
self._username = self.uiUsernameLineEdit.text()
|
||||
self._password = self.uiPasswordLineEdit.text()
|
||||
super().done(result)
|
||||
@@ -21,7 +21,7 @@ Dialog to load module and built-in preference pages.
|
||||
|
||||
from ..qt import QtCore, QtWidgets
|
||||
from ..ui.preferences_dialog_ui import Ui_PreferencesDialog
|
||||
from ..pages.server_preferences_page import ServerPreferencesPage
|
||||
from ..pages.controller_preferences_page import ControllerPreferencesPage
|
||||
from ..pages.general_preferences_page import GeneralPreferencesPage
|
||||
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
|
||||
from ..pages.gns3_vm_preferences_page import GNS3VMPreferencesPage
|
||||
@@ -84,8 +84,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
# load built-in preference pages
|
||||
pages = [
|
||||
GeneralPreferencesPage,
|
||||
ServerPreferencesPage,
|
||||
GNS3VMPreferencesPage,
|
||||
ControllerPreferencesPage,
|
||||
#GNS3VMPreferencesPage,
|
||||
PacketCapturePreferencesPage,
|
||||
]
|
||||
|
||||
|
||||
@@ -139,18 +139,24 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
|
||||
reset_mac_addresses = self.uiResetMacAddressesCheckBox.isChecked()
|
||||
|
||||
if Controller.instance().isRemote():
|
||||
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
|
||||
self._duplicateCallback,
|
||||
body={"name": name, "reset_mac_addresses": reset_mac_addresses},
|
||||
progressText="Duplicating project '{}'...".format(name),
|
||||
timeout=None)
|
||||
Controller.instance().post(
|
||||
"/projects/{project_id}/duplicate".format(project_id=project_id),
|
||||
self._duplicateCallback,
|
||||
body={"name": name, "reset_mac_addresses": reset_mac_addresses},
|
||||
progress_text="Duplicating project '{}'...".format(name),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
else:
|
||||
project_location = os.path.join(Topology.instance().projectsDirPath(), name)
|
||||
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
|
||||
self._duplicateCallback,
|
||||
body={"name": name, "path": project_location, "reset_mac_addresses": reset_mac_addresses},
|
||||
progressText="Duplicating project '{}'...".format(name),
|
||||
timeout=None)
|
||||
Controller.instance().post(
|
||||
"/projects/{project_id}/duplicate".format(project_id=project_id),
|
||||
self._duplicateCallback,
|
||||
body={"name": name, "path": project_location, "reset_mac_addresses": reset_mac_addresses},
|
||||
progress_text="Duplicating project '{}'...".format(name),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _duplicateCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..local_server import LocalServer
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..utils.export_project_worker import ExportProjectWorker
|
||||
from ..ui.export_project_wizard_ui import Ui_ExportProjectWizard
|
||||
from gns3.qt import QtCore, QtWidgets, QtGui
|
||||
from gns3.utils import parse_version
|
||||
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
|
||||
from gns3.local_server import LocalServer
|
||||
from gns3.ui.export_project_wizard_ui import Ui_ExportProjectWizard
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -34,6 +33,8 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
Export project wizard.
|
||||
"""
|
||||
|
||||
readme_signal = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, project, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
@@ -50,24 +51,40 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
self.uiCompressionComboBox.addItem("Zip compression (deflate)", "zip")
|
||||
self.uiCompressionComboBox.addItem("Bzip2 compression", "bzip2")
|
||||
self.uiCompressionComboBox.addItem("Lzma compression", "lzma")
|
||||
self.uiCompressionComboBox.addItem("Zstandard compression", "zstd")
|
||||
self.uiCompressionComboBox.currentIndexChanged.connect(self._compressionChangedSlot)
|
||||
|
||||
# set zip compression by default
|
||||
self.uiCompressionComboBox.setCurrentIndex(1)
|
||||
# set zstd compression by default
|
||||
self.uiCompressionComboBox.setCurrentIndex(4)
|
||||
self.helpRequested.connect(self._showHelpSlot)
|
||||
self.uiPathBrowserToolButton.clicked.connect(self._pathBrowserSlot)
|
||||
|
||||
# QTextDocument before Qt version 5.14 doesn't support Markdown
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
|
||||
self._use_markdown = False
|
||||
else:
|
||||
self._use_markdown = True
|
||||
|
||||
self._readme_filename = "README.txt"
|
||||
self.uiTabWidget.currentChanged.connect(self._previewMarkdownSlot)
|
||||
self._loadReadme()
|
||||
|
||||
def _loadReadme(self):
|
||||
|
||||
self._project.get("/files/README.txt", self._loadedReadme)
|
||||
self._project.get("/files/{}".format(self._readme_filename), self._loadedReadme, raw=True)
|
||||
|
||||
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
|
||||
def _loadedReadme(self, result, error=False, context={}, **kwargs):
|
||||
|
||||
if not error:
|
||||
self.uiReadmeTextEdit.setPlainText(raw_body.decode("utf-8", errors="replace"))
|
||||
content = result.decode("utf-8", errors="replace")
|
||||
self.uiReadmeTextEdit.setPlainText(content)
|
||||
else:
|
||||
readme_text = "Project: '{}' created on {}\nAuthor: John Doe <john.doe@example.com>\n\nNo project description was given".format(self._project.name(), datetime.date.today())
|
||||
self.uiReadmeTextEdit.setPlainText(readme_text)
|
||||
if self._use_markdown:
|
||||
readme_markdown = "# Project {}\n\nCreated on: {}\n\nAuthor: John Doe <john.doe@example.com>\n\n## Description\n\nNo project description was given".format(self._project.name(), datetime.date.today())
|
||||
self.uiReadmeTextEdit.setPlainText(readme_markdown)
|
||||
else:
|
||||
readme_text = "Project: '{}' created on {}\nAuthor: John Doe <john.doe@example.com>\n\nNo project description was given".format(self._project.name(), datetime.date.today())
|
||||
self.uiReadmeTextEdit.setPlainText(readme_text)
|
||||
|
||||
def _pathBrowserSlot(self):
|
||||
|
||||
@@ -75,14 +92,28 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
if len(directory) == 0:
|
||||
directory = LocalServer.instance().localServerSettings()["projects_path"]
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export portable project", directory,
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export project", directory,
|
||||
"GNS3 Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Project (*.gns3project *.gns3p)")
|
||||
if path is None or len(path) == 0:
|
||||
return
|
||||
|
||||
self.uiPathLineEdit.setText(path)
|
||||
|
||||
def _previewMarkdownSlot(self, index):
|
||||
|
||||
# index 1 is preview tab
|
||||
if index == 1:
|
||||
|
||||
if self._use_markdown is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Markdown preview", "Markdown preview is only support with Qt version 5.14.0 or above")
|
||||
return
|
||||
|
||||
# show Markdown preview
|
||||
document = QtGui.QTextDocument()
|
||||
self.uiReadmePreviewEdit.setDocument(document)
|
||||
document.setMarkdown(self.uiReadmeTextEdit.toPlainText())
|
||||
|
||||
def _showHelpSlot(self):
|
||||
|
||||
include_image_help = """Including base images means additional images will not be requested to
|
||||
@@ -110,15 +141,39 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
QtWidgets.QMessageBox.critical(self, "Export project", "Cannot export project to '{}': {}".format(path, e))
|
||||
return False
|
||||
self._path = path
|
||||
elif self.currentPage() == self.uiProjectReadmeWizardPage:
|
||||
text = self.uiReadmeTextEdit.toPlainText().strip()
|
||||
if text:
|
||||
self._project.post("/files/README.txt", self._saveReadmeCallback, body=text)
|
||||
return True
|
||||
|
||||
def _saveReadmeCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Export project", "Could not created readme file")
|
||||
self.readme_signal.emit()
|
||||
|
||||
def waitForReadme(self, signal, timeout=10000):
|
||||
|
||||
# inspired from https://www.jdreaver.com/posts/2014-07-03-waiting-for-signals-pyside-pyqt.html
|
||||
loop = QtCore.QEventLoop()
|
||||
signal.connect(loop.quit)
|
||||
if timeout is not None:
|
||||
QtCore.QTimer.singleShot(timeout, loop.quit)
|
||||
loop.exec_()
|
||||
|
||||
def _compressionChangedSlot(self, index):
|
||||
"""
|
||||
Set the default compression level.
|
||||
"""
|
||||
|
||||
compression = self.uiCompressionComboBox.itemData(index)
|
||||
self.uiCompressionLevelSpinBox.setEnabled(True)
|
||||
if compression == "zip":
|
||||
self.uiCompressionLevelSpinBox.setValue(6) # ZIP default compression level is 6
|
||||
elif compression == "bzip2":
|
||||
self.uiCompressionLevelSpinBox.setValue(9) # BZIP2 default compression level is 9
|
||||
elif compression == "zstd":
|
||||
self.uiCompressionLevelSpinBox.setValue(3) # ZSTD default compression level is 3
|
||||
else:
|
||||
# compression level is not supported
|
||||
self.uiCompressionLevelSpinBox.setValue(0)
|
||||
self.uiCompressionLevelSpinBox.setEnabled(False)
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
@@ -126,6 +181,11 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
"""
|
||||
|
||||
if result:
|
||||
|
||||
content = self.uiReadmeTextEdit.toPlainText()
|
||||
if content:
|
||||
self._project.post("/files/{}".format(self._readme_filename), self._saveReadmeCallback, body=content)
|
||||
|
||||
if self.uiIncludeImagesCheckBox.isChecked():
|
||||
include_images = "yes"
|
||||
else:
|
||||
@@ -139,8 +199,45 @@ class ExportProjectWizard(QtWidgets.QWizard, Ui_ExportProjectWizard):
|
||||
else:
|
||||
reset_mac_addresses = "no"
|
||||
compression = self.uiCompressionComboBox.currentData()
|
||||
export_worker = ExportProjectWorker(self._project, self._path, include_images, include_snapshots, reset_mac_addresses, compression)
|
||||
progress_dialog = ProgressDialog(export_worker, "Exporting project", "Exporting portable project files...", "Cancel", parent=self, create_thread=False)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
self.waitForReadme(self.readme_signal)
|
||||
|
||||
params = {
|
||||
"include_images": include_images,
|
||||
"include_snapshots": include_snapshots,
|
||||
"reset_mac_addresses": reset_mac_addresses,
|
||||
"compression": compression
|
||||
}
|
||||
|
||||
try:
|
||||
self._project.get(
|
||||
"/export",
|
||||
callback=None,
|
||||
download_progress_callback=self._downloadFileProgress,
|
||||
progress_text="Exporting project files...",
|
||||
params=params,
|
||||
timeout=None,
|
||||
wait=True,
|
||||
raw=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
pass
|
||||
except HttpClientError as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Project export",
|
||||
f"Could not export project: {e}"
|
||||
)
|
||||
|
||||
super().done(result)
|
||||
|
||||
def _downloadFileProgress(self, content, **kwargs):
|
||||
"""
|
||||
Called for each part of the file
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(self._path, 'ab') as f:
|
||||
f.write(content)
|
||||
except OSError as e:
|
||||
log.error(f"Could not write project file: {e}")
|
||||
return
|
||||
|
||||
@@ -61,11 +61,11 @@ class ProjectWelcomeDialog(QtWidgets.QDialog, Ui_ProjectWelcomeDialog):
|
||||
self.gridLayout.addWidget(valueEdit, i, 1)
|
||||
|
||||
def _loadReadme(self):
|
||||
self._project.get("/files/README.txt", self._loadedReadme)
|
||||
self._project.get("/files/README.txt", self._loadedReadme, raw=True)
|
||||
|
||||
def _loadedReadme(self, result, error=False, raw_body=None, context={}, **kwargs):
|
||||
def _loadedReadme(self, result, error=False, context={}, **kwargs):
|
||||
if not error:
|
||||
self.label.setText(raw_body.decode("utf-8"))
|
||||
self.label.setText(result.decode("utf-8"))
|
||||
|
||||
def onValueChange(self, variable, text):
|
||||
variable["value"] = text
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2021 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -19,14 +19,12 @@ import sys
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets, QtGui, QtNetwork, qslot
|
||||
from gns3.qt import QtCore, QtWidgets, QtNetwork
|
||||
from gns3.controller import Controller
|
||||
from gns3.local_server import LocalServer
|
||||
from gns3.utils.interfaces import interfaces
|
||||
|
||||
from ..settings import DEFAULT_LOCAL_SERVER_HOST
|
||||
from ..settings import DEFAULT_CONTROLLER_HOST
|
||||
from ..ui.setup_wizard_ui import Ui_SetupWizard
|
||||
from ..version import __version__
|
||||
|
||||
|
||||
import logging
|
||||
@@ -45,40 +43,17 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
self.setupUi(self)
|
||||
self.adjustSize()
|
||||
|
||||
self._gns3_vm_settings = {
|
||||
"enable": True,
|
||||
"headless": False,
|
||||
"when_exit": "stop",
|
||||
"engine": "vmware",
|
||||
"allocate_vcpus_ram": True,
|
||||
"vcpus": 1,
|
||||
"ram": 2048,
|
||||
"vmname": "GNS3 VM",
|
||||
"port": 80
|
||||
}
|
||||
|
||||
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
if sys.platform.startswith("darwin"):
|
||||
# we want to see the cancel button on OSX
|
||||
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
|
||||
|
||||
self.uiLocalServerToolButton.clicked.connect(self._localServerBrowserSlot)
|
||||
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText("")
|
||||
self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
|
||||
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
|
||||
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
|
||||
settings = parent.settings()
|
||||
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
|
||||
|
||||
# by default all radio buttons are unchecked
|
||||
self.uiVmwareRadioButton.setAutoExclusive(False)
|
||||
self.uiVirtualBoxRadioButton.setAutoExclusive(False)
|
||||
self.uiVmwareRadioButton.setChecked(False)
|
||||
self.uiVirtualBoxRadioButton.setChecked(False)
|
||||
|
||||
# Mandatory fields
|
||||
self.uiLocalServerWizardPage.registerField("path*", self.uiLocalServerPathLineEdit)
|
||||
self.uiLocalControllerWizardPage.registerField("path*", self.uiLocalServerPathLineEdit)
|
||||
|
||||
# load all available addresses
|
||||
for address in QtNetwork.QNetworkInterface.allAddresses():
|
||||
@@ -95,11 +70,11 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
self.uiLocalServerHostComboBox.addItem("0.0.0.0", "0.0.0.0") # all IPv4 addresses
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
self.uiLocalLabel.setText("Dependencies like Dynamips and Qemu must be manually installed")
|
||||
|
||||
Controller.instance().connected_signal.connect(self._refreshLocalServerStatusSlot)
|
||||
Controller.instance().connection_failed_signal.connect(self._refreshLocalServerStatusSlot)
|
||||
# only support local controller on Linux
|
||||
self.uiLocalControllerRadioButton.setChecked(True)
|
||||
else:
|
||||
self.uiLocalControllerRadioButton.setEnabled(False)
|
||||
self.uiRemoteControllerRadioButton.setChecked(True)
|
||||
|
||||
def _localServerBrowserSlot(self):
|
||||
"""
|
||||
@@ -116,36 +91,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
|
||||
self.uiLocalServerPathLineEdit.setText(path)
|
||||
|
||||
def _listVMwareVMsSlot(self):
|
||||
"""
|
||||
Slot to refresh the VMware VMs list.
|
||||
"""
|
||||
|
||||
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__)
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('The GNS3 VM can <a href="{download_url}">downloaded here</a>.<br>Import the VM in your virtualization software and hit refresh.'.format(download_url=download_url))
|
||||
self.uiVirtualBoxRadioButton.setChecked(False)
|
||||
from gns3.modules import VMware
|
||||
settings = VMware.instance().settings()
|
||||
if not os.path.exists(settings["vmrun_path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://customerconnect.vmware.com/downloads/details?downloadGroup=PLAYER-1400-VIX1170&productId=687. After installation you need to restart GNS3.")
|
||||
return
|
||||
self._refreshVMListSlot()
|
||||
|
||||
def _listVirtualBoxVMsSlot(self):
|
||||
"""
|
||||
Slot to refresh the VirtualBox VMs list.
|
||||
"""
|
||||
|
||||
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
|
||||
self.uiVmwareRadioButton.setChecked(False)
|
||||
from gns3.modules import VirtualBox
|
||||
settings = VirtualBox.instance().settings()
|
||||
if not os.path.exists(settings["vboxmanage_path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "VirtualBox", "VBoxManage could not be found, VirtualBox is probably not installed. After installation you need to restart GNS3.")
|
||||
return
|
||||
self._refreshVMListSlot()
|
||||
|
||||
def _setPreferencesPane(self, dialog, name):
|
||||
"""
|
||||
Finds the first child of the QTreeWidgetItem name.
|
||||
@@ -161,13 +106,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
dialog.uiTreeWidget.setCurrentItem(child_pane)
|
||||
return dialog.uiStackedWidget.currentWidget()
|
||||
|
||||
def _getSettingsCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while get gettings: {}".format(result["message"]))
|
||||
return
|
||||
self._gns3_vm_settings = result
|
||||
|
||||
def initializePage(self, page_id):
|
||||
"""
|
||||
Initialize Wizard pages.
|
||||
@@ -176,35 +114,16 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
"""
|
||||
|
||||
super().initializePage(page_id)
|
||||
if self.page(page_id) == self.uiServerWizardPage:
|
||||
if self.page(page_id) == self.uiControllerWizardPage:
|
||||
Controller.instance().setDisplayError(False)
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
elif self.page(page_id) == self.uiVMWizardPage:
|
||||
if self._GNS3VMSettings()["engine"] == "vmware":
|
||||
self.uiVmwareRadioButton.setChecked(True)
|
||||
self._listVMwareVMsSlot()
|
||||
elif self._GNS3VMSettings()["engine"] == "virtualbox":
|
||||
self.uiVirtualBoxRadioButton.setChecked(True)
|
||||
self._listVirtualBoxVMsSlot()
|
||||
self.uiCPUSpinBox.setValue(self._GNS3VMSettings()["vcpus"])
|
||||
self.uiRAMSpinBox.setValue(self._GNS3VMSettings()["ram"])
|
||||
|
||||
elif self.page(page_id) == self.uiLocalServerWizardPage:
|
||||
elif self.page(page_id) == self.uiLocalControllerWizardPage:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
self.uiLocalServerPathLineEdit.setText(local_server_settings["path"])
|
||||
index = self.uiLocalServerHostComboBox.findData(local_server_settings["host"])
|
||||
if index != -1:
|
||||
self.uiLocalServerHostComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
if self.uiVMRadioButton.isChecked():
|
||||
# Try to bind with the IP address allocated for VMnet1
|
||||
for interface in interfaces():
|
||||
if "vmnet1" in interface["name"].lower():
|
||||
index = self.uiLocalServerHostComboBox.findText(interface["ip_address"])
|
||||
break
|
||||
else:
|
||||
index = self.uiLocalServerHostComboBox.findText(DEFAULT_LOCAL_SERVER_HOST)
|
||||
|
||||
index = self.uiLocalServerHostComboBox.findText(DEFAULT_CONTROLLER_HOST)
|
||||
if index != -1:
|
||||
self.uiLocalServerHostComboBox.setCurrentIndex(index)
|
||||
|
||||
@@ -213,61 +132,28 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
elif self.page(page_id) == self.uiRemoteControllerWizardPage:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
if local_server_settings["host"] is None:
|
||||
self.uiRemoteMainServerHostLineEdit.setText(DEFAULT_LOCAL_SERVER_HOST)
|
||||
self.uiRemoteMainServerAuthCheckBox.setChecked(False)
|
||||
self.uiRemoteMainServerHostLineEdit.setText(DEFAULT_CONTROLLER_HOST)
|
||||
self.uiRemoteMainServerUserLineEdit.setText("")
|
||||
self.uiRemoteMainServerPasswordLineEdit.setText("")
|
||||
else:
|
||||
self.uiRemoteMainServerHostLineEdit.setText(local_server_settings["host"])
|
||||
self.uiRemoteMainServerAuthCheckBox.setChecked(local_server_settings["auth"])
|
||||
self.uiRemoteMainServerUserLineEdit.setText(local_server_settings["user"])
|
||||
self.uiRemoteMainServerUserLineEdit.setText(local_server_settings["username"])
|
||||
self.uiRemoteMainServerPasswordLineEdit.setText(local_server_settings["password"])
|
||||
self.uiRemoteMainServerPortSpinBox.setValue(local_server_settings["port"])
|
||||
elif self.page(page_id) == self.uiLocalServerStatusWizardPage:
|
||||
self._refreshLocalServerStatusSlot()
|
||||
|
||||
elif self.page(page_id) == self.uiSummaryWizardPage:
|
||||
self.uiSummaryTreeWidget.clear()
|
||||
if self.uiLocalRadioButton.isChecked():
|
||||
if self.uiLocalControllerRadioButton.isChecked():
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
self._addSummaryEntry("Server type:", "Local")
|
||||
self._addSummaryEntry("Type:", "Local")
|
||||
self._addSummaryEntry("Path:", local_server_settings["path"])
|
||||
self._addSummaryEntry("Host:", local_server_settings["host"])
|
||||
self._addSummaryEntry("Port:", str(local_server_settings["port"]))
|
||||
elif self.uiRemoteControllerRadioButton.isChecked():
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
self._addSummaryEntry("Server type:", "Remote")
|
||||
self._addSummaryEntry("Type:", "Remote")
|
||||
self._addSummaryEntry("Host:", local_server_settings["host"])
|
||||
self._addSummaryEntry("Port:", str(local_server_settings["port"]))
|
||||
self._addSummaryEntry("User:", local_server_settings["user"])
|
||||
else:
|
||||
self._addSummaryEntry("Server type:", "GNS3 Virtual Machine")
|
||||
self._addSummaryEntry("VM engine:", self._GNS3VMSettings()["engine"].capitalize())
|
||||
self._addSummaryEntry("VM name:", self._GNS3VMSettings()["vmname"])
|
||||
self._addSummaryEntry("VM vCPUs:", str(self._GNS3VMSettings()["vcpus"]))
|
||||
self._addSummaryEntry("VM RAM:", str(self._GNS3VMSettings()["ram"]) + " MB")
|
||||
|
||||
@qslot
|
||||
def _refreshLocalServerStatusSlot(self):
|
||||
"""
|
||||
Refresh the local server status page
|
||||
"""
|
||||
|
||||
self.uiLocalServerTextEdit.clear()
|
||||
if Controller.instance().connected():
|
||||
self.uiLocalServerTextEdit.setText("Connection to the local GNS3 server has been successful!")
|
||||
Controller.instance().get("/gns3vm", self._getSettingsCallback)
|
||||
elif Controller.instance().connecting():
|
||||
self.uiLocalServerTextEdit.setText("Please wait connection to the GNS3 server...")
|
||||
else:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
self.uiLocalServerTextEdit.setText("Connection to local server failed. Please try one of the following:\n\n- Make sure GNS3 is allowed to run by your firewall.\n- Go back and try to change the server host binding and/or the port\n- Check with a browser if you can connect to {protocol}://{host}:{port}.\n- Try to run {path} in a terminal to see if you have an error.".format(protocol=local_server_settings["protocol"], host=local_server_settings["host"], port=local_server_settings["port"], path=local_server_settings["path"]))
|
||||
|
||||
def _GNS3VMSettings(self):
|
||||
return self._gns3_vm_settings
|
||||
|
||||
def _setGNS3VMSettings(self, settings):
|
||||
Controller.instance().put("/gns3vm", self._saveSettingsCallback, settings, timeout=60 * 5)
|
||||
self._addSummaryEntry("User:", local_server_settings["username"])
|
||||
|
||||
def _saveSettingsCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
@@ -289,112 +175,44 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
"""
|
||||
|
||||
Controller.instance().setDisplayError(True)
|
||||
if self.currentPage() == self.uiVMWizardPage:
|
||||
vmname = self.uiVMListComboBox.currentText()
|
||||
if vmname:
|
||||
# save the GNS3 VM settings
|
||||
vm_settings = self._GNS3VMSettings()
|
||||
vm_settings["enable"] = True
|
||||
vm_settings["vmname"] = vmname
|
||||
if self.currentPage() == self.uiLocalControllerWizardPage:
|
||||
|
||||
if self.uiVmwareRadioButton.isChecked():
|
||||
vm_settings["engine"] = "vmware"
|
||||
elif self.uiVirtualBoxRadioButton.isChecked():
|
||||
vm_settings["engine"] = "virtualbox"
|
||||
local_controller_settings = LocalServer.instance().localServerSettings()
|
||||
local_controller_settings["auto_start"] = True
|
||||
local_controller_settings["remote"] = False
|
||||
local_controller_settings["path"] = self.uiLocalServerPathLineEdit.text().strip()
|
||||
local_controller_settings["host"] = self.uiLocalServerHostComboBox.itemData(self.uiLocalServerHostComboBox.currentIndex())
|
||||
local_controller_settings["port"] = self.uiLocalServerPortSpinBox.value()
|
||||
|
||||
# set the vCPU count and RAM
|
||||
vpcus = self.uiCPUSpinBox.value()
|
||||
ram = self.uiRAMSpinBox.value()
|
||||
if ram < 1024:
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM memory", "It is recommended to allocate a minimum of 1024 MB of memory to the GNS3 VM")
|
||||
vm_settings["vcpus"] = vpcus
|
||||
vm_settings["ram"] = ram
|
||||
|
||||
self._setGNS3VMSettings(vm_settings)
|
||||
else:
|
||||
if not self.uiVmwareRadioButton.isChecked() and not self.uiVirtualBoxRadioButton.isChecked():
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select VMware or VirtualBox")
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select a VM. If no VM is listed, check if the GNS3 VM is correctly imported and press refresh.")
|
||||
if not os.path.isfile(local_controller_settings["path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(local_controller_settings["path"]))
|
||||
return False
|
||||
elif self.currentPage() == self.uiLocalServerWizardPage:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
local_server_settings["auto_start"] = True
|
||||
local_server_settings["path"] = self.uiLocalServerPathLineEdit.text().strip()
|
||||
local_server_settings["host"] = self.uiLocalServerHostComboBox.itemData(self.uiLocalServerHostComboBox.currentIndex())
|
||||
local_server_settings["port"] = self.uiLocalServerPortSpinBox.value()
|
||||
|
||||
if not os.path.isfile(local_server_settings["path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(local_server_settings["path"]))
|
||||
return False
|
||||
if not os.access(local_server_settings["path"], os.X_OK):
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_server_settings["path"]))
|
||||
if not os.access(local_controller_settings["path"], os.X_OK):
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_controller_settings["path"]))
|
||||
return False
|
||||
|
||||
LocalServer.instance().updateLocalServerSettings(local_server_settings)
|
||||
LocalServer.instance().updateLocalServerSettings(local_controller_settings)
|
||||
|
||||
# start and connect to the controller if required
|
||||
if not LocalServer.instance().localServerAutoStartIfRequired():
|
||||
return False
|
||||
|
||||
elif self.currentPage() == self.uiRemoteControllerWizardPage:
|
||||
local_server_settings = LocalServer.instance().localServerSettings()
|
||||
local_server_settings["auto_start"] = False
|
||||
local_server_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
|
||||
local_server_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
|
||||
local_server_settings["protocol"] = "http"
|
||||
local_server_settings["user"] = self.uiRemoteMainServerUserLineEdit.text()
|
||||
local_server_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
|
||||
local_server_settings["auth"] = self.uiRemoteMainServerAuthCheckBox.isChecked()
|
||||
LocalServer.instance().updateLocalServerSettings(local_server_settings)
|
||||
|
||||
elif self.currentPage() == self.uiSummaryWizardPage:
|
||||
if self.uiLocalRadioButton.isChecked():
|
||||
# deactivate the GNS3 VM if using the local server
|
||||
vm_settings = self._GNS3VMSettings()
|
||||
vm_settings["enable"] = False
|
||||
self._setGNS3VMSettings(vm_settings)
|
||||
|
||||
elif self.currentPage() == self.uiLocalServerStatusWizardPage:
|
||||
if not Controller.instance().connected():
|
||||
return False
|
||||
remote_controller_settings = Controller.instance().settings()
|
||||
remote_controller_settings["auto_start"] = False
|
||||
remote_controller_settings["remote"] = True
|
||||
remote_controller_settings["host"] = self.uiRemoteMainServerHostLineEdit.text()
|
||||
remote_controller_settings["port"] = self.uiRemoteMainServerPortSpinBox.value()
|
||||
remote_controller_settings["protocol"] = "http"
|
||||
remote_controller_settings["username"] = self.uiRemoteMainServerUserLineEdit.text()
|
||||
remote_controller_settings["password"] = self.uiRemoteMainServerPasswordLineEdit.text()
|
||||
Controller.instance().setSettings(remote_controller_settings)
|
||||
Controller.instance().connect()
|
||||
return Controller.instance().connected()
|
||||
|
||||
return True
|
||||
|
||||
def _refreshVMListSlot(self):
|
||||
"""
|
||||
Refresh the list of VM available in VMware or VirtualBox.
|
||||
"""
|
||||
|
||||
if self.uiVmwareRadioButton.isChecked():
|
||||
Controller.instance().get("/gns3vm/engines/vmware/vms", self._getVMsFromServerCallback, progressText="Retrieving VMware VM list from server...")
|
||||
elif self.uiVirtualBoxRadioButton.isChecked():
|
||||
Controller.instance().get("/gns3vm/engines/virtualbox/vms", self._getVMsFromServerCallback, progressText="Retrieving VirtualBox VM list from server...")
|
||||
|
||||
def _getVMsFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getVMsFromServer.
|
||||
|
||||
:param progress_dialog: QProgressDialog instance
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "VM List", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiVMListComboBox.clear()
|
||||
for vm in result:
|
||||
self.uiVMListComboBox.addItem(vm["vmname"])
|
||||
|
||||
index = self.uiVMListComboBox.findText(self._GNS3VMSettings()["vmname"])
|
||||
if index != -1:
|
||||
self.uiVMListComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
index = self.uiVMListComboBox.findText("GNS3 VM")
|
||||
if index != -1:
|
||||
self.uiVMListComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "Could not find a VM named 'GNS3 VM', is it imported in VMware or VirtualBox?")
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
This dialog is closed.
|
||||
@@ -419,20 +237,17 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
"""
|
||||
|
||||
current_id = self.currentId()
|
||||
if self.page(current_id) == self.uiLocalServerStatusWizardPage and not self.uiVMRadioButton.isChecked():
|
||||
if self.page(current_id) == self.uiLocalControllerWizardPage and self.uiLocalControllerRadioButton.isChecked():
|
||||
return self._pageId(self.uiSummaryWizardPage)
|
||||
|
||||
if self.page(current_id) == self.uiServerWizardPage and self.uiRemoteControllerRadioButton.isChecked():
|
||||
if self.page(current_id) == self.uiControllerWizardPage and self.uiRemoteControllerRadioButton.isChecked():
|
||||
return self._pageId(self.uiRemoteControllerWizardPage)
|
||||
|
||||
if self.page(current_id) == self.uiVMWizardPage:
|
||||
return self._pageId(self.uiSummaryWizardPage)
|
||||
return QtWidgets.QWizard.nextId(self)
|
||||
|
||||
def _pageId(self, page):
|
||||
"""
|
||||
Return id of the page
|
||||
"""
|
||||
|
||||
for id in self.pageIds():
|
||||
if self.page(id) == page:
|
||||
return id
|
||||
|
||||
71
gns3/dialogs/show_readme_dialog.py
Normal file
71
gns3/dialogs/show_readme_dialog.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.ui.show_readme_dialog_ui import Ui_ShowReadmeDialog
|
||||
from gns3.utils import parse_version
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ShowReadmeDialog(QtWidgets.QDialog, Ui_ShowReadmeDialog):
|
||||
|
||||
def __init__(self, project, path, content=None, parent=None):
|
||||
|
||||
if parent is None:
|
||||
from gns3.main_window import MainWindow
|
||||
parent = MainWindow.instance()
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._project = project
|
||||
self._path = path
|
||||
self.setWindowTitle(project.name() + " " + os.path.basename(path))
|
||||
self.uiRefreshButton.pressed.connect(self._refreshSlot)
|
||||
|
||||
# QTextDocument before Qt version 5.14 doesn't support Markdown
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.14.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.14.0"):
|
||||
self._markdown = False
|
||||
else:
|
||||
self._markdown = True
|
||||
|
||||
self._document = QtGui.QTextDocument()
|
||||
self.uiTextBrowser.setDocument(self._document)
|
||||
|
||||
if content:
|
||||
if self._markdown:
|
||||
self._document.setMarkdown(content)
|
||||
else:
|
||||
self._document.setPlainText(content)
|
||||
else:
|
||||
self._refreshSlot()
|
||||
|
||||
def _refreshSlot(self):
|
||||
self._project.get("/files/" + self._path, self._getCallback, raw=True)
|
||||
|
||||
def _getCallback(self, result, error=False, **kwargs):
|
||||
|
||||
if not error:
|
||||
content = result.decode("utf-8", errors="ignore")
|
||||
if self._markdown:
|
||||
self._document.setMarkdown(content)
|
||||
else:
|
||||
self._document.setPlainText(content)
|
||||
@@ -85,11 +85,14 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
|
||||
|
||||
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.Normal, "Unnamed")
|
||||
if ok and snapshot_name and self._project:
|
||||
Controller.instance().post("/projects/{}/snapshots".format(self._project.id()),
|
||||
self._createSnapshotsCallback,
|
||||
{"name": snapshot_name},
|
||||
progressText="Creation of snapshot '{}' in progress...".format(snapshot_name),
|
||||
timeout=None)
|
||||
Controller.instance().post(
|
||||
"/projects/{}/snapshots".format(self._project.id()),
|
||||
self._createSnapshotsCallback,
|
||||
{"name": snapshot_name},
|
||||
progress_text="Creation of snapshot '{}' in progress...".format(snapshot_name),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _createSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
|
||||
if error:
|
||||
@@ -139,8 +142,13 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
|
||||
if reply == QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
Controller.instance().post("/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id),
|
||||
self._restoreSnapshotsCallback, progressText="Restoring snapshot...", timeout=None)
|
||||
Controller.instance().post(
|
||||
"/projects/{}/snapshots/{}/restore".format(self._project.id(), snapshot_id),
|
||||
self._restoreSnapshotsCallback,
|
||||
progress_text="Restoring snapshot...",
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _restoreSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):
|
||||
|
||||
|
||||
@@ -191,7 +191,14 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
SymbolSelectionDialog._symbols_dir = os.path.dirname(path)
|
||||
|
||||
symbol_id = os.path.basename(path)
|
||||
Controller.instance().post("/symbols/" + symbol_id + "/raw", qpartial(self._finishSymbolUpload, path), body=pathlib.Path(path), progressText="Uploading {}".format(symbol_id), timeout=None)
|
||||
Controller.instance().post(
|
||||
"/symbols/" + symbol_id + "/raw",
|
||||
qpartial(self._finishSymbolUpload, path),
|
||||
body=pathlib.Path(path),
|
||||
progress_text="Uploading {}".format(symbol_id),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def _finishSymbolUpload(self, path, result, error=False, **kwargs):
|
||||
if error:
|
||||
|
||||
@@ -135,14 +135,17 @@ class VMWithImagesWizard(VMWizard):
|
||||
if create_button:
|
||||
create_button.show()
|
||||
|
||||
def loadImagesList(self, endpoint):
|
||||
def loadImagesList(self, image_type):
|
||||
"""
|
||||
Fill the list box with available Images"
|
||||
Fill the list box with available images
|
||||
|
||||
:param endpoint: server endpoint with the list of Images
|
||||
:param image_type: image type (qemu, iou or ios)
|
||||
"""
|
||||
|
||||
Controller.instance().getCompute(endpoint, self._compute_id, self._getImagesFromServerCallback)
|
||||
params = None
|
||||
if image_type:
|
||||
params = {"image_type": image_type}
|
||||
Controller.instance().get("/images", self._getImagesFromServerCallback, params=params)
|
||||
|
||||
def _getImagesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -179,7 +182,7 @@ class VMWithImagesWizard(VMWizard):
|
||||
if self._widgetOnCurrentPage(combo_box):
|
||||
combo_box.clear()
|
||||
for vm in result:
|
||||
combo_box.addItem(vm["path"], vm)
|
||||
combo_box.addItem(vm["filename"], vm)
|
||||
|
||||
def _widgetOnCurrentPage(self, widget):
|
||||
"""
|
||||
|
||||
@@ -94,8 +94,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self._drawing_grid_size = 25
|
||||
self._last_mouse_position = None
|
||||
self._topology = Topology.instance()
|
||||
self._background_warning_msgbox = QtWidgets.QErrorMessage(self)
|
||||
self._background_warning_msgbox.setWindowTitle("Layer position")
|
||||
|
||||
# set the scene
|
||||
scene = QtWidgets.QGraphicsScene(parent=self)
|
||||
@@ -822,6 +820,18 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
console_edit_action.triggered.connect(self.customConsoleActionSlot)
|
||||
menu.addAction(console_edit_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
|
||||
isolate_action = QtWidgets.QAction("Isolate", menu)
|
||||
isolate_action.setIcon(get_icon("link-pause.svg"))
|
||||
isolate_action.triggered.connect(self.isolateActionSlot)
|
||||
menu.addAction(isolate_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
|
||||
unisolate_action = QtWidgets.QAction("Un-isolate", menu)
|
||||
unisolate_action.setIcon(get_icon("link-start.svg"))
|
||||
unisolate_action.triggered.connect(self.unisolateActionSlot)
|
||||
menu.addAction(unisolate_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem), items)):
|
||||
# Action: Change hostname
|
||||
change_hostname_action = QtWidgets.QAction("Change hostname", menu)
|
||||
@@ -1001,6 +1011,26 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "reload") and item.node().initialized():
|
||||
item.node().reload()
|
||||
|
||||
def isolateActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the isolate action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "isolate") and item.node().initialized():
|
||||
item.node().isolate()
|
||||
|
||||
def unisolateActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the unisolate action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "unisolate") and item.node().initialized():
|
||||
item.node().unisolate()
|
||||
|
||||
def configureActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the configure action in the
|
||||
@@ -1028,10 +1058,6 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
if not new_hostname.strip():
|
||||
QtWidgets.QMessageBox.critical(self, "Change hostname", "Hostname cannot be blank")
|
||||
continue
|
||||
if hasattr(item.node(), "validateHostname"):
|
||||
if not item.node().validateHostname(new_hostname):
|
||||
QtWidgets.QMessageBox.critical(self, "Change hostname", "Invalid name detected for this node: {}".format(new_hostname))
|
||||
continue
|
||||
item.node().update({"name": new_hostname})
|
||||
|
||||
def changeSymbolActionSlot(self):
|
||||
@@ -1064,7 +1090,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
break
|
||||
|
||||
if os.path.exists(node_dir):
|
||||
log.debug("Open %s in file manager")
|
||||
log.debug(f"Open {node_dir} in file manager")
|
||||
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(node_dir)) is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(node_dir))
|
||||
break
|
||||
|
||||
1112
gns3/http_client.py
1112
gns3/http_client.py
File diff suppressed because it is too large
Load Diff
58
gns3/http_client_error.py
Normal file
58
gns3/http_client_error.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2021 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class HttpClientError(Exception):
|
||||
def __init__(self, message: str):
|
||||
super().__init__()
|
||||
self._message = message
|
||||
|
||||
def __repr__(self):
|
||||
return self._message
|
||||
|
||||
def __str__(self):
|
||||
return self._message
|
||||
|
||||
|
||||
class HttpClientNotFoundError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class HttpClientCancelledRequestError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class HttpClientBadRequestError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class HttpClientUnauthorizedError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class HttpClientForbiddenError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class HttpClientTimeoutError(HttpClientError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
@@ -20,11 +20,10 @@ import copy
|
||||
import pathlib
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.settings import LOCAL_SERVER_SETTINGS
|
||||
from gns3.controller import Controller
|
||||
from gns3.utils.file_copy_worker import FileCopyWorker
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
|
||||
from gns3.registry.image import Image
|
||||
|
||||
|
||||
@@ -69,7 +68,7 @@ class ImageManager:
|
||||
"""
|
||||
|
||||
if (server and server != "local") or Controller.instance().isRemote():
|
||||
return self._uploadImageToRemoteServer(source_path, server, node_type)
|
||||
return self._uploadImageToRemoteServer(source_path, node_type, parent)
|
||||
else:
|
||||
destination_directory = self.getDirectoryForType(node_type)
|
||||
destination_path = os.path.join(destination_directory, os.path.basename(source_path))
|
||||
@@ -115,27 +114,45 @@ class ImageManager:
|
||||
source_path = destination_path
|
||||
return source_path
|
||||
|
||||
def _uploadImageToRemoteServer(self, path, server, node_type):
|
||||
def _uploadImageToRemoteServer(self, path, node_type, parent):
|
||||
"""
|
||||
Upload image to remote server
|
||||
|
||||
:param path: File path on computer
|
||||
:param server: The server where the images should be located
|
||||
:param node_type: Image node_type
|
||||
:returns path: Final path
|
||||
"""
|
||||
|
||||
if node_type == 'QEMU':
|
||||
upload_endpoint = '/qemu/images'
|
||||
image_type = 'qemu'
|
||||
elif node_type == 'IOU':
|
||||
upload_endpoint = '/iou/images'
|
||||
image_type = 'iou'
|
||||
elif node_type == 'DYNAMIPS':
|
||||
upload_endpoint = '/dynamips/images'
|
||||
image_type = 'ios'
|
||||
else:
|
||||
raise Exception('Invalid node type')
|
||||
|
||||
filename = self._getRelativeImagePath(path, node_type).replace("\\", "/")
|
||||
Controller.instance().postCompute('{}/{}'.format(upload_endpoint, filename), server, None, body=pathlib.Path(path), progressText="Uploading {}".format(filename), timeout=None)
|
||||
|
||||
try:
|
||||
Controller.instance().post(
|
||||
f"/images/upload/{filename}",
|
||||
callback=None,
|
||||
params={"image_type": image_type},
|
||||
body=pathlib.Path(path),
|
||||
progress_text="Uploading {}".format(filename),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
return
|
||||
except HttpClientError as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
parent,
|
||||
"Image upload",
|
||||
f"Could not upload image {filename}: {e}"
|
||||
)
|
||||
|
||||
return filename
|
||||
|
||||
def _getRelativeImagePath(self, path, node_type):
|
||||
@@ -164,7 +181,7 @@ class ImageManager:
|
||||
:returns: path to the default images directory
|
||||
"""
|
||||
|
||||
return copy.copy(LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)['images_path'])
|
||||
return copy.copy(Controller.instance().settings()['images_path'])
|
||||
|
||||
def getDirectoryForType(self, node_type):
|
||||
"""
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import urllib.parse
|
||||
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.http_client_error import HttpClientError, HttpClientCancelledRequestError
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.registry.image import Image
|
||||
from gns3.controller import Controller
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -27,64 +29,43 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class ImageUploadManager(object):
|
||||
"""
|
||||
Manager over the image upload. Encapsulates file uploads to computes or via controller.
|
||||
Manager over the image upload
|
||||
"""
|
||||
|
||||
def __init__(self, image, controller, compute_id, callback=None, directFileUpload=False):
|
||||
self._image = image
|
||||
self._compute_id = compute_id
|
||||
self._callback = callback
|
||||
self._directFileUpload = directFileUpload
|
||||
self._controller = controller
|
||||
def __init__(self, image: Image, controller: Controller, parent: QtWidgets.QWidget):
|
||||
|
||||
self._image = image
|
||||
self._controller = controller
|
||||
self._parent = parent
|
||||
|
||||
def upload(self) -> bool:
|
||||
|
||||
def upload(self):
|
||||
if not os.path.exists(self._image.path):
|
||||
log.error("Image '{}' could not be found".format(self._image.path))
|
||||
return
|
||||
if self._directFileUpload:
|
||||
# first obtain endpoint and know when target request
|
||||
self._controller.getEndpoint(self._getComputePath(), self._compute_id, self._onLoadEndpointCallback, showProgress=False)
|
||||
else:
|
||||
self._fileUploadToController()
|
||||
return False
|
||||
return self._fileUploadToController()
|
||||
|
||||
def _getComputePath(self):
|
||||
return '/{emulator}/images/{filename}'.format(emulator=self._image.emulator, filename=self._image.filename)
|
||||
def _fileUploadToController(self) -> bool:
|
||||
|
||||
def _onLoadEndpointCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
if "message" in result:
|
||||
log.error("Error while getting endpoint: {}".format(result["message"]))
|
||||
return
|
||||
|
||||
# we know where is the endpoint and we trying to post there a file
|
||||
endpoint = result['endpoint']
|
||||
self._fileUploadToCompute(endpoint)
|
||||
|
||||
def _checkIfSuccessfulCallback(self, result, error=False, **kwargs):
|
||||
if error:
|
||||
connection_error = kwargs.get('connection_error', False)
|
||||
if connection_error:
|
||||
log.debug("During direct file upload compute is not visible. Fallback to upload via controller.")
|
||||
# there was an issue with connection, probably we don't have a direct access to compute
|
||||
# we need to fallback to uploading files via controller
|
||||
self._fileUploadToController()
|
||||
else:
|
||||
if "message" in result:
|
||||
log.error("Error while direct file upload: {}".format(result["message"]))
|
||||
return
|
||||
self._callback(result, error, **kwargs)
|
||||
|
||||
def _fileUploadToCompute(self, endpoint):
|
||||
log.debug("Uploading image '{}' to compute".format(self._image.path))
|
||||
parse_results = urllib.parse.urlparse(endpoint)
|
||||
network_manager = self._controller.getHttpClient().getNetworkManager()
|
||||
client = HTTPClient.fromUrl(endpoint, network_manager=network_manager)
|
||||
# We don't retry connection as in case of fail we try direct file upload
|
||||
client.setMaxRetryConnection(0)
|
||||
client.createHTTPQuery('POST', parse_results.path, self._checkIfSuccessfulCallback, body=pathlib.Path(self._image.path),
|
||||
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None, prefix="")
|
||||
|
||||
def _fileUploadToController(self):
|
||||
log.debug("Uploading image '{}' to controller".format(self._image.path))
|
||||
self._controller.postCompute(self._getComputePath(), self._compute_id, self._callback, body=pathlib.Path(self._image.path),
|
||||
context={"image_path": self._image.path}, progressText="Uploading {}".format(self._image.filename), timeout=None)
|
||||
try:
|
||||
self._controller.post(
|
||||
f"/images/upload/{self._image.filename}",
|
||||
callback=None,
|
||||
params={"allow_raw_image": True},
|
||||
body=pathlib.Path(self._image.path),
|
||||
context={"image_path": self._image.path},
|
||||
progress_text="Uploading {}".format(self._image.filename),
|
||||
timeout=None,
|
||||
wait=True
|
||||
)
|
||||
except HttpClientCancelledRequestError:
|
||||
return False
|
||||
except HttpClientError as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self._parent,
|
||||
"Image upload",
|
||||
f"Could not upload image {self._image.filename}: {e}"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -92,7 +92,7 @@ class DrawingItem:
|
||||
|
||||
def updateDrawing(self):
|
||||
if self._id and not self.deleting() and self._project:
|
||||
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), showProgress=False)
|
||||
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__(), show_progress=False)
|
||||
|
||||
@qslot
|
||||
def updateDrawingCallback(self, result, error=False, **kwargs):
|
||||
|
||||
@@ -159,6 +159,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
"""
|
||||
self._link.deleteLink()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset this link
|
||||
"""
|
||||
self._link.resetLink()
|
||||
|
||||
def link(self):
|
||||
"""
|
||||
Returns the link attached to this link item.
|
||||
@@ -282,6 +288,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
resume_action.triggered.connect(self._suspendActionSlot)
|
||||
menu.addAction(resume_action)
|
||||
|
||||
# reset
|
||||
reset_action = QtWidgets.QAction("Reset", menu)
|
||||
reset_action.setIcon(get_icon('reload.svg'))
|
||||
reset_action.triggered.connect(self._resetActionSlot)
|
||||
menu.addAction(reset_action)
|
||||
|
||||
# style
|
||||
style_action = QtWidgets.QAction("Style", menu)
|
||||
style_action.setIcon(get_icon("node_conception.svg"))
|
||||
@@ -340,6 +352,14 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
self._deleteActionSlot()
|
||||
return
|
||||
|
||||
def _resetActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the reset action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
self.reset()
|
||||
|
||||
def _deleteActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the delete action in the
|
||||
|
||||
40
gns3/link.py
40
gns3/link.py
@@ -117,12 +117,13 @@ class Link(QtCore.QObject):
|
||||
else:
|
||||
self._capture_file = QtCore.QFile(self._capture_file_path)
|
||||
self._capture_file.open(QtCore.QFile.WriteOnly)
|
||||
self._response_stream = Controller.instance().get("/projects/{project_id}/links/{link_id}/pcap".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
None,
|
||||
showProgress=False,
|
||||
downloadProgressCallback=self._downloadPcapProgress,
|
||||
ignoreErrors=True, # If something is wrong avoid disconnect us from server
|
||||
timeout=None)
|
||||
self._response_stream = Controller.instance().get(
|
||||
"/projects/{project_id}/links/{link_id}/capture/stream".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
callback=None,
|
||||
show_progress=False,
|
||||
download_progress_callback=self._downloadPcapProgress,
|
||||
timeout=None
|
||||
)
|
||||
log.debug("Has successfully started capturing packets on link {} to '{}'".format(self._link_id, self._capture_file_path))
|
||||
else:
|
||||
self._response_stream = None
|
||||
@@ -348,12 +349,35 @@ class Link(QtCore.QObject):
|
||||
# let the GUI know about this link has been deleted
|
||||
self.delete_link_signal.emit(self._id)
|
||||
|
||||
def resetLink(self):
|
||||
"""
|
||||
Resets this link.
|
||||
"""
|
||||
|
||||
log.debug("reset link from {} {} to {} {}".format(self._source_node.name(),
|
||||
self._source_port.name(),
|
||||
self._destination_node.name(),
|
||||
self._destination_port.name()))
|
||||
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/reset".format(project_id=self.project().id(),
|
||||
link_id=self._link_id),
|
||||
self._linkResetCallback)
|
||||
|
||||
def _linkResetCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Called after the link is reset.
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("Error while resetting link: {}".format(result["message"]))
|
||||
return
|
||||
|
||||
def startCapture(self, data_link_type, capture_file_name):
|
||||
data = {
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type
|
||||
}
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/start_capture".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/capture/start".format(project_id=self.project().id(), link_id=self._link_id),
|
||||
self._startCaptureCallback,
|
||||
body=data)
|
||||
|
||||
@@ -384,7 +408,7 @@ class Link(QtCore.QObject):
|
||||
# except OSError as e:
|
||||
# log.error("Cannot remove file {}: {}".format(self._capture_file_path, e))
|
||||
self._capture_file_path = None
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/stop_capture".format(project_id=self.project().id(),
|
||||
Controller.instance().post("/projects/{project_id}/links/{link_id}/capture/stop".format(project_id=self.project().id(),
|
||||
link_id=self._link_id),
|
||||
self._stopCaptureCallback)
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ import psutil
|
||||
from .qt import QtCore, QtWidgets
|
||||
from .version import __version__, __version_info__
|
||||
from .utils import parse_version
|
||||
from .local_server_config import LocalServerConfig
|
||||
from .settings import LOCAL_SERVER_SETTINGS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -100,10 +98,10 @@ class LocalConfig(QtCore.QObject):
|
||||
# migrate post version 2.2.0 configuration file
|
||||
shutil.copyfile(old_config_path, self._config_file)
|
||||
# reset the local server path and ubridge path
|
||||
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
|
||||
settings["path"] = ""
|
||||
settings["ubridge_path"] = ""
|
||||
LocalServerConfig.instance().saveSettings("Server", settings)
|
||||
# settings = LocalServerConfig.instance().loadSettings("Controller", CONTROLLER_SETTINGS)
|
||||
# settings["path"] = ""
|
||||
# settings["ubridge_path"] = ""
|
||||
# LocalServerConfig.instance().saveSettings("Controller", settings)
|
||||
else:
|
||||
# create a new config
|
||||
with open(self._config_file, "w", encoding="utf-8") as f:
|
||||
@@ -416,20 +414,6 @@ class LocalConfig(QtCore.QObject):
|
||||
settings["multi_profiles"] = value
|
||||
self.saveSectionSettings("MainWindow", settings)
|
||||
|
||||
def directFileUpload(self):
|
||||
"""
|
||||
:returns: Boolean. True if direct_file_upload is enabled
|
||||
"""
|
||||
|
||||
from gns3.settings import GENERAL_SETTINGS
|
||||
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["direct_file_upload"]
|
||||
|
||||
def setDirectFileUpload(self, value):
|
||||
from gns3.settings import GENERAL_SETTINGS
|
||||
settings = self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)
|
||||
settings["direct_file_upload"] = value
|
||||
self.saveSectionSettings("MainWindow", settings)
|
||||
|
||||
def showInterfaceLabelsOnNewProject(self):
|
||||
"""
|
||||
:returns: Boolean. True if show_interface_labels_on_new_project is enabled
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2021 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -22,8 +22,6 @@ import stat
|
||||
import shlex
|
||||
import socket
|
||||
import shutil
|
||||
import random
|
||||
import string
|
||||
import struct
|
||||
import psutil
|
||||
import signal
|
||||
@@ -31,13 +29,12 @@ import subprocess
|
||||
|
||||
|
||||
from gns3.qt import QtWidgets, QtCore, qslot
|
||||
from gns3.settings import LOCAL_SERVER_SETTINGS, DEFAULT_LOCAL_SERVER_HOST
|
||||
from gns3.settings import DEFAULT_CONTROLLER_HOST
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.utils.wait_for_connection_worker import WaitForConnectionWorker
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.utils.sudo import sudo
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.controller import Controller
|
||||
|
||||
|
||||
@@ -94,13 +91,6 @@ class LocalServer(QtCore.QObject):
|
||||
self._settings = {}
|
||||
self.localServerSettings()
|
||||
self._port = self._settings.get("port", 3080)
|
||||
if not self._settings.get("auto_start", True):
|
||||
if self._settings.get("host") is None:
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
else:
|
||||
self._http_client = None
|
||||
|
||||
self._stopping = False
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.setInterval(5000)
|
||||
@@ -122,27 +112,6 @@ class LocalServer(QtCore.QObject):
|
||||
return MainWindow.instance()
|
||||
return self._parent
|
||||
|
||||
def _checkWindowsService(self, service_name):
|
||||
|
||||
try:
|
||||
import pywintypes
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
except ImportError as e:
|
||||
log.error("Could not check if the {} service is running: {}".format(service_name, e))
|
||||
return
|
||||
|
||||
try:
|
||||
if win32serviceutil.QueryServiceStatus(service_name, None)[1] != win32service.SERVICE_RUNNING:
|
||||
return False
|
||||
except pywintypes.error as e:
|
||||
if e.winerror == 1060: # service is not installed
|
||||
return False
|
||||
else:
|
||||
log.error("Could not check if the {} service is running: {}".format(service_name, e.strerror))
|
||||
|
||||
return True
|
||||
|
||||
def _checkUbridgePermissions(self):
|
||||
"""
|
||||
Checks that uBridge can interact with network interfaces.
|
||||
@@ -199,12 +168,6 @@ class LocalServer(QtCore.QObject):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _passwordGenerate(self):
|
||||
"""
|
||||
Generate a random password
|
||||
"""
|
||||
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(64))
|
||||
|
||||
def localServerSettings(self):
|
||||
"""
|
||||
Returns the local server settings.
|
||||
@@ -212,14 +175,9 @@ class LocalServer(QtCore.QObject):
|
||||
:returns: local server settings (dict)
|
||||
"""
|
||||
|
||||
settings = LocalServerConfig.instance().loadSettings("Server", LOCAL_SERVER_SETTINGS)
|
||||
settings = Controller.instance().settings()
|
||||
self._settings = copy.copy(settings)
|
||||
|
||||
# user & password
|
||||
if settings["auth"] is True and not settings["user"].strip():
|
||||
settings["user"] = "admin"
|
||||
settings["password"] = self._passwordGenerate()
|
||||
|
||||
# local GNS3 server path
|
||||
local_server_path = shutil.which(settings["path"].strip())
|
||||
if local_server_path is None:
|
||||
@@ -248,14 +206,14 @@ class LocalServer(QtCore.QObject):
|
||||
"""
|
||||
|
||||
if "host" in new_settings and new_settings["host"] is None:
|
||||
new_settings["host"] = DEFAULT_LOCAL_SERVER_HOST
|
||||
new_settings["host"] = DEFAULT_CONTROLLER_HOST
|
||||
old_settings = copy.copy(self._settings)
|
||||
if not self._settings:
|
||||
self._settings = new_settings
|
||||
else:
|
||||
self._settings.update(new_settings)
|
||||
self._port = self._settings["port"]
|
||||
LocalServerConfig.instance().saveSettings("Server", self._settings)
|
||||
Controller.instance().setSettings(self._settings)
|
||||
|
||||
# Settings have changed we need to restart the server
|
||||
if old_settings != self._settings:
|
||||
@@ -275,12 +233,6 @@ class LocalServer(QtCore.QObject):
|
||||
else:
|
||||
self.stopLocalServer(wait=True)
|
||||
|
||||
if self._settings.get("host") is None:
|
||||
self._http_client = None
|
||||
else:
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
|
||||
def shouldLocalServerAutoStart(self):
|
||||
"""
|
||||
Returns either the local server
|
||||
@@ -321,29 +273,24 @@ class LocalServer(QtCore.QObject):
|
||||
Try to start the embedded gns3 server.
|
||||
"""
|
||||
|
||||
if not self.shouldLocalServerAutoStart():
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
return
|
||||
|
||||
if self.isLocalServerRunning() and self._server_started_by_me:
|
||||
local_server_already_running = self.isLocalServerRunning()
|
||||
if local_server_already_running and self._server_started_by_me:
|
||||
return True
|
||||
|
||||
# We check if two gui are not launched at the same time
|
||||
# to avoid killing the server of the other GUI
|
||||
if not LocalConfig.isMainGui():
|
||||
log.info("Not the main GUI, will not auto start the server")
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
Controller.instance().connect()
|
||||
return True
|
||||
|
||||
if self.isLocalServerRunning():
|
||||
if local_server_already_running:
|
||||
log.debug("A local server already running on this host")
|
||||
# Try to kill the server. The server can be still running after
|
||||
# if the server was started by hand
|
||||
self._killAlreadyRunningServer()
|
||||
|
||||
if not self.isLocalServerRunning():
|
||||
if not local_server_already_running:
|
||||
if not self.initLocalServer():
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Local server", "Could not start the local server process: {}".format(self._settings["path"]))
|
||||
return False
|
||||
@@ -354,15 +301,14 @@ class LocalServer(QtCore.QObject):
|
||||
if self.parent():
|
||||
worker = WaitForConnectionWorker(self._settings["host"], self._port)
|
||||
progress_dialog = ProgressDialog(worker,
|
||||
"Local server",
|
||||
"Connecting to server {} on port {}...".format(self._settings["host"], self._port),
|
||||
"Local controller",
|
||||
"Starting local controller {} on port {}...".format(self._settings["host"], self._port),
|
||||
"Cancel", busy=True, parent=self.parent())
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return False
|
||||
self._server_started_by_me = True
|
||||
self._http_client = HTTPClient(self._settings)
|
||||
Controller.instance().setHttpClient(self._http_client)
|
||||
Controller.instance().connect()
|
||||
return True
|
||||
|
||||
def initLocalServer(self):
|
||||
@@ -371,15 +317,6 @@ class LocalServer(QtCore.QObject):
|
||||
"""
|
||||
|
||||
self._checkUbridgePermissions()
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
import pywintypes
|
||||
try:
|
||||
if not self._checkWindowsService("npf") and not self._checkWindowsService("npcap"):
|
||||
log.warning("The NPF or NPCAP service is not installed, please install Winpcap or Npcap and reboot.")
|
||||
except pywintypes.error as e:
|
||||
log.warning("Could not check if the NPF or Npcap service is running: {}".format(e.strerror))
|
||||
|
||||
self._port = self._settings["port"]
|
||||
# check the local server path
|
||||
local_server_path = self.localServerPath()
|
||||
@@ -472,7 +409,7 @@ class LocalServer(QtCore.QObject):
|
||||
pass
|
||||
except OSError as e:
|
||||
log.warning("could not delete server log file {}: {}".format(logpath, e))
|
||||
command += ' --log="{}" --pid="{}"'.format(logpath, self._pid_path())
|
||||
command += ' --logfile="{}" --pid="{}"'.format(logpath, self._pid_path())
|
||||
|
||||
log.debug("Starting local server process with {}".format(command))
|
||||
try:
|
||||
@@ -521,19 +458,8 @@ class LocalServer(QtCore.QObject):
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
status, json_data = HTTPClient(self._settings).getSynchronous("GET", "/version")
|
||||
if status == 401: # Auth issue that need to be solved later
|
||||
return True
|
||||
elif json_data is None:
|
||||
return False
|
||||
elif status != 200:
|
||||
return False
|
||||
else:
|
||||
version = json_data.get("version", None)
|
||||
if version is None:
|
||||
log.debug("Server is not a GNS3 server")
|
||||
return False
|
||||
return True
|
||||
http_client = HTTPClient(self._settings)
|
||||
return http_client.checkServerRunning()
|
||||
|
||||
def stopLocalServer(self, wait=False):
|
||||
"""
|
||||
@@ -544,13 +470,14 @@ class LocalServer(QtCore.QObject):
|
||||
|
||||
if self.localServerProcessIsRunning():
|
||||
self._stopping = True
|
||||
log.debug("Stopping local server (PID={})".format(self._local_server_process.pid))
|
||||
log.debug("Stopping local controller (PID={})".format(self._local_server_process.pid))
|
||||
# local server is running, let's stop it
|
||||
if self._http_client:
|
||||
self._http_client.shutdown()
|
||||
http_client = Controller.instance().httpClient()
|
||||
if http_client:
|
||||
http_client.shutdown()
|
||||
if wait:
|
||||
worker = StopLocalServerWorker(self._local_server_process)
|
||||
progress_dialog = ProgressDialog(worker, "Local server", "Waiting for the local server to stop...", None, busy=True, parent=self.parent())
|
||||
progress_dialog = ProgressDialog(worker, "Local server", "Waiting for the local controller to stop...", None, busy=True, parent=self.parent())
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
if self._local_server_process.returncode is None:
|
||||
@@ -573,8 +500,8 @@ class LocalServer(QtCore.QObject):
|
||||
self._local_server_process.wait(timeout=60)
|
||||
except subprocess.TimeoutExpired:
|
||||
proceed = QtWidgets.QMessageBox.question(self.parent(),
|
||||
"Local server",
|
||||
"The Local server cannot be stopped, would you like to kill it?",
|
||||
"Local controller",
|
||||
"The local controller cannot be stopped, would you like to kill it?",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No)
|
||||
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import configparser
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalServerConfig:
|
||||
|
||||
"""
|
||||
Local server configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, config_file=None):
|
||||
|
||||
appname = "GNS3"
|
||||
|
||||
self._config = configparser.RawConfigParser()
|
||||
|
||||
if config_file:
|
||||
self._config_file = config_file
|
||||
else:
|
||||
if sys.platform.startswith("win"):
|
||||
filename = "gns3_server.ini"
|
||||
else:
|
||||
filename = "gns3_server.conf"
|
||||
|
||||
from .local_config import LocalConfig
|
||||
if sys.platform.startswith("win"):
|
||||
self._config_file = os.path.join(LocalConfig.instance().configDirectory(), filename)
|
||||
else:
|
||||
self._config_file = os.path.join(LocalConfig.instance().configDirectory(), filename)
|
||||
|
||||
try:
|
||||
# create the config file if it doesn't exist
|
||||
open(self._config_file, "a").close()
|
||||
except OSError as e:
|
||||
log.error("Could not create the local server configuration {}: {}".format(self._config_file, e))
|
||||
self.readConfig()
|
||||
|
||||
def setConfigFile(self, path):
|
||||
"""
|
||||
Change the location of the server config (use for test)
|
||||
"""
|
||||
self._config = configparser.RawConfigParser()
|
||||
self._config_file = path
|
||||
self.readConfig()
|
||||
|
||||
def readConfig(self):
|
||||
"""
|
||||
Read the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._config.read(self._config_file, encoding="utf-8")
|
||||
except (OSError, configparser.Error, UnicodeEncodeError, UnicodeDecodeError) as e:
|
||||
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
|
||||
|
||||
def writeConfig(self):
|
||||
"""
|
||||
Write the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
log.debug("Write configuration file %s", self._config_file)
|
||||
with open(self._config_file, "w", encoding="utf-8") as fp:
|
||||
self._config.write(fp)
|
||||
except (OSError, configparser.Error) as e:
|
||||
log.error("Could not write the local server configuration {}: {}".format(self._config_file, e))
|
||||
|
||||
def loadSettings(self, section, default_settings):
|
||||
"""
|
||||
Get all the settings from a given section.
|
||||
|
||||
:param section: section name
|
||||
:param default_settings: setting names and default values (dict)
|
||||
|
||||
:returns: settings (dict)
|
||||
"""
|
||||
|
||||
if section not in self._config:
|
||||
self._config[section] = {}
|
||||
|
||||
settings = {}
|
||||
for name, default in default_settings.items():
|
||||
if isinstance(default, bool):
|
||||
settings[name] = self._config[section].getboolean(name, default)
|
||||
elif isinstance(default, int):
|
||||
settings[name] = self._config[section].getint(name, default)
|
||||
elif isinstance(default, float):
|
||||
settings[name] = self._config[section].getfloat(name, default)
|
||||
else:
|
||||
settings[name] = self._config[section].get(name, default)
|
||||
if settings[name] == "None":
|
||||
settings[name] = None
|
||||
|
||||
# sync with the config file
|
||||
self.saveSettings(section, settings)
|
||||
return settings
|
||||
|
||||
def saveSettings(self, section, settings):
|
||||
"""
|
||||
Save all the settings in a given section.
|
||||
|
||||
:param section: section name
|
||||
:param settings: settings to save (dict)
|
||||
"""
|
||||
|
||||
changed = False
|
||||
if section not in self._config:
|
||||
self._config[section] = {}
|
||||
changed = True
|
||||
|
||||
for name, value in settings.items():
|
||||
if name not in self._config[section] or self._config[section][name] != str(value):
|
||||
self._config[section][name] = str(value)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.writeConfig()
|
||||
|
||||
def deleteSetting(self, section, name):
|
||||
"""
|
||||
Delete a specific setting in a given section.
|
||||
|
||||
:param section: section name
|
||||
:param name: setting name to delete
|
||||
"""
|
||||
|
||||
if section in self._config and name in self._config[section]:
|
||||
del self._config[section][name]
|
||||
self.writeConfig()
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of LocalServerConfig.
|
||||
|
||||
:returns: instance of Config
|
||||
"""
|
||||
|
||||
if not hasattr(LocalServerConfig, "_instance"):
|
||||
LocalServerConfig._instance = LocalServerConfig()
|
||||
return LocalServerConfig._instance
|
||||
@@ -146,7 +146,6 @@ def main():
|
||||
frozen_dir,
|
||||
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
|
||||
os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
|
||||
os.path.normpath(os.path.join(frozen_dir, 'traceng'))
|
||||
]
|
||||
|
||||
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")
|
||||
@@ -185,9 +184,9 @@ def main():
|
||||
# catch exceptions to write them in a file
|
||||
sys.excepthook = exceptionHook
|
||||
|
||||
# we only support Python 3 version >= 3.4
|
||||
if sys.version_info < (3, 4):
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
# we only support Python 3 version >= 3.7
|
||||
if sys.version_info < (3, 7):
|
||||
raise SystemExit("Python 3.7 or higher is required")
|
||||
|
||||
if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
|
||||
raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
|
||||
@@ -39,6 +39,7 @@ from .dialogs.snapshots_dialog import SnapshotsDialog
|
||||
from .dialogs.export_debug_dialog import ExportDebugDialog
|
||||
from .dialogs.doctor_dialog import DoctorDialog
|
||||
from .dialogs.edit_project_dialog import EditProjectDialog
|
||||
from .dialogs.image_dialog import ImageDialog
|
||||
from .dialogs.setup_wizard import SetupWizard
|
||||
from .settings import GENERAL_SETTINGS
|
||||
from .items.node_item import NodeItem
|
||||
@@ -86,7 +87,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.setupUi(self)
|
||||
self.setUnifiedTitleAndToolBarOnMac(True)
|
||||
|
||||
# These widgets will be disabled when no project is loaded
|
||||
# This widgets will be disable when you have no project loaded
|
||||
self.disableWhenNoProjectWidgets = [
|
||||
self.uiGraphicsView,
|
||||
self.uiAnnotateMenu,
|
||||
@@ -100,9 +101,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiEditProjectAction,
|
||||
self.uiDeleteProjectAction,
|
||||
self.uiImportExportConfigsAction,
|
||||
self.uiLockAllAction
|
||||
self.uiLockAllAction,
|
||||
self.uiShowReadmeAction
|
||||
]
|
||||
|
||||
for widget in self.disableWhenNoProjectWidgets:
|
||||
widget.setEnabled(False)
|
||||
|
||||
self.disableWhenControllerNotConnectedWidgets = [
|
||||
self.uiNewProjectAction,
|
||||
self.uiOpenProjectAction,
|
||||
self.uiImportProjectAction,
|
||||
self.uiNewTemplateAction,
|
||||
self.uiImportProjectAction,
|
||||
self.uiOpenApplianceAction,
|
||||
self.uiWebUIAction,
|
||||
self.uiNodesDockWidget
|
||||
]
|
||||
|
||||
for widget in self.disableWhenControllerNotConnectedWidgets:
|
||||
widget.setEnabled(False)
|
||||
|
||||
self._notif_dialog = NotifDialog(self)
|
||||
# Setup logger
|
||||
logging.getLogger().addHandler(NotifDialogHandler(self._notif_dialog))
|
||||
@@ -117,8 +136,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Controller.instance().setParent(self)
|
||||
LocalServer.instance().setParent(self)
|
||||
|
||||
HTTPClient.setProgressCallback(Progress.instance(self))
|
||||
|
||||
self._first_file_load = True
|
||||
self._open_project_path = None
|
||||
self._loadSettings()
|
||||
@@ -172,7 +189,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiFileMenu.insertActions(self.uiQuitAction, self.recent_file_actions)
|
||||
self.recent_file_actions_separator = self.uiFileMenu.insertSeparator(self.uiQuitAction)
|
||||
self.recent_file_actions_separator.setVisible(False)
|
||||
self.updateRecentFileActions()
|
||||
#self.updateRecentFileActions()
|
||||
|
||||
# add recent projects to the File menu
|
||||
for i in range(0, self._maxrecent_files):
|
||||
@@ -208,6 +225,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiOpenProjectAction.triggered.connect(self.openProjectActionSlot)
|
||||
self.uiOpenApplianceAction.triggered.connect(self.openApplianceActionSlot)
|
||||
self.uiNewTemplateAction.triggered.connect(self._newTemplateActionSlot)
|
||||
self.uiImageManagementAction.triggered.connect(self._imageManagementActionSlot)
|
||||
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
|
||||
self.uiExportProjectAction.triggered.connect(self._exportProjectActionSlot)
|
||||
self.uiImportProjectAction.triggered.connect(self._importProjectActionSlot)
|
||||
@@ -232,6 +250,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiResetPortLabelsAction.triggered.connect(self._resetPortLabelsActionSlot)
|
||||
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
|
||||
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
|
||||
self.uiShowReadmeAction.triggered.connect(self._showReadmeActionSlot)
|
||||
self.uiSnapToGridAction.triggered.connect(self._snapToGridActionSlot)
|
||||
self.uiLockAllAction.triggered.connect(self._lockActionSlot)
|
||||
self.uiResetDocksAction.triggered.connect(self._resetDocksSlot)
|
||||
@@ -323,7 +342,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
def _openWebInterfaceActionSlot(self):
|
||||
if Controller.instance().connected():
|
||||
base_url = Controller.instance().httpClient().fullUrl()
|
||||
webui_url = "{}/static/web-ui/bundled".format(base_url)
|
||||
webui_url = f"{base_url}/static/web-ui/bundled"
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(webui_url))
|
||||
|
||||
def _showGridActionSlot(self):
|
||||
@@ -412,6 +431,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _imageManagementActionSlot(self):
|
||||
"""
|
||||
Called when user wants to manage images
|
||||
"""
|
||||
|
||||
dialog = ImageDialog(self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
@qslot
|
||||
def openApplianceActionSlot(self, *args):
|
||||
"""
|
||||
@@ -508,7 +536,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._appliance_wizard.exec_()
|
||||
elif path.endswith(".gns3"):
|
||||
if Controller.instance().isRemote():
|
||||
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a portable project (.gns3p) instead")
|
||||
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a project (.gns3p) instead")
|
||||
return
|
||||
else:
|
||||
Topology.instance().loadProject(path)
|
||||
@@ -548,6 +576,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Refresh widgets that should be visible or not
|
||||
"""
|
||||
|
||||
for widget in self.disableWhenControllerNotConnectedWidgets:
|
||||
widget.setEnabled(Controller.instance().connected())
|
||||
|
||||
# No projects
|
||||
if Topology.instance().project() is None:
|
||||
for widget in self.disableWhenNoProjectWidgets:
|
||||
@@ -967,12 +998,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot to open the setup wizard.
|
||||
"""
|
||||
|
||||
with Progress.instance().context(min_duration=0):
|
||||
setup_wizard = SetupWizard(self)
|
||||
setup_wizard.show()
|
||||
res = setup_wizard.exec_()
|
||||
# start and connect to the local server if needed
|
||||
LocalServer.instance().localServerAutoStartIfRequired()
|
||||
setup_wizard = SetupWizard(self)
|
||||
setup_wizard.show()
|
||||
setup_wizard.exec_()
|
||||
|
||||
def _aboutQtActionSlot(self):
|
||||
"""
|
||||
@@ -1106,6 +1134,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
Topology.instance().editReadme()
|
||||
|
||||
def _showReadmeActionSlot(self):
|
||||
"""
|
||||
Slot to show the README file
|
||||
"""
|
||||
Topology.instance().showReadme()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self._notif_dialog.resize()
|
||||
super().resizeEvent(event)
|
||||
@@ -1241,13 +1275,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if not self._settings["hide_setup_wizard"]:
|
||||
self._setupWizardActionSlot()
|
||||
else:
|
||||
# start and connect to the local server if needed
|
||||
LocalServer.instance().localServerAutoStartIfRequired()
|
||||
if self._open_file_at_startup:
|
||||
self.loadPath(self._open_file_at_startup)
|
||||
self._open_file_at_startup = None
|
||||
elif Topology.instance().project() is None:
|
||||
self._newProjectActionSlot()
|
||||
if Controller.instance().isRemote():
|
||||
Controller.instance().connect()
|
||||
else:
|
||||
# start and connect to the local server if needed
|
||||
LocalServer.instance().localServerAutoStartIfRequired()
|
||||
|
||||
if self._settings["check_for_update"]:
|
||||
# automatic check for update every week (604800 seconds)
|
||||
@@ -1400,9 +1432,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.recent_file_actions_separator.setVisible(False)
|
||||
|
||||
def _controllerConnectedSlot(self):
|
||||
|
||||
self.updateRecentFileActions()
|
||||
self._refreshVisibleWidgets()
|
||||
|
||||
if self._settings["hide_setup_wizard"]:
|
||||
if self._open_file_at_startup:
|
||||
self.loadPath(self._open_file_at_startup)
|
||||
self._open_file_at_startup = None
|
||||
elif Topology.instance().project() is None:
|
||||
self._newProjectActionSlot()
|
||||
|
||||
def run_later(self, counter, callback):
|
||||
"""
|
||||
Run a function after X milliseconds
|
||||
@@ -1415,20 +1455,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def _exportProjectActionSlot(self):
|
||||
"""
|
||||
Slot called to export a portable project
|
||||
Slot called to export a project
|
||||
"""
|
||||
|
||||
Topology.instance().exportProject()
|
||||
|
||||
def _importProjectActionSlot(self):
|
||||
"""
|
||||
Slot called to import a portable project
|
||||
Slot called to import a project
|
||||
"""
|
||||
|
||||
directory = self._portable_project_dir
|
||||
if not os.path.exists(directory):
|
||||
directory = Topology.instance().projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open portable project", directory,
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open project", directory,
|
||||
"All files (*.*);;GNS3 Portable Project (*.gns3project *.gns3p)",
|
||||
"GNS3 Portable Project (*.gns3project *.gns3p)")
|
||||
if path:
|
||||
|
||||
@@ -19,12 +19,9 @@ from gns3.modules.builtin import Builtin
|
||||
from gns3.modules.dynamips import Dynamips
|
||||
from gns3.modules.iou import IOU
|
||||
from gns3.modules.vpcs import VPCS
|
||||
from gns3.modules.traceng import TraceNG
|
||||
from gns3.modules.virtualbox import VirtualBox
|
||||
from gns3.modules.qemu import Qemu
|
||||
from gns3.modules.vmware import VMware
|
||||
from gns3.modules.docker import Docker
|
||||
|
||||
#MODULES = [Builtin, VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker, TraceNG]
|
||||
#FIXME: deactivate TraceNG module
|
||||
MODULES = [Builtin, VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker]
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
Built-in module implementation.
|
||||
"""
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
|
||||
from ..module import Module
|
||||
from .cloud import Cloud
|
||||
@@ -53,14 +51,15 @@ class Builtin(Module):
|
||||
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
server_settings = {}
|
||||
config = LocalServerConfig.instance()
|
||||
if self._settings["default_nat_interface"]:
|
||||
# save some settings to the local server config file
|
||||
server_settings["default_nat_interface"] = self._settings["default_nat_interface"]
|
||||
config.saveSettings(self.__class__.__name__, server_settings)
|
||||
else:
|
||||
config.deleteSetting(self.__class__.__name__, "default_nat_interface")
|
||||
# FIXME: handle server side config
|
||||
# server_settings = {}
|
||||
# config = LocalServerConfig.instance()
|
||||
# if self._settings["default_nat_interface"]:
|
||||
# # save some settings to the local server config file
|
||||
# server_settings["default_nat_interface"] = self._settings["default_nat_interface"]
|
||||
# config.saveSettings(self.__class__.__name__, server_settings)
|
||||
# else:
|
||||
# config.deleteSetting(self.__class__.__name__, "default_nat_interface")
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
|
||||
@@ -42,6 +42,7 @@ class Cloud(Node):
|
||||
self._always_on = True
|
||||
self._interfaces = {}
|
||||
self._cloud_settings = {"ports_mapping": [],
|
||||
"usage": "",
|
||||
"remote_console_host": CLOUD_SETTINGS["remote_console_host"],
|
||||
"remote_console_port": CLOUD_SETTINGS["remote_console_port"],
|
||||
"remote_console_type": CLOUD_SETTINGS["remote_console_type"],
|
||||
@@ -139,7 +140,8 @@ class Cloud(Node):
|
||||
port_info += " Port {name} {description}\n".format(name=port.name(),
|
||||
description=port.description())
|
||||
|
||||
return info + port_info
|
||||
usage = "\n" + self._settings.get("usage")
|
||||
return info + port_info + usage
|
||||
|
||||
def configPage(self):
|
||||
"""
|
||||
|
||||
@@ -48,7 +48,7 @@ class CloudWizard(VMWizard, Ui_CloudNodeWizard):
|
||||
"""
|
||||
|
||||
settings = {"name": self.uiNameLineEdit.text(),
|
||||
"symbol": ":/symbols/cloud.svg",
|
||||
"symbol": "cloud",
|
||||
"compute_id": self._compute_id}
|
||||
|
||||
return settings
|
||||
|
||||
@@ -54,7 +54,7 @@ class EthernetHubWizard(VMWizard, Ui_EthernetHubWizard):
|
||||
"name": "Ethernet{}".format(port_number)})
|
||||
|
||||
settings = {"name": self.uiNameLineEdit.text(),
|
||||
"symbol": ":/symbols/hub.svg",
|
||||
"symbol": "hub",
|
||||
"category": Node.switches,
|
||||
"compute_id": self._compute_id,
|
||||
"ports_mapping": ports}
|
||||
|
||||
@@ -54,10 +54,10 @@ class EthernetSwitchWizard(VMWizard, Ui_EthernetSwitchWizard):
|
||||
"name": "Ethernet{}".format(port_number),
|
||||
"type": "access",
|
||||
"vlan": 1,
|
||||
"ethertype": ""})
|
||||
"ethertype": "0x8100"})
|
||||
|
||||
settings = {"name": self.uiNameLineEdit.text(),
|
||||
"symbol": ":/symbols/ethernet_switch.svg",
|
||||
"symbol": "ethernet_switch",
|
||||
"category": Node.switches,
|
||||
"compute_id": self._compute_id,
|
||||
"ports_mapping": ports}
|
||||
|
||||
@@ -473,9 +473,13 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
|
||||
if index != -1:
|
||||
self.uiCategoryComboBox.setCurrentIndex(index)
|
||||
|
||||
Controller.instance().getCompute("/network/interfaces", settings["compute_id"],
|
||||
self._getInterfacesFromServerCallback,
|
||||
progressText="Retrieving network interfaces...")
|
||||
Controller.instance().getCompute(
|
||||
"/network/interfaces",
|
||||
settings["compute_id"],
|
||||
self._getInterfacesFromServerCallback,
|
||||
progress_text="Retrieving network interfaces...",
|
||||
wait=True
|
||||
)
|
||||
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
@@ -490,6 +494,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
|
||||
self._interfaces = self._node.interfaces()
|
||||
self._loadNetworkInterfaces(self._interfaces)
|
||||
|
||||
self.uiUsageTextEdit.setPlainText(settings["usage"])
|
||||
# load the current ports
|
||||
self._ports = []
|
||||
self.uiEthernetListWidget.clear()
|
||||
@@ -560,4 +565,6 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
|
||||
settings["ports_mapping"] = self._ports
|
||||
else:
|
||||
settings["ports_mapping"] = self._ports
|
||||
|
||||
settings["usage"] = self.uiUsageTextEdit.toPlainText()
|
||||
return settings
|
||||
|
||||
@@ -87,7 +87,11 @@ class CloudPreferencesPage(QtWidgets.QWidget, Ui_CloudPreferencesPageWidget):
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", cloud_node["remote_console_type"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", cloud_node["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(cloud_node["compute_id"]).name()])
|
||||
compute_id = cloud_node.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -83,7 +83,11 @@ class EthernetHubPreferencesPage(QtWidgets.QWidget, Ui_EthernetHubPreferencesPag
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ethernet_hub.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ethernet_hub["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ethernet_hub["compute_id"]).name()])
|
||||
compute_id = ethernet_hub.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Number of ports:", str(len(ethernet_hub["ports_mapping"]))])
|
||||
|
||||
@@ -82,7 +82,11 @@ class EthernetSwitchPreferencesPage(QtWidgets.QWidget, Ui_EthernetSwitchPreferen
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ethernet_switch.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ethernet_switch["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ethernet_switch["compute_id"]).name()])
|
||||
compute_id = ethernet_switch.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -34,12 +34,13 @@ BUILTIN_SETTINGS = {
|
||||
|
||||
CLOUD_SETTINGS = {
|
||||
"name": "",
|
||||
"usage": "",
|
||||
"remote_console_host": "127.0.0.1",
|
||||
"remote_console_port": 23,
|
||||
"remote_console_type": "none",
|
||||
"remote_console_http_path": "/",
|
||||
"default_name_format": "Cloud{0}",
|
||||
"symbol": ":/symbols/cloud.svg",
|
||||
"symbol": "cloud",
|
||||
"category": Node.end_devices,
|
||||
"ports_mapping": [],
|
||||
"node_type": "cloud"
|
||||
@@ -48,7 +49,7 @@ CLOUD_SETTINGS = {
|
||||
ETHERNET_HUB_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "Hub{0}",
|
||||
"symbol": ":/symbols/hub.svg",
|
||||
"symbol": "hub",
|
||||
"category": Node.switches,
|
||||
"ports_mapping": [],
|
||||
"node_type": "ethernet_hub"
|
||||
@@ -57,7 +58,7 @@ ETHERNET_HUB_SETTINGS = {
|
||||
ETHERNET_SWITCH_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "Switch{0}",
|
||||
"symbol": ":/symbols/ethernet_switch.svg",
|
||||
"symbol": "ethernet_switch",
|
||||
"category": Node.switches,
|
||||
"console_type": "none",
|
||||
"ports_mapping": [],
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>979</width>
|
||||
<height>564</height>
|
||||
<width>1034</width>
|
||||
<height>575</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -555,6 +555,16 @@
|
||||
<zorder>uiConsoleTypeLabel</zorder>
|
||||
<zorder>uiConsoleTypeComboBox</zorder>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Usage</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="uiUsageTextEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
# Created by: PyQt5 UI code generator 5.15.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_cloudConfigPageWidget(object):
|
||||
def setupUi(self, cloudConfigPageWidget):
|
||||
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
|
||||
cloudConfigPageWidget.resize(979, 564)
|
||||
cloudConfigPageWidget.resize(1034, 575)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(cloudConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(cloudConfigPageWidget)
|
||||
@@ -281,6 +284,14 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiConsoleTypeLabel.raise_()
|
||||
self.uiConsoleTypeComboBox.raise_()
|
||||
self.uiTabWidget.addTab(self.MiscTab, "")
|
||||
self.tab = QtWidgets.QWidget()
|
||||
self.tab.setObjectName("tab")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab)
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.uiUsageTextEdit = QtWidgets.QPlainTextEdit(self.tab)
|
||||
self.uiUsageTextEdit.setObjectName("uiUsageTextEdit")
|
||||
self.verticalLayout_3.addWidget(self.uiUsageTextEdit)
|
||||
self.uiTabWidget.addTab(self.tab, "")
|
||||
self.verticalLayout.addWidget(self.uiTabWidget)
|
||||
|
||||
self.retranslateUi(cloudConfigPageWidget)
|
||||
@@ -335,4 +346,4 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiConsoleTypeComboBox.setItemText(4, _translate("cloudConfigPageWidget", "https"))
|
||||
self.uiConsoleTypeComboBox.setItemText(5, _translate("cloudConfigPageWidget", "none"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc."))
|
||||
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("cloudConfigPageWidget", "Usage"))
|
||||
|
||||
@@ -39,7 +39,7 @@ class DockerVMWizard(VMWizard, Ui_DockerVMWizard):
|
||||
|
||||
super().__init__(docker_containers, parent)
|
||||
self._docker_containers = docker_containers
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/docker.png"))
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/docker_guest.svg"))
|
||||
|
||||
self.uiNewImageRadioButton.setChecked(True)
|
||||
self._existingImageRadioButtonToggledSlot(False)
|
||||
|
||||
@@ -45,14 +45,17 @@ class DockerVM(Node):
|
||||
"custom_adapters": DOCKER_CONTAINER_SETTINGS["custom_adapters"],
|
||||
"start_command": DOCKER_CONTAINER_SETTINGS["start_command"],
|
||||
"environment": DOCKER_CONTAINER_SETTINGS["environment"],
|
||||
"aux": None,
|
||||
"console_type": DOCKER_CONTAINER_SETTINGS["console_type"],
|
||||
"console_auto_start": DOCKER_CONTAINER_SETTINGS["console_auto_start"],
|
||||
"aux_type": DOCKER_CONTAINER_SETTINGS["aux_type"],
|
||||
"console_resolution": DOCKER_CONTAINER_SETTINGS["console_resolution"],
|
||||
"console_http_port": DOCKER_CONTAINER_SETTINGS["console_http_port"],
|
||||
"console_http_path": DOCKER_CONTAINER_SETTINGS["console_http_path"],
|
||||
"extra_hosts": DOCKER_CONTAINER_SETTINGS["extra_hosts"],
|
||||
"extra_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"]}
|
||||
"extra_volumes": DOCKER_CONTAINER_SETTINGS["extra_volumes"],
|
||||
"memory": DOCKER_CONTAINER_SETTINGS["memory"],
|
||||
"cpus": DOCKER_CONTAINER_SETTINGS["cpus"],
|
||||
}
|
||||
|
||||
self.settings().update(docker_vm_settings)
|
||||
|
||||
@@ -68,6 +71,7 @@ class DockerVM(Node):
|
||||
Local ID is {id} and server ID is {node_id}
|
||||
Docker image is "{image}"
|
||||
Console is on port {console} and type is {console_type}
|
||||
Aux console is on port {aux} and type is {aux_type}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
node_id=self._node_id,
|
||||
@@ -76,6 +80,8 @@ class DockerVM(Node):
|
||||
port=self.compute().port(),
|
||||
console=self._settings["console"],
|
||||
console_type=self._settings["console_type"],
|
||||
aux=self._settings["aux"],
|
||||
aux_type=self._settings["aux_type"],
|
||||
image=self._settings["image"])
|
||||
|
||||
port_info = ""
|
||||
@@ -116,18 +122,6 @@ class DockerVM(Node):
|
||||
from .pages.docker_vm_configuration_page import DockerVMConfigurationPage
|
||||
return DockerVMConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return DockerVM.isValidRfc1123Hostname(hostname)
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -100,11 +100,14 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
self.uiEnvironmentTextEdit.setText(settings["environment"])
|
||||
self.uiConsoleTypeComboBox.setCurrentIndex(self.uiConsoleTypeComboBox.findText(settings["console_type"]))
|
||||
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
|
||||
self.uiAuxTypeComboBox.setCurrentIndex(self.uiAuxTypeComboBox.findText(settings["aux_type"]))
|
||||
self.uiConsoleResolutionComboBox.setCurrentIndex(self.uiConsoleResolutionComboBox.findText(settings["console_resolution"]))
|
||||
self.uiConsoleHttpPortSpinBox.setValue(settings["console_http_port"])
|
||||
self.uiHttpConsolePathLineEdit.setText(settings["console_http_path"])
|
||||
self.uiExtraHostsTextEdit.setPlainText(settings["extra_hosts"])
|
||||
self.uiExtraVolumeTextEdit.setPlainText("\n".join(settings["extra_volumes"]))
|
||||
self.uiMaxMemorySpinBox.setValue(settings["memory"])
|
||||
self.uiMaxCPUsDoubleSpinBox.setValue(settings["cpus"])
|
||||
|
||||
if not group:
|
||||
self.uiNameLineEdit.setText(settings["name"])
|
||||
@@ -172,12 +175,15 @@ class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
|
||||
settings["environment"] = self.uiEnvironmentTextEdit.toPlainText()
|
||||
settings["console_type"] = self.uiConsoleTypeComboBox.currentText()
|
||||
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
|
||||
settings["aux_type"] = self.uiAuxTypeComboBox.currentText()
|
||||
settings["console_resolution"] = self.uiConsoleResolutionComboBox.currentText()
|
||||
settings["console_http_port"] = self.uiConsoleHttpPortSpinBox.value()
|
||||
settings["console_http_path"] = self.uiHttpConsolePathLineEdit.text()
|
||||
settings["extra_hosts"] = self.uiExtraHostsTextEdit.toPlainText()
|
||||
# only tidy input here, validation is performed server side
|
||||
settings["extra_volumes"] = [ y for x in self.uiExtraVolumeTextEdit.toPlainText().split("\n") for y in [ x.strip() ] if y ]
|
||||
settings["memory"] = self.uiMaxMemorySpinBox.value()
|
||||
settings["cpus"] = self.uiMaxCPUsDoubleSpinBox.value()
|
||||
|
||||
if not group:
|
||||
adapters = self.uiAdapterSpinBox.value()
|
||||
|
||||
@@ -81,11 +81,16 @@ class DockerVMPreferencesPage(QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidge
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", docker_container.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Image name:", docker_container["image"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(docker_container["compute_id"]).name()])
|
||||
compute_id = docker_container.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", str(docker_container["console_type"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(docker_container["console_auto_start"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", str(docker_container["aux_type"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", docker_container["default_name_format"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Adapters:", str(docker_container["adapters"])])
|
||||
if docker_container["start_command"]:
|
||||
|
||||
@@ -29,7 +29,7 @@ DOCKER_SETTINGS = {
|
||||
DOCKER_CONTAINER_SETTINGS = {
|
||||
"default_name_format": "{name}-{0}",
|
||||
"usage": "",
|
||||
"symbol": ":/symbols/docker_guest.svg",
|
||||
"symbol": "docker_guest",
|
||||
"category": Node.end_devices,
|
||||
"start_command": "",
|
||||
"name": "",
|
||||
@@ -39,10 +39,13 @@ DOCKER_CONTAINER_SETTINGS = {
|
||||
"environment": "",
|
||||
"console_type": "telnet",
|
||||
"console_auto_start": False,
|
||||
"aux_type": "none",
|
||||
"console_resolution": "1024x768",
|
||||
"console_http_port": 80,
|
||||
"console_http_path": "/",
|
||||
"extra_hosts": "",
|
||||
"extra_volumes": [],
|
||||
"memory": 0,
|
||||
"cpus": 0,
|
||||
"node_type": "docker"
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>938</width>
|
||||
<height>872</height>
|
||||
<width>961</width>
|
||||
<height>1126</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -116,14 +116,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="uiConsoleTypeLabel">
|
||||
<property name="text">
|
||||
<string>Console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="10" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetNoConstraint</enum>
|
||||
@@ -166,14 +166,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="uiConsoleResolutionLabel">
|
||||
<property name="text">
|
||||
<string>VNC console resolution:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QComboBox" name="uiConsoleResolutionComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -227,14 +227,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>HTTP port in the container:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QSpinBox" name="uiConsoleHttpPortSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
@@ -244,17 +244,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>HTTP path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="14" column="1">
|
||||
<widget class="QLineEdit" name="uiHttpConsolePathLineEdit"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="uiEnvironmentLabel">
|
||||
<property name="text">
|
||||
<string>Environment variables:
|
||||
@@ -268,23 +268,81 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<item row="15" column="1">
|
||||
<widget class="QTextEdit" name="uiEnvironmentTextEdit"/>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="uiNetworkConfigLabel">
|
||||
<property name="text">
|
||||
<string>Network configuration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<item row="16" column="1">
|
||||
<widget class="QPushButton" name="uiNetworkConfigEditButton">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiMaxCPUsLabel">
|
||||
<property name="text">
|
||||
<string>Maximum CPUs:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QDoubleSpinBox" name="uiMaxCPUsDoubleSpinBox">
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="uiMaxMemoryLabel">
|
||||
<property name="text">
|
||||
<string>Maximum memory:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="uiMaxMemorySpinBox">
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>32</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="uiAuxTypeLabel">
|
||||
<property name="text">
|
||||
<string>Auxiliary console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QComboBox" name="uiAuxTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>telnet</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
# Created by: PyQt5 UI code generator 5.15.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_dockerVMConfigPageWidget(object):
|
||||
def setupUi(self, dockerVMConfigPageWidget):
|
||||
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
|
||||
dockerVMConfigPageWidget.resize(938, 872)
|
||||
dockerVMConfigPageWidget.resize(961, 1126)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
|
||||
@@ -74,7 +75,7 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.gridLayout.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 1)
|
||||
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
|
||||
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 7, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleTypeLabel, 10, 0, 1, 1)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
@@ -89,10 +90,10 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.uiConsoleAutoStartCheckBox = QtWidgets.QCheckBox(self.tab)
|
||||
self.uiConsoleAutoStartCheckBox.setObjectName("uiConsoleAutoStartCheckBox")
|
||||
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 7, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 10, 1, 1, 1)
|
||||
self.uiConsoleResolutionLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiConsoleResolutionLabel.setObjectName("uiConsoleResolutionLabel")
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 8, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionLabel, 12, 0, 1, 1)
|
||||
self.uiConsoleResolutionComboBox = QtWidgets.QComboBox(self.tab)
|
||||
self.uiConsoleResolutionComboBox.setObjectName("uiConsoleResolutionComboBox")
|
||||
self.uiConsoleResolutionComboBox.addItem("")
|
||||
@@ -108,32 +109,56 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
self.gridLayout.addWidget(self.uiConsoleResolutionComboBox, 8, 1, 1, 1)
|
||||
self.label = QtWidgets.QLabel(self.tab)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 9, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label, 13, 0, 1, 1)
|
||||
self.uiConsoleHttpPortSpinBox = QtWidgets.QSpinBox(self.tab)
|
||||
self.uiConsoleHttpPortSpinBox.setMinimum(1)
|
||||
self.uiConsoleHttpPortSpinBox.setMaximum(65535)
|
||||
self.uiConsoleHttpPortSpinBox.setObjectName("uiConsoleHttpPortSpinBox")
|
||||
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 9, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsoleHttpPortSpinBox, 13, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(self.tab)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout.addWidget(self.label_2, 10, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.label_2, 14, 0, 1, 1)
|
||||
self.uiHttpConsolePathLineEdit = QtWidgets.QLineEdit(self.tab)
|
||||
self.uiHttpConsolePathLineEdit.setObjectName("uiHttpConsolePathLineEdit")
|
||||
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 10, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiHttpConsolePathLineEdit, 14, 1, 1, 1)
|
||||
self.uiEnvironmentLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiEnvironmentLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
|
||||
self.uiEnvironmentLabel.setWordWrap(False)
|
||||
self.uiEnvironmentLabel.setObjectName("uiEnvironmentLabel")
|
||||
self.gridLayout.addWidget(self.uiEnvironmentLabel, 11, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiEnvironmentLabel, 15, 0, 1, 1)
|
||||
self.uiEnvironmentTextEdit = QtWidgets.QTextEdit(self.tab)
|
||||
self.uiEnvironmentTextEdit.setObjectName("uiEnvironmentTextEdit")
|
||||
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 11, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiEnvironmentTextEdit, 15, 1, 1, 1)
|
||||
self.uiNetworkConfigLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiNetworkConfigLabel.setObjectName("uiNetworkConfigLabel")
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 12, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigLabel, 16, 0, 1, 1)
|
||||
self.uiNetworkConfigEditButton = QtWidgets.QPushButton(self.tab)
|
||||
self.uiNetworkConfigEditButton.setObjectName("uiNetworkConfigEditButton")
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 12, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiNetworkConfigEditButton, 16, 1, 1, 1)
|
||||
self.uiMaxCPUsLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiMaxCPUsLabel.setObjectName("uiMaxCPUsLabel")
|
||||
self.gridLayout.addWidget(self.uiMaxCPUsLabel, 8, 0, 1, 1)
|
||||
self.uiMaxCPUsDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.tab)
|
||||
self.uiMaxCPUsDoubleSpinBox.setDecimals(1)
|
||||
self.uiMaxCPUsDoubleSpinBox.setSingleStep(0.1)
|
||||
self.uiMaxCPUsDoubleSpinBox.setObjectName("uiMaxCPUsDoubleSpinBox")
|
||||
self.gridLayout.addWidget(self.uiMaxCPUsDoubleSpinBox, 8, 1, 1, 1)
|
||||
self.uiMaxMemoryLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiMaxMemoryLabel.setObjectName("uiMaxMemoryLabel")
|
||||
self.gridLayout.addWidget(self.uiMaxMemoryLabel, 7, 0, 1, 1)
|
||||
self.uiMaxMemorySpinBox = QtWidgets.QSpinBox(self.tab)
|
||||
self.uiMaxMemorySpinBox.setMaximum(65535)
|
||||
self.uiMaxMemorySpinBox.setSingleStep(32)
|
||||
self.uiMaxMemorySpinBox.setObjectName("uiMaxMemorySpinBox")
|
||||
self.gridLayout.addWidget(self.uiMaxMemorySpinBox, 7, 1, 1, 1)
|
||||
self.uiAuxTypeLabel = QtWidgets.QLabel(self.tab)
|
||||
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
|
||||
self.gridLayout.addWidget(self.uiAuxTypeLabel, 11, 0, 1, 1)
|
||||
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.tab)
|
||||
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.gridLayout.addWidget(self.uiAuxTypeComboBox, 11, 1, 1, 1)
|
||||
self.uiTabWidget.addTab(self.tab, "")
|
||||
self.tab_2 = QtWidgets.QWidget()
|
||||
self.tab_2.setObjectName("tab_2")
|
||||
@@ -211,6 +236,12 @@ class Ui_dockerVMConfigPageWidget(object):
|
||||
"(KEY=VALUE, one per line)"))
|
||||
self.uiNetworkConfigLabel.setText(_translate("dockerVMConfigPageWidget", "Network configuration"))
|
||||
self.uiNetworkConfigEditButton.setText(_translate("dockerVMConfigPageWidget", "Edit"))
|
||||
self.uiMaxCPUsLabel.setText(_translate("dockerVMConfigPageWidget", "Maximum CPUs:"))
|
||||
self.uiMaxMemoryLabel.setText(_translate("dockerVMConfigPageWidget", "Maximum memory:"))
|
||||
self.uiMaxMemorySpinBox.setSuffix(_translate("dockerVMConfigPageWidget", " MB"))
|
||||
self.uiAuxTypeLabel.setText(_translate("dockerVMConfigPageWidget", "Auxiliary console type:"))
|
||||
self.uiAuxTypeComboBox.setItemText(0, _translate("dockerVMConfigPageWidget", "telnet"))
|
||||
self.uiAuxTypeComboBox.setItemText(1, _translate("dockerVMConfigPageWidget", "none"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("dockerVMConfigPageWidget", "General settings"))
|
||||
self.uiExtraHostsLabel.setText(_translate("dockerVMConfigPageWidget", "Extra hosts added\n"
|
||||
"to the /etc/hosts file.\n"
|
||||
|
||||
@@ -25,7 +25,6 @@ import hashlib
|
||||
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.image_manager import ImageManager
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.controller import Controller
|
||||
from gns3.template_manager import TemplateManager
|
||||
from gns3.template import Template
|
||||
@@ -145,18 +144,19 @@ class Dynamips(Module):
|
||||
# save the settings
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
# save some settings to the local server config file
|
||||
server_settings = {
|
||||
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
|
||||
"ghost_ios_support": self._settings["ghost_ios_support"],
|
||||
"sparse_memory_support": self._settings["sparse_memory_support"],
|
||||
"mmap_support": self._settings["mmap_support"],
|
||||
}
|
||||
|
||||
if self._settings["dynamips_path"]:
|
||||
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
|
||||
config = LocalServerConfig.instance()
|
||||
config.saveSettings(self.__class__.__name__, server_settings)
|
||||
# FIXME: handle server side config
|
||||
# # save some settings to the local server config file
|
||||
# server_settings = {
|
||||
# "allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
|
||||
# "ghost_ios_support": self._settings["ghost_ios_support"],
|
||||
# "sparse_memory_support": self._settings["sparse_memory_support"],
|
||||
# "mmap_support": self._settings["mmap_support"],
|
||||
# }
|
||||
#
|
||||
# if self._settings["dynamips_path"]:
|
||||
# server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
|
||||
# config = LocalServerConfig.instance()
|
||||
# config.saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
def updateImageIdlepc(self, image_path, idlepc):
|
||||
"""
|
||||
|
||||
@@ -220,13 +220,13 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
|
||||
ram = self.uiRamSpinBox.value()
|
||||
Controller.instance().postCompute("/auto_idlepc",
|
||||
self._compute_id,
|
||||
self._computeAutoIdlepcCallback,
|
||||
callback=self._computeAutoIdlepcCallback,
|
||||
timeout=None,
|
||||
body={
|
||||
"image": image,
|
||||
"platform": platform,
|
||||
"ram": ram})
|
||||
self.uiIdlePCFinderPushButton.setEnabled(False)
|
||||
"ram": ram},
|
||||
wait=True)
|
||||
|
||||
def _etherSwitchSlot(self, state):
|
||||
"""
|
||||
@@ -316,7 +316,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
|
||||
super().initializePage(page_id)
|
||||
|
||||
if self.page(page_id) == self.uiIOSImageWizardPage:
|
||||
self.loadImagesList("/dynamips/images")
|
||||
self.loadImagesList("ios")
|
||||
elif self.page(page_id) == self.uiNameWizardPage:
|
||||
self._prefillPlatform()
|
||||
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())
|
||||
|
||||
@@ -71,6 +71,7 @@ class Router(Node):
|
||||
"console_type": "telnet",
|
||||
"console_auto_start": False,
|
||||
"aux": None,
|
||||
"aux_type": "none",
|
||||
"mac_addr": None,
|
||||
"system_id": "FTX0945W0MY",
|
||||
"slot0": None,
|
||||
@@ -101,11 +102,14 @@ class Router(Node):
|
||||
"""
|
||||
|
||||
log.debug("{} is requesting Idle-PC proposals".format(self.name()))
|
||||
self.controllerHttpGet("/nodes/{node_id}/dynamips/idlepc_proposals".format(node_id=self._node_id),
|
||||
callback,
|
||||
timeout=240,
|
||||
context={"router": self},
|
||||
progressText="Computing Idle-PC values, please wait...")
|
||||
self.controllerHttpGet(
|
||||
"/nodes/{node_id}/dynamips/idlepc_proposals".format(node_id=self._node_id),
|
||||
callback,
|
||||
context={"router": self},
|
||||
progress_text="Computing Idle-PC values, please wait...",
|
||||
timeout=240,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def computeAutoIdlepc(self, callback):
|
||||
"""
|
||||
@@ -113,11 +117,14 @@ class Router(Node):
|
||||
"""
|
||||
|
||||
log.debug("{} is requesting Idle-PC proposals".format(self.name()))
|
||||
self.controllerHttpGet("/nodes/{node_id}/dynamips/auto_idlepc".format(node_id=self._node_id),
|
||||
callback,
|
||||
timeout=240,
|
||||
context={"router": self},
|
||||
progressText="Computing Idle-PC values, please wait...")
|
||||
self.controllerHttpGet(
|
||||
"/nodes/{node_id}/dynamips/auto_idlepc".format(node_id=self._node_id),
|
||||
callback,
|
||||
context={"router": self},
|
||||
progress_text="Computing Idle-PC values, please wait...",
|
||||
timeout=240,
|
||||
wait=True
|
||||
)
|
||||
|
||||
def idlepc(self):
|
||||
"""
|
||||
@@ -242,7 +249,8 @@ class Router(Node):
|
||||
Local ID is {id} and server ID is {node_id}
|
||||
Dynamips ID is {dynamips_id}
|
||||
Hardware is Dynamips emulated Cisco {platform} {specific_info} with {ram}MB RAM and {nvram}KB NVRAM
|
||||
Console is on port {console} and type is {console_type}, AUX console is on port {aux}
|
||||
Console is on port {console} and type is {console_type}
|
||||
Auxiliary console is on port {aux} and type is {aux_type}
|
||||
IOS image is "{image_name}"
|
||||
{idlepc_info}
|
||||
PCMCIA disks: disk0 is {disk0}MB and disk1 is {disk1}MB
|
||||
@@ -260,6 +268,7 @@ class Router(Node):
|
||||
console=self._settings["console"],
|
||||
console_type=self._settings["console_type"],
|
||||
aux=self._settings["aux"],
|
||||
aux_type=self._settings["aux_type"],
|
||||
image_name=os.path.basename(self._settings["image"]),
|
||||
idlepc_info=idlepc_info,
|
||||
disk0=self._settings["disk0"],
|
||||
@@ -298,23 +307,6 @@ class Router(Node):
|
||||
from ..pages.ios_router_configuration_page import IOSRouterConfigurationPage
|
||||
return IOSRouterConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
# IOS names must start with a letter, end with a letter or digit, and
|
||||
# have as interior characters only letters, digits, and hyphens.
|
||||
# They must be 63 characters or fewer (ARPANET rules).
|
||||
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -374,6 +374,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
|
||||
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
|
||||
|
||||
# load the auxiliary console type
|
||||
index = self.uiAuxTypeComboBox.findText(settings["aux_type"])
|
||||
if index != -1:
|
||||
self.uiAuxTypeComboBox.setCurrentIndex(index)
|
||||
|
||||
# load the memories and disks settings
|
||||
self.uiRamSpinBox.setValue(settings["ram"])
|
||||
self.uiNvramSpinBox.setValue(settings["nvram"])
|
||||
@@ -488,8 +493,6 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
|
||||
elif node and not node.validateHostname(name):
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
@@ -579,6 +582,9 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
settings["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()
|
||||
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
|
||||
|
||||
# save auxiliary console type
|
||||
settings["aux_type"] = self.uiAuxTypeComboBox.currentText().lower()
|
||||
|
||||
# save the memories and disks settings
|
||||
settings["ram"] = self.uiRamSpinBox.value()
|
||||
settings["nvram"] = self.uiNvramSpinBox.value()
|
||||
|
||||
@@ -386,7 +386,11 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", ios_router.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ios_router["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(ios_router["compute_id"]).name()])
|
||||
compute_id = ios_router.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Platform:", ios_router["platform"]])
|
||||
@@ -395,6 +399,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Image:", ios_router["image"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", ios_router["console_type"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(ios_router["console_auto_start"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", ios_router["aux_type"]])
|
||||
if ios_router["idlepc"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Idle-PC:", ios_router["idlepc"]])
|
||||
if ios_router["startup_config"]:
|
||||
|
||||
@@ -34,12 +34,13 @@ IOS_ROUTER_SETTINGS = {
|
||||
"default_name_format": "R{0}",
|
||||
"usage": "",
|
||||
"image": "",
|
||||
"symbol": ":/symbols/router.svg",
|
||||
"symbol": "router",
|
||||
"category": Node.routers,
|
||||
"startup_config": "",
|
||||
"private_config": "",
|
||||
"console_type": "telnet",
|
||||
"console_auto_start": False,
|
||||
"aux_type": "none",
|
||||
"platform": "",
|
||||
"idlepc": "",
|
||||
"idlemax": 500,
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>980</width>
|
||||
<height>734</height>
|
||||
<width>964</width>
|
||||
<height>830</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -250,7 +250,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="12" column="1" colspan="2">
|
||||
<item row="14" column="1" colspan="2">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -263,6 +263,27 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="12" column="2">
|
||||
<widget class="QComboBox" name="uiAuxTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>telnet</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiAuxTypeLabel">
|
||||
<property name="text">
|
||||
<string>Aux console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiMemoriesPageWidget">
|
||||
@@ -270,7 +291,16 @@
|
||||
<string>Memories and disks</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
@@ -448,7 +478,16 @@
|
||||
<string>Slots</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -649,7 +688,16 @@
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -828,7 +876,16 @@
|
||||
<string>Environment</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
# Created by: PyQt5 UI code generator 5.15.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_iosRouterConfigPageWidget(object):
|
||||
def setupUi(self, iosRouterConfigPageWidget):
|
||||
iosRouterConfigPageWidget.setObjectName("iosRouterConfigPageWidget")
|
||||
iosRouterConfigPageWidget.resize(980, 734)
|
||||
iosRouterConfigPageWidget.resize(964, 830)
|
||||
self.vboxlayout = QtWidgets.QVBoxLayout(iosRouterConfigPageWidget)
|
||||
self.vboxlayout.setObjectName("vboxlayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(iosRouterConfigPageWidget)
|
||||
@@ -143,7 +146,15 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.horizontalLayout.addWidget(self.uiConsoleAutoStartCheckBox)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout, 11, 2, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem, 12, 1, 1, 2)
|
||||
self.gridLayout_2.addItem(spacerItem, 14, 1, 1, 2)
|
||||
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.gridLayout_2.addWidget(self.uiAuxTypeComboBox, 12, 2, 1, 1)
|
||||
self.uiAuxTypeLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
|
||||
self.gridLayout_2.addWidget(self.uiAuxTypeLabel, 12, 0, 1, 2)
|
||||
self.uiTabWidget.addTab(self.uiGeneralPageWidget, "")
|
||||
self.uiMemoriesPageWidget = QtWidgets.QWidget()
|
||||
self.uiMemoriesPageWidget.setObjectName("uiMemoriesPageWidget")
|
||||
@@ -582,6 +593,9 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiConsoleTypeComboBox.setItemText(0, _translate("iosRouterConfigPageWidget", "telnet"))
|
||||
self.uiConsoleTypeComboBox.setItemText(1, _translate("iosRouterConfigPageWidget", "none"))
|
||||
self.uiConsoleAutoStartCheckBox.setText(_translate("iosRouterConfigPageWidget", "Auto start console"))
|
||||
self.uiAuxTypeComboBox.setItemText(0, _translate("iosRouterConfigPageWidget", "telnet"))
|
||||
self.uiAuxTypeComboBox.setItemText(1, _translate("iosRouterConfigPageWidget", "none"))
|
||||
self.uiAuxTypeLabel.setText(_translate("iosRouterConfigPageWidget", "Aux console type:"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralPageWidget), _translate("iosRouterConfigPageWidget", "General"))
|
||||
self.uiRamLabel.setText(_translate("iosRouterConfigPageWidget", "RAM size:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB"))
|
||||
@@ -639,4 +653,3 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiSensor4SpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " C"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiEnvironmentPageWidget), _translate("iosRouterConfigPageWidget", "Environment"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiUsageTab), _translate("iosRouterConfigPageWidget", "Usage"))
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class IOUDeviceWizard(VMWithImagesWizard, Ui_IOUDeviceWizard):
|
||||
if self.page(page_id) == self.uiNameWizardPage:
|
||||
if not self.uiIOUImageToolButton.isEnabled():
|
||||
QtWidgets.QMessageBox.warning(self, "IOU image", "You have chosen to use a remote server, please provide the path to an IOU image located on this server!")
|
||||
self.loadImagesList("/iou/images")
|
||||
self.loadImagesList("iou")
|
||||
|
||||
def getSettings(self):
|
||||
"""
|
||||
|
||||
@@ -119,23 +119,6 @@ class IOUDevice(Node):
|
||||
from .pages.iou_device_configuration_page import iouDeviceConfigurationPage
|
||||
return iouDeviceConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
# IOS names must start with a letter, end with a letter or digit, and
|
||||
# have as interior characters only letters, digits, and hyphens.
|
||||
# They must be 63 characters or fewer (ARPANET rules).
|
||||
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -244,8 +244,6 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "IOU device name cannot be empty!")
|
||||
elif node and not node.validateHostname(name):
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOU device: {}".format(name))
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
|
||||
@@ -88,7 +88,11 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", iou_device.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", iou_device["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(iou_device["compute_id"]).name()])
|
||||
compute_id = iou_device.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
# Compute doesn't exists
|
||||
pass
|
||||
|
||||
@@ -32,7 +32,7 @@ IOU_DEVICE_SETTINGS = {
|
||||
"default_name_format": "IOU{0}",
|
||||
"usage": "",
|
||||
"path": "",
|
||||
"symbol": ":/symbols/multilayer_switch.svg",
|
||||
"symbol": "multilayer_switch",
|
||||
"category": Node.routers,
|
||||
"startup_config": "",
|
||||
"private_config": "",
|
||||
|
||||
@@ -20,7 +20,6 @@ QEMU module implementation.
|
||||
"""
|
||||
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.controller import Controller
|
||||
from gns3.template_manager import TemplateManager
|
||||
from gns3.template import Template
|
||||
@@ -74,33 +73,11 @@ class Qemu(Module):
|
||||
|
||||
# save the settings
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
server_settings = {"enable_hardware_acceleration": self._settings["enable_hardware_acceleration"],
|
||||
"require_hardware_acceleration": self._settings["require_hardware_acceleration"]}
|
||||
LocalServerConfig.instance().saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
def getQemuBinariesFromServer(self, compute_id, callback, archs=None):
|
||||
"""
|
||||
Gets the QEMU binaries list from a server.
|
||||
|
||||
:param compute_id: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
|
||||
"""
|
||||
|
||||
request_body = None
|
||||
if archs is not None:
|
||||
request_body = {"archs": archs}
|
||||
Controller.instance().getCompute("/qemu/binaries", compute_id, callback, body=request_body)
|
||||
|
||||
def getQemuImgBinariesFromServer(self, compute_id, callback):
|
||||
"""
|
||||
Gets the QEMU-img binaries list from a server.
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
"""
|
||||
|
||||
Controller.instance().getCompute("/qemu/img-binaries", compute_id, callback)
|
||||
# FIXME: handle server side config
|
||||
# server_settings = {"enable_hardware_acceleration": self._settings["enable_hardware_acceleration"],
|
||||
# "require_hardware_acceleration": self._settings["require_hardware_acceleration"]}
|
||||
# LocalServerConfig.instance().saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
def getQemuCapabilitiesFromServer(self, compute_id, callback):
|
||||
"""
|
||||
@@ -112,27 +89,44 @@ class Qemu(Module):
|
||||
|
||||
Controller.instance().getCompute("/qemu/capabilities", compute_id, callback)
|
||||
|
||||
def createDiskImage(self, compute_id, callback, options):
|
||||
@staticmethod
|
||||
def getQemuPlatforms():
|
||||
"""
|
||||
Create a disk image on the remote server
|
||||
Returns all Qemu platforms
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
:param options: Options for the image creation
|
||||
:return: list of Qemu platforms
|
||||
"""
|
||||
|
||||
Controller.instance().postCompute("/qemu/img", compute_id, callback, body=options)
|
||||
|
||||
def updateDiskImage(self, compute_id, callback, options):
|
||||
"""
|
||||
Update a disk image on the remote server
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
:param options: Options for the image update
|
||||
"""
|
||||
|
||||
Controller.instance().putCompute("/qemu/img", compute_id, callback, body=options)
|
||||
return [
|
||||
"aarch64",
|
||||
"alpha",
|
||||
"arm",
|
||||
"cris",
|
||||
"i386",
|
||||
"lm32",
|
||||
"m68k",
|
||||
"microblaze",
|
||||
"microblazeel",
|
||||
"mips",
|
||||
"mips64",
|
||||
"mips64el",
|
||||
"mipsel",
|
||||
"moxie",
|
||||
"or32",
|
||||
"ppc",
|
||||
"ppc64",
|
||||
"ppcemb",
|
||||
"s390x",
|
||||
"sh4",
|
||||
"sh4eb",
|
||||
"sparc",
|
||||
"sparc64",
|
||||
"tricore",
|
||||
"unicore32",
|
||||
"x86_64",
|
||||
"xtensa",
|
||||
"xtensaeb"
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def getNodeClass(node_type, platform=None):
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Wizard for QEMU images.
|
||||
Wizard for QEMU disk images.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets, QFileDialog
|
||||
from .. import Qemu
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from ..ui.qemu_image_wizard_ui import Ui_QemuImageWizard
|
||||
|
||||
|
||||
@@ -32,19 +29,18 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
|
||||
Wizard to create a Qemu VM.
|
||||
|
||||
:param parent: parent widget
|
||||
:param server: Server where the image should be created
|
||||
:param node: node where the image should be created
|
||||
:param filename: Default filename of image.
|
||||
:param folder: Default folder for the image. If absent, defaults to Qemu's images folder.
|
||||
:param size: Default size (in MiB) for the image.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, server, filename="disk", folder=None, size=30000):
|
||||
def __init__(self, parent, node, filename="disk", size=30000):
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
self._server = server
|
||||
self._node = node
|
||||
self.setupUi(self)
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/qemu.svg"))
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/qemu_guest.svg"))
|
||||
|
||||
# Initialize "constants"
|
||||
self._mappings = {
|
||||
@@ -57,45 +53,19 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
|
||||
}
|
||||
|
||||
# isComplete() overrides
|
||||
self.uiSizeAndLocationWizardPage.isComplete = self._uiSizeAndLocationWizardPage_isComplete
|
||||
self.uiBinaryWizardPage.isComplete = self._uiBinaryWizardPage_isComplete
|
||||
self.uiNameAndSizeWizardPage.isComplete = self._uiNameAndSizeWizardPage_isComplete
|
||||
self.uiFormatWizardPage.isComplete = self._uiFormatWizardPage_isComplete
|
||||
|
||||
# Signal connections
|
||||
self.uiFormatRadios.buttonClicked.connect(self._formatChangedSlot)
|
||||
self.uiLocationLineEdit.textChanged.connect(self._locationChangedSlot)
|
||||
self.uiLocationBrowseToolButton.clicked.connect(self._browserSlot)
|
||||
self.uiDiskFilenameLineEdit.textChanged.connect(self._filenameChangedSlot)
|
||||
|
||||
# Finish setup
|
||||
self.page(self.pageIds()[-1]).validatePage = self._createDisk
|
||||
|
||||
# Default values
|
||||
Qemu.instance().getQemuImgBinariesFromServer(self._server,
|
||||
self._getQemuImgBinariesFromServerCallback)
|
||||
self.uiLocationLineEdit.setText(filename)
|
||||
self.uiDiskFilenameLineEdit.setText(filename)
|
||||
self.uiSizeSpinBox.setValue(size)
|
||||
self._formatChangedSlot(self.uiFormatQcow2Radio)
|
||||
|
||||
def _getQemuImgBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getQemuImgBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu-img binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiBinaryComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiBinaryComboBox.addItem(
|
||||
"{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"]
|
||||
)
|
||||
else:
|
||||
self.uiBinaryComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
self.uiBinaryWizardPage.completeChanged.emit()
|
||||
|
||||
def _getCreateDiskServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
:param result: server response
|
||||
@@ -105,45 +75,34 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Create disk", "{}".format(result["message"]))
|
||||
|
||||
def _uiSizeAndLocationWizardPage_isComplete(self):
|
||||
return not "" == self.uiLocationLineEdit.text()
|
||||
def _uiNameAndSizeWizardPage_isComplete(self):
|
||||
|
||||
def _uiBinaryWizardPage_isComplete(self):
|
||||
return self.uiFormatRadios.checkedButton() is not None and self.uiBinaryComboBox.currentData() is not None
|
||||
return not "" == self.uiDiskFilenameLineEdit.text()
|
||||
|
||||
def _locationChangedSlot(self, new_value):
|
||||
self.uiSizeAndLocationWizardPage.completeChanged.emit()
|
||||
def _uiFormatWizardPage_isComplete(self):
|
||||
|
||||
def _browserSlot(self):
|
||||
path, name_filter = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
'Image location',
|
||||
self.uiLocationLineEdit.text(),
|
||||
'{0} files (*{1});;All files (*)'.format(
|
||||
self.uiFormatRadios.checkedButton().text(),
|
||||
self._mappings[self.uiFormatRadios.checkedButton()][1]
|
||||
),
|
||||
options=QFileDialog.DontConfirmOverwrite
|
||||
)
|
||||
if path:
|
||||
self.uiLocationLineEdit.setText(path)
|
||||
return self.uiFormatRadios.checkedButton() is not None
|
||||
|
||||
def _filenameChangedSlot(self, new_value):
|
||||
|
||||
self.uiNameAndSizeWizardPage.completeChanged.emit()
|
||||
|
||||
def _formatChangedSlot(self, new_format):
|
||||
dir, filename = os.path.split(self.uiLocationLineEdit.text())
|
||||
|
||||
filename = self.uiDiskFilenameLineEdit.text().strip()
|
||||
try:
|
||||
filename = filename[:filename.rindex('.')] + self._mappings[new_format][1]
|
||||
except ValueError:
|
||||
# The file has no extension; Just give it one
|
||||
filename = filename + self._mappings[new_format][1]
|
||||
self.uiLocationLineEdit.setText(os.path.join(dir, filename))
|
||||
self.uiBinaryWizardPage.completeChanged.emit()
|
||||
self.uiDiskFilenameLineEdit.setText(filename)
|
||||
self.uiFormatWizardPage.completeChanged.emit()
|
||||
|
||||
def _createDisk(self):
|
||||
selected_format = self.uiFormatRadios.checkedButton()
|
||||
|
||||
selected_format = self.uiFormatRadios.checkedButton()
|
||||
disk_image_filename = self.uiDiskFilenameLineEdit.text().strip()
|
||||
options = {}
|
||||
options["path"] = self.uiLocationLineEdit.text()
|
||||
options["qemu_img"] = self.uiBinaryComboBox.currentData()
|
||||
options["format"] = self._mappings[selected_format][0]
|
||||
options["size"] = self.uiSizeSpinBox.value()
|
||||
|
||||
@@ -214,11 +173,12 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
|
||||
}
|
||||
options['subformat'] = two + size_mode_mappings[size_mode]
|
||||
|
||||
Qemu.instance().createDiskImage(self._server, self._getCreateDiskServerCallback, options)
|
||||
self._node.createDiskImage(disk_image_filename, options, self._getCreateDiskServerCallback)
|
||||
return True
|
||||
|
||||
def nextId(self):
|
||||
if self.page(self.currentId()) == self.uiBinaryWizardPage:
|
||||
|
||||
if self.page(self.currentId()) == self.uiFormatWizardPage:
|
||||
current_format = self.uiFormatRadios.checkedButton()
|
||||
if not current_format:
|
||||
return self.currentId()
|
||||
|
||||
@@ -43,7 +43,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
def __init__(self, qemu_vms, parent):
|
||||
|
||||
super().__init__(qemu_vms, parent)
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/qemu.svg"))
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/qemu_guest.svg"))
|
||||
|
||||
# Mandatory fields
|
||||
self.uiNameWizardPage.registerField("vm_name*", self.uiNameLineEdit)
|
||||
@@ -55,31 +55,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
self.addImageSelector(self.uiLinuxExistingImageRadioButton, self.uiInitrdImageListComboBox, self.uiInitrdImageLineEdit, self.uiInitrdImageToolButton, QemuVMConfigurationPage.getDiskImage)
|
||||
self.addImageSelector(self.uiLinuxExistingImageRadioButton, self.uiKernelImageListComboBox, self.uiKernelImageLineEdit, self.uiKernelImageToolButton, QemuVMConfigurationPage.getDiskImage)
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Validates the server.
|
||||
"""
|
||||
|
||||
if super().validateCurrentPage() is False:
|
||||
return False
|
||||
|
||||
if self.currentPage() == self.uiNameWizardPage:
|
||||
if self.uiLegacyASACheckBox.isChecked():
|
||||
QtWidgets.QMessageBox.warning(self, "Legacy ASA VM", "Running ASA (with initrd/kernel) is not recommended and will not work on Windows 10, please use ASAv instead")
|
||||
self.uiRamSpinBox.setValue(1024)
|
||||
else:
|
||||
self.uiRamSpinBox.setValue(256)
|
||||
|
||||
if self.currentPage() == self.uiBinaryMemoryWizardPage:
|
||||
if not self.uiQemuListComboBox.count():
|
||||
QtWidgets.QMessageBox.critical(self, "QEMU binaries", "Sorry, no QEMU binary has been found. Please make sure QEMU is installed before continuing")
|
||||
return False
|
||||
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
|
||||
|
||||
if sys.platform.startswith("darwin") and "GNS3.app" in qemu_path:
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu binaries", "This version of qemu is obsolete and provided only for compatibility with old GNS3 versions.\nPlease use Qemu in the GNS3 VM for full Qemu support.")
|
||||
return True
|
||||
|
||||
def initializePage(self, page_id):
|
||||
|
||||
super().initializePage(page_id)
|
||||
@@ -89,53 +64,13 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
QtWidgets.QMessageBox.warning(self, "QEMU on Windows or Mac", "The recommended way to run QEMU on Windows and OSX is to use the GNS3 VM")
|
||||
|
||||
if self.page(page_id) in [self.uiDiskWizardPage, self.uiInitrdKernelImageWizardPage]:
|
||||
self.loadImagesList("/qemu/images")
|
||||
elif self.page(page_id) == self.uiBinaryMemoryWizardPage:
|
||||
try:
|
||||
Qemu.instance().getQemuBinariesFromServer(self._compute_id, self._getQemuBinariesFromServerCallback)
|
||||
except ModuleError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getQemuBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiQemuListComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
|
||||
else:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
|
||||
is_64bit = sys.maxsize > 2 ** 32
|
||||
if ComputeManager.instance().localPlatform().startswith("win") and self.uiLocalRadioButton.isChecked():
|
||||
if self.uiLegacyASACheckBox.isChecked():
|
||||
search_string = r"qemu-0.13.0\qemu-system-i386w.exe"
|
||||
elif is_64bit:
|
||||
# default is qemu-system-x86_64w.exe on Windows 64-bit with a remote server
|
||||
search_string = "x86_64w.exe"
|
||||
else:
|
||||
# default is qemu-system-i386w.exe on Windows 32-bit with a remote server
|
||||
search_string = "i386w.exe"
|
||||
elif ComputeManager.instance().localPlatform().startswith("darwin") and hasattr(sys, "frozen") and self.uiLocalRadioButton.isChecked():
|
||||
search_string = "GNS3.app/Contents/MacOS/qemu/bin/qemu-system-x86_64"
|
||||
elif is_64bit:
|
||||
# default is qemu-system-x86_64 on other 64-bit platforms
|
||||
search_string = "x86_64"
|
||||
else:
|
||||
# default is qemu-system-i386 on other platforms
|
||||
search_string = "i386"
|
||||
|
||||
index = self.uiQemuListComboBox.findData(search_string, flags=QtCore.Qt.MatchEndsWith)
|
||||
self.loadImagesList("qemu")
|
||||
elif self.page(page_id) == self.uiPlatformMemoryWizardPage:
|
||||
platforms = Qemu.getQemuPlatforms()
|
||||
self.uiQemuPlatformComboBox.addItems(platforms)
|
||||
index = self.uiQemuPlatformComboBox.findText("x86_64", flags=QtCore.Qt.MatchEndsWith)
|
||||
if index != -1:
|
||||
self.uiQemuListComboBox.setCurrentIndex(index)
|
||||
self.uiQemuPlatformComboBox.setCurrentIndex(index)
|
||||
|
||||
def getSettings(self):
|
||||
"""
|
||||
@@ -145,11 +80,11 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
"""
|
||||
|
||||
console_type = self.uiQemuConsoleTypeComboBox.itemText(self.uiQemuConsoleTypeComboBox.currentIndex())
|
||||
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
|
||||
qemu_platform = self.uiQemuPlatformComboBox.itemText(self.uiQemuPlatformComboBox.currentIndex())
|
||||
settings = {
|
||||
"name": self.uiNameLineEdit.text(),
|
||||
"ram": self.uiRamSpinBox.value(),
|
||||
"qemu_path": qemu_path,
|
||||
"platform": qemu_platform,
|
||||
"compute_id": self._compute_id,
|
||||
"category": Node.end_devices,
|
||||
"console_type": console_type
|
||||
@@ -158,25 +93,8 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
if self.uiHdaDiskImageLineEdit.text().strip():
|
||||
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
|
||||
|
||||
if self.uiLegacyASACheckBox.isChecked():
|
||||
# special settings for legacy ASA VM
|
||||
settings["adapters"] = 4
|
||||
settings["initrd"] = self.uiInitrdImageLineEdit.text()
|
||||
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
|
||||
settings["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 -net nic"
|
||||
settings["options"] = "-machine accel=tcg -icount auto"
|
||||
if not sys.platform.startswith("darwin"):
|
||||
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
|
||||
settings["process_priority"] = "low"
|
||||
settings["symbol"] = ":/symbols/asa.svg"
|
||||
settings["category"] = Node.security_devices
|
||||
|
||||
if "options" not in settings:
|
||||
settings["options"] = ""
|
||||
if self._compute_id == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.11.0\qemu.exe")) or \
|
||||
(sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
|
||||
settings["options"] += " -vga none -vnc none"
|
||||
settings["legacy_networking"] = True
|
||||
settings["options"] = settings["options"].strip()
|
||||
|
||||
return settings
|
||||
@@ -188,8 +106,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
|
||||
current_id = self.currentId()
|
||||
if self.page(current_id) == self.uiDiskWizardPage:
|
||||
if self.uiLegacyASACheckBox.isChecked():
|
||||
return self.uiDiskWizardPage.nextId()
|
||||
return -1
|
||||
elif self.page(current_id) == self.uiInitrdKernelImageWizardPage:
|
||||
return -1
|
||||
|
||||
@@ -51,6 +51,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self._settings = None
|
||||
self._custom_adapters = []
|
||||
|
||||
self.uiQemuPlatformComboBox.addItems(Qemu.getQemuPlatforms())
|
||||
|
||||
self.uiBootPriorityComboBox.addItem("HDD", "c")
|
||||
self.uiBootPriorityComboBox.addItem("CD/DVD-ROM", "d")
|
||||
self.uiBootPriorityComboBox.addItem("Network", "n")
|
||||
@@ -88,7 +90,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiInitrdToolButton.clicked.connect(self._initrdBrowserSlot)
|
||||
self.uiKernelImageToolButton.clicked.connect(self._kernelImageBrowserSlot)
|
||||
self.uiActivateCPUThrottlingCheckBox.stateChanged.connect(self._cpuThrottlingChangedSlot)
|
||||
self.uiLegacyNetworkingCheckBox.stateChanged.connect(self._legacyNetworkingChangedSlot)
|
||||
self.uiCustomAdaptersConfigurationPushButton.clicked.connect(self._customAdaptersConfigurationSlot)
|
||||
self.uiCreateConfigDiskCheckBox.stateChanged.connect(self._createConfigDiskChangedSlot)
|
||||
|
||||
@@ -103,7 +104,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
# Supported NIC models: e1000, e1000-82544gc, e1000-82545em, e1000e, i82550, i82551, i82557a, i82557b, i82557c, i82558a
|
||||
# i82558b, i82559a, i82559b, i82559c, i82559er, i82562, i82801, ne2k_pci, pcnet, rocker, rtl8139, virtio-net-pci, vmxnet3
|
||||
# This list can be retrieved using "qemu-system-x86_64 -nic model=?" or "qemu-system-x86_64 -device help"
|
||||
self._legacy_devices = ("e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio")
|
||||
self._qemu_network_devices = OrderedDict([("e1000", "Intel Gigabit Ethernet"),
|
||||
("e1000-82544gc", "Intel 82544GC Gigabit Ethernet"),
|
||||
("e1000-82545em", "Intel 82545EM Gigabit Ethernet"),
|
||||
@@ -125,7 +125,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
("pcnet", "AMD PCNet Ethernet"),
|
||||
("rocker", "Rocker L2 switch device"),
|
||||
("rtl8139", "Realtek 8139 Ethernet"),
|
||||
("virtio", "Legacy paravirtualized Network I/O"),
|
||||
("virtio-net-pci", "Paravirtualized Network I/O"),
|
||||
("vmxnet3", "VMWare Paravirtualized Ethernet v3")])
|
||||
|
||||
@@ -144,20 +143,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiSymbolLineEdit.setText(new_symbol_path)
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
|
||||
|
||||
def _refreshQemuNetworkDevices(self, legacy_networking=False):
|
||||
def _refreshQemuNetworkDevices(self,):
|
||||
"""
|
||||
Refreshes the Qemu network device list.
|
||||
|
||||
:param legacy_networking: True if legacy networking is enabled.
|
||||
"""
|
||||
|
||||
self.uiAdapterTypesComboBox.clear()
|
||||
for device_name, device_description in self._qemu_network_devices.items():
|
||||
if legacy_networking and device_name not in self._legacy_devices:
|
||||
continue
|
||||
# special case for virtio legacy networking
|
||||
if not legacy_networking and device_name == "virtio":
|
||||
continue
|
||||
self.uiAdapterTypesComboBox.addItem("{} ({})".format(device_description, device_name), device_name)
|
||||
|
||||
@staticmethod
|
||||
@@ -244,24 +237,64 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiCdromImageLineEdit.setText(path)
|
||||
|
||||
def _hdaDiskImageCreateSlot(self):
|
||||
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hda')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdaDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
|
||||
|
||||
if self._node:
|
||||
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hda')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdaDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
|
||||
|
||||
def _hdbDiskImageCreateSlot(self):
|
||||
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdb')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdbDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
|
||||
|
||||
if self._node:
|
||||
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdb')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdbDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
|
||||
|
||||
def _hdcDiskImageCreateSlot(self):
|
||||
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdc')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdcDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
|
||||
|
||||
if self._node:
|
||||
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdc')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHdcDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
|
||||
|
||||
def _hddDiskImageCreateSlot(self):
|
||||
create_dialog = QemuImageWizard(self, self._compute_id, self.uiNameLineEdit.text() + '-hdd')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHddDiskImageLineEdit.setText(create_dialog.uiLocationLineEdit.text())
|
||||
|
||||
if self._node:
|
||||
create_dialog = QemuImageWizard(self, self._node, self.uiNameLineEdit.text() + '-hdd')
|
||||
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
|
||||
self.uiHddDiskImageLineEdit.setText(create_dialog.uiDiskFilenameLineEdit.text())
|
||||
|
||||
def _hdaDiskImageResizeSlot(self):
|
||||
|
||||
disk_image_filename = self.uiHdaDiskImageLineEdit.text().strip()
|
||||
if disk_image_filename:
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDA disk size", "Increase hda disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hdbDiskImageResizeSlot(self):
|
||||
|
||||
disk_image_filename = self.uiHdbDiskImageLineEdit.text()
|
||||
if disk_image_filename:
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDB disk size", "Increase hdb disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hdcDiskImageResizeSlot(self):
|
||||
|
||||
disk_image_filename = self.uiHdcDiskImageLineEdit.text()
|
||||
if disk_image_filename:
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDC disk size", "Increase hdc disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hddDiskImageResizeSlot(self):
|
||||
|
||||
disk_image_filename = self.uiHddDiskImageLineEdit.text()
|
||||
if disk_image_filename:
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDD disk size", "Increase hdd disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage(disk_image_filename, size, self._resizeDiskImageCallback)
|
||||
|
||||
def _resizeDiskImageCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -276,25 +309,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "Disk image", "The disk has been resized")
|
||||
|
||||
def _hdaDiskImageResizeSlot(self):
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDA disk size", "Increase hda disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage("hda", size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hdbDiskImageResizeSlot(self):
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDB disk size", "Increase hdb disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage("hdb", size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hdcDiskImageResizeSlot(self):
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDC disk size", "Increase hdc disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage("hdc", size, self._resizeDiskImageCallback)
|
||||
|
||||
def _hddDiskImageResizeSlot(self):
|
||||
size, ok = QtWidgets.QInputDialog.getInt(self, "HDD disk size", "Increase hdd disk size in MB:", 10000, 1, 1000000000, 1000)
|
||||
if ok and self._node:
|
||||
self._node.resizeDiskImage("hdd", size, self._resizeDiskImageCallback)
|
||||
|
||||
def _initrdBrowserSlot(self):
|
||||
"""
|
||||
@@ -316,40 +330,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiKernelImageLineEdit.clear()
|
||||
self.uiKernelImageLineEdit.setText(path)
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, qemu_path=None, **kwargs):
|
||||
"""
|
||||
Callback for getQemuBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if sip_is_deleted(self.uiQemuListComboBox) or sip_is_deleted(self):
|
||||
return
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiQemuListComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
|
||||
else:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
|
||||
if qemu_path and "/" not in qemu_path and "\\" not in qemu_path:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu_path), qemu_path)
|
||||
|
||||
index = self.uiQemuListComboBox.findData("{path}".format(path=qemu_path))
|
||||
if index != -1:
|
||||
self.uiQemuListComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
index = self.uiQemuListComboBox.findData("{path}".format(path=os.path.basename(qemu_path)), flags=QtCore.Qt.MatchEndsWith)
|
||||
self.uiQemuListComboBox.setCurrentIndex(index)
|
||||
if index == -1:
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, please select a new binary".format(qemu_path))
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu","Could not find '{}' in the Qemu binaries list, an alternative path has been selected".format(qemu_path))
|
||||
|
||||
def _cpuThrottlingChangedSlot(self, state):
|
||||
"""
|
||||
@@ -361,16 +341,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
else:
|
||||
self.uiCPUThrottlingSpinBox.setEnabled(False)
|
||||
|
||||
def _legacyNetworkingChangedSlot(self, state):
|
||||
"""
|
||||
Slot to enable or not legacy networking.
|
||||
"""
|
||||
|
||||
if state:
|
||||
self._refreshQemuNetworkDevices(legacy_networking=True)
|
||||
else:
|
||||
self._refreshQemuNetworkDevices()
|
||||
|
||||
def _createConfigDiskChangedSlot(self, state):
|
||||
"""
|
||||
Slot to allow or not HDD disk to be configured based on the state of the config disk option.
|
||||
@@ -419,16 +389,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
QtWidgets.QMessageBox.critical(self, "Invalid format", "Invalid port name format")
|
||||
return
|
||||
|
||||
if self.uiLegacyNetworkingCheckBox.isChecked():
|
||||
network_devices = {}
|
||||
for nic, desc in self._qemu_network_devices.items():
|
||||
if nic in self._legacy_devices:
|
||||
network_devices[nic] = desc
|
||||
else:
|
||||
network_devices = self._qemu_network_devices.copy()
|
||||
# special case for virtio legacy networking
|
||||
network_devices.pop("virtio")
|
||||
|
||||
network_devices = self._qemu_network_devices.copy()
|
||||
dialog = CustomAdaptersConfigurationDialog(ports, self._custom_adapters, default_adapter, network_devices, base_mac_address, parent=self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
@@ -450,12 +411,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self._compute_id = settings["compute_id"]
|
||||
self._node = None
|
||||
|
||||
if self._compute_id is None:
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu", "Server {} is not running, cannot retrieve the QEMU binaries list".format(settings["compute_id"]))
|
||||
else:
|
||||
callback = qpartial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
|
||||
Qemu.instance().getQemuBinariesFromServer(self._compute_id, callback)
|
||||
|
||||
if not group:
|
||||
# set the device name
|
||||
self.uiNameLineEdit.setText(settings["name"])
|
||||
@@ -512,6 +467,11 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
|
||||
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
|
||||
|
||||
self.uiHdaDiskImageCreateToolButton.hide()
|
||||
self.uiHdbDiskImageCreateToolButton.hide()
|
||||
self.uiHdcDiskImageCreateToolButton.hide()
|
||||
self.uiHddDiskImageCreateToolButton.hide()
|
||||
|
||||
self.uiHdaDiskImageResizeToolButton.hide()
|
||||
self.uiHdbDiskImageResizeToolButton.hide()
|
||||
self.uiHdcDiskImageResizeToolButton.hide()
|
||||
@@ -532,6 +492,10 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiFirstPortNameLabel.hide()
|
||||
self.uiFirstPortNameLineEdit.hide()
|
||||
|
||||
index = self.uiQemuPlatformComboBox.findText(settings["platform"])
|
||||
if index != -1:
|
||||
self.uiQemuPlatformComboBox.setCurrentIndex(index)
|
||||
|
||||
index = self.uiBootPriorityComboBox.findData(settings["boot_priority"])
|
||||
if index != -1:
|
||||
self.uiBootPriorityComboBox.setCurrentIndex(index)
|
||||
@@ -540,12 +504,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
if index != -1:
|
||||
self.uiConsoleTypeComboBox.setCurrentIndex(index)
|
||||
|
||||
index = self.uiAuxTypeComboBox.findText(settings["aux_type"])
|
||||
if index != -1:
|
||||
self.uiAuxTypeComboBox.setCurrentIndex(index)
|
||||
|
||||
self.uiConsoleAutoStartCheckBox.setChecked(settings["console_auto_start"])
|
||||
self.uiKernelCommandLineEdit.setText(settings["kernel_command_line"])
|
||||
self.uiAdaptersSpinBox.setValue(settings["adapters"])
|
||||
self._custom_adapters = settings["custom_adapters"].copy()
|
||||
|
||||
self.uiLegacyNetworkingCheckBox.setChecked(settings["legacy_networking"])
|
||||
self.uiReplicateNetworkConnectionStateCheckBox.setChecked(settings["replicate_network_connection_state"])
|
||||
|
||||
# load the MAC address setting
|
||||
@@ -655,17 +621,11 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
settings["first_port_name"] = first_port_name
|
||||
|
||||
if self.uiQemuListComboBox.currentIndex() != -1:
|
||||
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
|
||||
settings["qemu_path"] = qemu_path
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binary", "Please select a Qemu binary")
|
||||
if node:
|
||||
raise ConfigurationError()
|
||||
|
||||
settings["platform"] = self.uiQemuPlatformComboBox.itemText(self.uiQemuPlatformComboBox.currentIndex())
|
||||
settings["boot_priority"] = self.uiBootPriorityComboBox.itemData(self.uiBootPriorityComboBox.currentIndex())
|
||||
settings["console_type"] = self.uiConsoleTypeComboBox.currentText().lower()
|
||||
settings["console_auto_start"] = self.uiConsoleAutoStartCheckBox.isChecked()
|
||||
settings["aux_type"] = self.uiAuxTypeComboBox.currentText().lower()
|
||||
settings["adapter_type"] = self.uiAdapterTypesComboBox.itemData(self.uiAdapterTypesComboBox.currentIndex())
|
||||
settings["kernel_command_line"] = self.uiKernelCommandLineEdit.text()
|
||||
|
||||
@@ -679,7 +639,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
raise ConfigurationError()
|
||||
|
||||
settings["adapters"] = adapters
|
||||
settings["legacy_networking"] = self.uiLegacyNetworkingCheckBox.isChecked()
|
||||
settings["replicate_network_connection_state"] = self.uiReplicateNetworkConnectionStateCheckBox.isChecked()
|
||||
settings["custom_adapters"] = self._custom_adapters.copy()
|
||||
settings["on_close"] = self.uiOnCloseComboBox.itemData(self.uiOnCloseComboBox.currentIndex())
|
||||
|
||||
@@ -84,18 +84,21 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
|
||||
if qemu_vm["linked_clone"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", qemu_vm["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(qemu_vm["compute_id"]).name()])
|
||||
compute_id = qemu_vm.get("compute_id")
|
||||
if compute_id:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(compute_id).name()])
|
||||
else:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", "Dynamically allocated by the controller"])
|
||||
except KeyError:
|
||||
pass
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["QEMU platform:", os.path.basename(qemu_vm["platform"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", qemu_vm["console_type"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auto start console:", "{}".format(qemu_vm["console_auto_start"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Auxiliary console type:", qemu_vm["aux_type"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["CPUs:", str(qemu_vm["cpus"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Memory:", "{} MB".format(qemu_vm["ram"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Linked base VM:", "{}".format(qemu_vm["linked_clone"])])
|
||||
|
||||
if qemu_vm["qemu_path"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["QEMU binary:", os.path.basename(qemu_vm["qemu_path"])])
|
||||
|
||||
# fill out the Hard disks section
|
||||
if qemu_vm["hda_disk_image"] or qemu_vm["hdb_disk_image"] or qemu_vm["hdc_disk_image"] or qemu_vm["hdd_disk_image"]:
|
||||
section_item = self._createSectionItem("Hard disks")
|
||||
|
||||
@@ -45,7 +45,6 @@ class QemuVM(Node):
|
||||
self._linked_clone = True
|
||||
|
||||
qemu_vm_settings = {"usage": "",
|
||||
"qemu_path": "",
|
||||
"hda_disk_image": "",
|
||||
"hdb_disk_image": "",
|
||||
"hdc_disk_image": "",
|
||||
@@ -68,11 +67,11 @@ class QemuVM(Node):
|
||||
"cpus": QEMU_VM_SETTINGS["cpus"],
|
||||
"console_type": QEMU_VM_SETTINGS["console_type"],
|
||||
"console_auto_start": QEMU_VM_SETTINGS["console_auto_start"],
|
||||
"aux_type": QEMU_VM_SETTINGS["aux_type"],
|
||||
"adapters": QEMU_VM_SETTINGS["adapters"],
|
||||
"custom_adapters": QEMU_VM_SETTINGS["custom_adapters"],
|
||||
"adapter_type": QEMU_VM_SETTINGS["adapter_type"],
|
||||
"mac_address": QEMU_VM_SETTINGS["mac_address"],
|
||||
"legacy_networking": QEMU_VM_SETTINGS["legacy_networking"],
|
||||
"replicate_network_connection_state": QEMU_VM_SETTINGS["replicate_network_connection_state"],
|
||||
"create_config_disk": QEMU_VM_SETTINGS["create_config_disk"],
|
||||
"platform": QEMU_VM_SETTINGS["platform"],
|
||||
@@ -90,16 +89,24 @@ class QemuVM(Node):
|
||||
|
||||
self.settings().update(qemu_vm_settings)
|
||||
|
||||
def resizeDiskImage(self, drive_name, size, callback):
|
||||
def createDiskImage(self, disk_name, options, callback):
|
||||
"""
|
||||
Resize a disk image allocated to the VM.
|
||||
Create a disk image attached to the VM.
|
||||
|
||||
:param callback: callback for the reply from the server
|
||||
"""
|
||||
|
||||
params = {"drive_name": drive_name,
|
||||
"extend": size}
|
||||
self.post("/resize_disk", callback, body=params)
|
||||
self.post(f"/qemu/disk_image/{disk_name}", callback, body=options)
|
||||
|
||||
def resizeDiskImage(self, disk_name, size, callback):
|
||||
"""
|
||||
Resize a disk image attached to the VM.
|
||||
|
||||
:param callback: callback for the reply from the server
|
||||
"""
|
||||
|
||||
params = {"extend": size}
|
||||
self.put(f"/qemu/disk_image/{disk_name}", callback, body=params)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
@@ -113,6 +120,7 @@ class QemuVM(Node):
|
||||
Local ID is {id} and server ID is {node_id}
|
||||
Number of processors is {cpus} and amount of memory is {ram}MB
|
||||
Console is on port {console} and type is {console_type}
|
||||
Auxiliary console is on port {aux} and type is {aux_type}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
node_id=self._node_id,
|
||||
@@ -122,7 +130,9 @@ class QemuVM(Node):
|
||||
cpus=self._settings["cpus"],
|
||||
ram=self._settings["ram"],
|
||||
console=self._settings["console"],
|
||||
console_type=self._settings["console_type"])
|
||||
console_type=self._settings["console_type"],
|
||||
aux=self._settings["aux"],
|
||||
aux_type=self._settings["aux_type"])
|
||||
|
||||
port_info = ""
|
||||
for port in self._ports:
|
||||
@@ -155,6 +165,15 @@ class QemuVM(Node):
|
||||
|
||||
return None
|
||||
|
||||
def auxConsole(self):
|
||||
"""
|
||||
Returns the console port for this Docker VM instance.
|
||||
|
||||
:returns: port (integer)
|
||||
"""
|
||||
|
||||
return self._settings["aux"]
|
||||
|
||||
def configPage(self):
|
||||
"""
|
||||
Returns the configuration page widget to be used by the node properties dialog.
|
||||
@@ -165,18 +184,6 @@ class QemuVM(Node):
|
||||
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
|
||||
return QemuVMConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return QemuVM.isValidRfc1123Hostname(hostname)
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
@@ -30,13 +30,12 @@ QEMU_VM_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "{name}-{0}",
|
||||
"usage": "",
|
||||
"symbol": ":/symbols/qemu_guest.svg",
|
||||
"symbol": "qemu_guest",
|
||||
"category": Node.end_devices,
|
||||
"port_name_format": "Ethernet{0}",
|
||||
"port_segment_size": 0,
|
||||
"first_port_name": "",
|
||||
"custom_adapters": [],
|
||||
"qemu_path": "",
|
||||
"hda_disk_image": "",
|
||||
"hdb_disk_image": "",
|
||||
"hdc_disk_image": "",
|
||||
@@ -50,16 +49,16 @@ QEMU_VM_SETTINGS = {
|
||||
"boot_priority": "c",
|
||||
"console_type": "telnet",
|
||||
"console_auto_start": False,
|
||||
"aux_type": "none",
|
||||
"ram": 256,
|
||||
"cpus": 1,
|
||||
"adapters": 1,
|
||||
"adapter_type": "e1000",
|
||||
"mac_address": "",
|
||||
"legacy_networking": False,
|
||||
"replicate_network_connection_state": True,
|
||||
"create_config_disk": False,
|
||||
"on_close": "power_off",
|
||||
"platform": "",
|
||||
"platform": "x86_64",
|
||||
"cpu_throttling": 0,
|
||||
"process_priority": "normal",
|
||||
"options": "",
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>535</width>
|
||||
<height>369</height>
|
||||
<width>493</width>
|
||||
<height>314</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Qemu image creator</string>
|
||||
<string>Qemu disk image creator</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
@@ -19,39 +19,22 @@
|
||||
<property name="wizardStyle">
|
||||
<enum>QWizard::ModernStyle</enum>
|
||||
</property>
|
||||
<widget class="QWizardPage" name="uiBinaryWizardPage">
|
||||
<widget class="QWizardPage" name="uiFormatWizardPage">
|
||||
<property name="title">
|
||||
<string>Binary and format</string>
|
||||
<string>Disk image format</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please select a qemu-img binary, and the format for your new image.</string>
|
||||
<string>Please select the disk image format</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiBinaryLabel">
|
||||
<widget class="QLabel" name="uiFormatLabel">
|
||||
<property name="text">
|
||||
<string>Qemu-img binary:</string>
|
||||
<string>Disk image format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="uiBinaryComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiFormatLabel">
|
||||
<property name="text">
|
||||
<string>Image format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiFormatQcow2Radio">
|
||||
@@ -682,31 +665,20 @@ Free space will be zero filled.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiSizeAndLocationWizardPage">
|
||||
<widget class="QWizardPage" name="uiNameAndSizeWizardPage">
|
||||
<property name="title">
|
||||
<string>Size and location</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiLocationLabel">
|
||||
<property name="text">
|
||||
<string>File location:</string>
|
||||
<string>Disk filename:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiLocationLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiLocationBrowseToolButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="uiDiskFilenameLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiSizeLabel">
|
||||
@@ -715,34 +687,30 @@ Free space will be zero filled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="uiSizeSpinBox">
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::UpDownArrows</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>30000</number>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="uiSizeSpinBox">
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::UpDownArrows</enum>
|
||||
</property>
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>30000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -783,11 +751,11 @@ Free space will be zero filled.</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="uiQcow2PreallocationRadios"/>
|
||||
<buttongroup name="uiVmdkAdapterRadios"/>
|
||||
<buttongroup name="uiFormatRadios"/>
|
||||
<buttongroup name="uiVdiSizeModeRadios"/>
|
||||
<buttongroup name="uiVmdkSizeModeRadios"/>
|
||||
<buttongroup name="uiFormatRadios"/>
|
||||
<buttongroup name="uiVhdSizeModeRadios"/>
|
||||
<buttongroup name="uiQcow2PreallocationRadios"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuImageWizard(object):
|
||||
|
||||
def setupUi(self, QemuImageWizard):
|
||||
QemuImageWizard.setObjectName("QemuImageWizard")
|
||||
QemuImageWizard.resize(535, 369)
|
||||
QemuImageWizard.resize(493, 314)
|
||||
QemuImageWizard.setModal(True)
|
||||
QemuImageWizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
self.uiBinaryWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiBinaryWizardPage.setObjectName("uiBinaryWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiBinaryWizardPage)
|
||||
self.uiFormatWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiFormatWizardPage.setObjectName("uiFormatWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiFormatWizardPage)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiBinaryLabel = QtWidgets.QLabel(self.uiBinaryWizardPage)
|
||||
self.uiBinaryLabel.setObjectName("uiBinaryLabel")
|
||||
self.gridLayout.addWidget(self.uiBinaryLabel, 0, 0, 1, 1)
|
||||
self.uiBinaryComboBox = QtWidgets.QComboBox(self.uiBinaryWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiBinaryComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiBinaryComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiBinaryComboBox.setObjectName("uiBinaryComboBox")
|
||||
self.gridLayout.addWidget(self.uiBinaryComboBox, 0, 1, 1, 1)
|
||||
self.uiFormatLabel = QtWidgets.QLabel(self.uiBinaryWizardPage)
|
||||
self.uiFormatLabel = QtWidgets.QLabel(self.uiFormatWizardPage)
|
||||
self.uiFormatLabel.setObjectName("uiFormatLabel")
|
||||
self.gridLayout.addWidget(self.uiFormatLabel, 1, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiFormatLabel, 0, 0, 1, 1)
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.uiFormatQcow2Radio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatQcow2Radio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatQcow2Radio.setText("Qcow2")
|
||||
self.uiFormatQcow2Radio.setChecked(True)
|
||||
self.uiFormatQcow2Radio.setObjectName("uiFormatQcow2Radio")
|
||||
@@ -44,32 +34,32 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiFormatRadios.setObjectName("uiFormatRadios")
|
||||
self.uiFormatRadios.addButton(self.uiFormatQcow2Radio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatQcow2Radio)
|
||||
self.uiFormatQcowRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatQcowRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatQcowRadio.setText("Qcow")
|
||||
self.uiFormatQcowRadio.setObjectName("uiFormatQcowRadio")
|
||||
self.uiFormatRadios.addButton(self.uiFormatQcowRadio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatQcowRadio)
|
||||
self.uiFormatVhdRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatVhdRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatVhdRadio.setText("VHD")
|
||||
self.uiFormatVhdRadio.setObjectName("uiFormatVhdRadio")
|
||||
self.uiFormatRadios.addButton(self.uiFormatVhdRadio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatVhdRadio)
|
||||
self.uiFormatVdiRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatVdiRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatVdiRadio.setText("VDI")
|
||||
self.uiFormatVdiRadio.setObjectName("uiFormatVdiRadio")
|
||||
self.uiFormatRadios.addButton(self.uiFormatVdiRadio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatVdiRadio)
|
||||
self.uiFormatVmdkRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatVmdkRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatVmdkRadio.setText("VMDK")
|
||||
self.uiFormatVmdkRadio.setObjectName("uiFormatVmdkRadio")
|
||||
self.uiFormatRadios.addButton(self.uiFormatVmdkRadio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatVmdkRadio)
|
||||
self.uiFormatRawRadio = QtWidgets.QRadioButton(self.uiBinaryWizardPage)
|
||||
self.uiFormatRawRadio = QtWidgets.QRadioButton(self.uiFormatWizardPage)
|
||||
self.uiFormatRawRadio.setObjectName("uiFormatRawRadio")
|
||||
self.uiFormatRadios.addButton(self.uiFormatRawRadio)
|
||||
self.verticalLayout_3.addWidget(self.uiFormatRawRadio)
|
||||
self.gridLayout.addLayout(self.verticalLayout_3, 1, 1, 1, 1)
|
||||
QemuImageWizard.addPage(self.uiBinaryWizardPage)
|
||||
self.gridLayout.addLayout(self.verticalLayout_3, 0, 1, 1, 1)
|
||||
QemuImageWizard.addPage(self.uiFormatWizardPage)
|
||||
self.uiQcow2OptionsWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiQcow2OptionsWizardPage.setObjectName("uiQcow2OptionsWizardPage")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiQcow2OptionsWizardPage)
|
||||
@@ -271,59 +261,49 @@ class Ui_QemuImageWizard(object):
|
||||
self.horizontalLayout_6.addItem(spacerItem5)
|
||||
self.verticalLayout_6.addWidget(self.uiVmdkMiscGroupBox)
|
||||
QemuImageWizard.addPage(self.uiVmdkOptionsWizardPage)
|
||||
self.uiSizeAndLocationWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiSizeAndLocationWizardPage.setObjectName("uiSizeAndLocationWizardPage")
|
||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiSizeAndLocationWizardPage)
|
||||
self.uiNameAndSizeWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiNameAndSizeWizardPage.setObjectName("uiNameAndSizeWizardPage")
|
||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiNameAndSizeWizardPage)
|
||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||
self.uiLocationLabel = QtWidgets.QLabel(self.uiSizeAndLocationWizardPage)
|
||||
self.uiLocationLabel = QtWidgets.QLabel(self.uiNameAndSizeWizardPage)
|
||||
self.uiLocationLabel.setObjectName("uiLocationLabel")
|
||||
self.gridLayout_4.addWidget(self.uiLocationLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.uiLocationLineEdit = QtWidgets.QLineEdit(self.uiSizeAndLocationWizardPage)
|
||||
self.uiLocationLineEdit.setObjectName("uiLocationLineEdit")
|
||||
self.horizontalLayout.addWidget(self.uiLocationLineEdit)
|
||||
self.uiLocationBrowseToolButton = QtWidgets.QToolButton(self.uiSizeAndLocationWizardPage)
|
||||
self.uiLocationBrowseToolButton.setObjectName("uiLocationBrowseToolButton")
|
||||
self.horizontalLayout.addWidget(self.uiLocationBrowseToolButton)
|
||||
self.gridLayout_4.addLayout(self.horizontalLayout, 0, 1, 1, 1)
|
||||
self.uiSizeLabel = QtWidgets.QLabel(self.uiSizeAndLocationWizardPage)
|
||||
self.gridLayout_4.addWidget(self.uiLocationLabel, 0, 0, 1, 2)
|
||||
self.uiDiskFilenameLineEdit = QtWidgets.QLineEdit(self.uiNameAndSizeWizardPage)
|
||||
self.uiDiskFilenameLineEdit.setObjectName("uiDiskFilenameLineEdit")
|
||||
self.gridLayout_4.addWidget(self.uiDiskFilenameLineEdit, 0, 2, 1, 1)
|
||||
self.uiSizeLabel = QtWidgets.QLabel(self.uiNameAndSizeWizardPage)
|
||||
self.uiSizeLabel.setObjectName("uiSizeLabel")
|
||||
self.gridLayout_4.addWidget(self.uiSizeLabel, 1, 0, 1, 1)
|
||||
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
|
||||
self.uiSizeSpinBox = QtWidgets.QSpinBox(self.uiSizeAndLocationWizardPage)
|
||||
self.uiSizeSpinBox = QtWidgets.QSpinBox(self.uiNameAndSizeWizardPage)
|
||||
self.uiSizeSpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.UpDownArrows)
|
||||
self.uiSizeSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.uiSizeSpinBox.setMinimum(0)
|
||||
self.uiSizeSpinBox.setMaximum(2000000)
|
||||
self.uiSizeSpinBox.setSingleStep(1000)
|
||||
self.uiSizeSpinBox.setProperty("value", 30000)
|
||||
self.uiSizeSpinBox.setProperty("showGroupSeparator", True)
|
||||
self.uiSizeSpinBox.setObjectName("uiSizeSpinBox")
|
||||
self.horizontalLayout_7.addWidget(self.uiSizeSpinBox)
|
||||
self.gridLayout_4.addLayout(self.horizontalLayout_7, 1, 1, 1, 1)
|
||||
QemuImageWizard.addPage(self.uiSizeAndLocationWizardPage)
|
||||
self.gridLayout_4.addWidget(self.uiSizeSpinBox, 1, 2, 1, 1)
|
||||
QemuImageWizard.addPage(self.uiNameAndSizeWizardPage)
|
||||
|
||||
self.retranslateUi(QemuImageWizard)
|
||||
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkFileSizeModeFlatRadio.setDisabled)
|
||||
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkSplit2gCheckBox.setDisabled)
|
||||
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkFileSizeModeFlatRadio.setDisabled) # type: ignore
|
||||
self.uiVmdkStreamOptimizedCheckBox.toggled['bool'].connect(self.uiVmdkSplit2gCheckBox.setDisabled) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(QemuImageWizard)
|
||||
|
||||
def retranslateUi(self, QemuImageWizard):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QemuImageWizard.setWindowTitle(_translate("QemuImageWizard", "Qemu image creator"))
|
||||
self.uiBinaryWizardPage.setTitle(_translate("QemuImageWizard", "Binary and format"))
|
||||
self.uiBinaryWizardPage.setSubTitle(_translate("QemuImageWizard", "Please select a qemu-img binary, and the format for your new image."))
|
||||
self.uiBinaryLabel.setText(_translate("QemuImageWizard", "Qemu-img binary:"))
|
||||
self.uiFormatLabel.setText(_translate("QemuImageWizard", "Image format:"))
|
||||
QemuImageWizard.setWindowTitle(_translate("QemuImageWizard", "Qemu disk image creator"))
|
||||
self.uiFormatWizardPage.setTitle(_translate("QemuImageWizard", "Disk image format"))
|
||||
self.uiFormatWizardPage.setSubTitle(_translate("QemuImageWizard", "Please select the disk image format"))
|
||||
self.uiFormatLabel.setText(_translate("QemuImageWizard", "Disk image format:"))
|
||||
self.uiFormatQcow2Radio.setToolTip(_translate("QemuImageWizard", "Qcow2 is the current Qemu format, supporting many special features."))
|
||||
self.uiFormatQcowRadio.setToolTip(_translate("QemuImageWizard", "Qcow is a legacy Qemu format that is also supported by VirtualBox."))
|
||||
self.uiFormatVhdRadio.setToolTip(_translate("QemuImageWizard", "VHD is the format used by Microsoft VirtualPC, and is also supported by Qemu and VirtualBox.\n"
|
||||
"On Windows 7 and above, it can be mounted on the host PC."))
|
||||
"On Windows 7 and above, it can be mounted on the host PC."))
|
||||
self.uiFormatVdiRadio.setToolTip(_translate("QemuImageWizard", "VDI is the native format of VirtualBox"))
|
||||
self.uiFormatVmdkRadio.setToolTip(_translate("QemuImageWizard", "VMDK is the native format for VMware and is also supported by Qemu and VirtualBox."))
|
||||
self.uiFormatRawRadio.setToolTip(_translate("QemuImageWizard", "Raw image files represent the actual data on the image, with zero special features.\n"
|
||||
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
|
||||
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
|
||||
self.uiFormatRawRadio.setText(_translate("QemuImageWizard", "Raw"))
|
||||
self.uiQcow2OptionsWizardPage.setTitle(_translate("QemuImageWizard", "Qcow2 options"))
|
||||
self.uiSizeOptionsGroupBox.setTitle(_translate("QemuImageWizard", "Size options"))
|
||||
@@ -331,12 +311,12 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiQcow2PreallocationOffRadio.setToolTip(_translate("QemuImageWizard", "The file only takes as much space from the host as needed. The VM will still see the full capacity you specify."))
|
||||
self.uiQcow2PreallocationOffRadio.setText(_translate("QemuImageWizard", "off"))
|
||||
self.uiQcow2PreallocationMetadataRadio.setToolTip(_translate("QemuImageWizard", "Same as \"off\", but preallocates enough space to hold any potenial metadata for the HDD.\n"
|
||||
"This improves performance when the image file needs to grow."))
|
||||
"This improves performance when the image file needs to grow."))
|
||||
self.uiQcow2PreallocationMetadataRadio.setText(_translate("QemuImageWizard", "metadata"))
|
||||
self.uiQcow2PreallocationFallocRadio.setToolTip(_translate("QemuImageWizard", "Same as \"full\", but uses C\'s posix_fallocate() if available on the host, instead of zero filling the file."))
|
||||
self.uiQcow2PreallocationFallocRadio.setText(_translate("QemuImageWizard", "falloc"))
|
||||
self.uiQcow2PreallocationFullRadio.setToolTip(_translate("QemuImageWizard", "The file will start off at the full size you specify.\n"
|
||||
"Free space will be zero filled."))
|
||||
"Free space will be zero filled."))
|
||||
self.uiQcow2PreallocationFullRadio.setText(_translate("QemuImageWizard", "full"))
|
||||
self.uiClusterSizeLabel.setText(_translate("QemuImageWizard", "Cluster size:"))
|
||||
self.uiQcow2ClusterSizeComboBox.setItemText(0, _translate("QemuImageWizard", "<default>"))
|
||||
@@ -391,8 +371,7 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiVmdkStreamOptimizedCheckBox.setText(_translate("QemuImageWizard", "Stream optimized"))
|
||||
self.uiVmdkSplit2gCheckBox.setText(_translate("QemuImageWizard", "Split every 2 GiB"))
|
||||
self.uiVmdkZeroedGrainCheckBox.setText(_translate("QemuImageWizard", "Zeroed grain"))
|
||||
self.uiSizeAndLocationWizardPage.setTitle(_translate("QemuImageWizard", "Size and location"))
|
||||
self.uiLocationLabel.setText(_translate("QemuImageWizard", "File location:"))
|
||||
self.uiLocationBrowseToolButton.setText(_translate("QemuImageWizard", "Browse"))
|
||||
self.uiNameAndSizeWizardPage.setTitle(_translate("QemuImageWizard", "Size and location"))
|
||||
self.uiLocationLabel.setText(_translate("QemuImageWizard", "Disk filename:"))
|
||||
self.uiSizeLabel.setText(_translate("QemuImageWizard", "Disk size:"))
|
||||
self.uiSizeSpinBox.setSuffix(_translate("QemuImageWizard", " MiB"))
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>941</width>
|
||||
<height>939</height>
|
||||
<width>478</width>
|
||||
<height>530</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -119,14 +119,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiQemuListLabel">
|
||||
<widget class="QLabel" name="uiQemuPlatformLabel">
|
||||
<property name="text">
|
||||
<string>Qemu binary:</string>
|
||||
<string>Qemu platform:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="uiQemuListComboBox">
|
||||
<widget class="QComboBox" name="uiQemuPlatformComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -202,7 +202,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -215,6 +215,42 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="uiAuxTypeLabel">
|
||||
<property name="text">
|
||||
<string>Auxiliary console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QComboBox" name="uiAuxTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>telnet</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>vnc</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>spice</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>spice+agent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiHddTab">
|
||||
@@ -222,10 +258,67 @@
|
||||
<string>HDD</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="uiHdcGroupBox">
|
||||
<property name="title">
|
||||
<string>HDC (Disk 2)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiHdcDiskImageLabel">
|
||||
<property name="text">
|
||||
<string>Disk image:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiHdcDiskImageLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageCreateToolButton">
|
||||
<property name="text">
|
||||
<string>Create...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageResizeToolButton">
|
||||
<property name="text">
|
||||
<string>Resize...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiHdcDiskInterfaceLabel">
|
||||
<property name="text">
|
||||
<string>Disk interface:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="uiHdcDiskInterfaceComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="uiHdaGroupBox">
|
||||
<property name="title">
|
||||
<string>HDA (Primary Master)</string>
|
||||
<string>HDA (Disk 0)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
@@ -282,7 +375,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="uiHdbgroupBox">
|
||||
<property name="title">
|
||||
<string>HDB (Primary Slave)</string>
|
||||
<string>HDB (Disk 1)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
@@ -336,84 +429,37 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="uiHdcGroupBox">
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="uiHddGroupBox">
|
||||
<property name="title">
|
||||
<string>HDC (Secondary Master)</string>
|
||||
<string>HDD (Disk 3)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiHdcDiskImageLabel">
|
||||
<property name="text">
|
||||
<string>Disk image:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiHdcDiskImageLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageCreateToolButton">
|
||||
<property name="text">
|
||||
<string>Create...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiHdcDiskImageResizeToolButton">
|
||||
<property name="text">
|
||||
<string>Resize...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="uiHddDiskInterfaceComboBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiHdcDiskInterfaceLabel">
|
||||
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
|
||||
<property name="text">
|
||||
<string>Disk interface:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="uiHdcDiskInterfaceComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="uiHddGroupBox">
|
||||
<property name="title">
|
||||
<string>HDD (Secondary Slave)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiCreateConfigDiskCheckBox">
|
||||
<property name="text">
|
||||
<string>Automatically create a config disk on HDD</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiHddDiskImageLabel">
|
||||
<property name="text">
|
||||
<string>Disk image:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiHddDiskImageLineEdit"/>
|
||||
@@ -444,20 +490,10 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiHddDiskInterfaceLabel">
|
||||
<property name="text">
|
||||
<string>Disk interface:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="uiHddDiskInterfaceComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<spacer name="spacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -542,50 +578,6 @@
|
||||
<string>Network</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiPortSegmentSizeLabel">
|
||||
<property name="text">
|
||||
<string>Segment size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="uiPortSegmentSizeSpinBox">
|
||||
<property name="maximum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiAdaptersLabel">
|
||||
<property name="text">
|
||||
<string>Adapters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiFirstPortNameLabel">
|
||||
<property name="text">
|
||||
<string>First port name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="uiFirstPortNameLineEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="uiPortNameFormatLineEdit">
|
||||
<property name="text">
|
||||
@@ -593,57 +585,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiMacAddrLabel">
|
||||
<property name="text">
|
||||
<string>Base MAC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="uiMacAddrLineEdit"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiAdapterTypesLabel">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiCustomAdaptersLabel">
|
||||
<property name="text">
|
||||
<string>Custom adapters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QPushButton" name="uiCustomAdaptersConfigurationPushButton">
|
||||
<property name="text">
|
||||
<string>&Configure custom adapters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="uiLegacyNetworkingCheckBox">
|
||||
<property name="text">
|
||||
<string>Use the legacy networking mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>261</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="uiAdapterTypesComboBox">
|
||||
<property name="sizePolicy">
|
||||
@@ -654,6 +595,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiAdapterTypesLabel">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="uiAdaptersSpinBox">
|
||||
<property name="sizePolicy">
|
||||
@@ -670,6 +618,36 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiPortSegmentSizeLabel">
|
||||
<property name="text">
|
||||
<string>Segment size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>261</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiFirstPortNameLabel">
|
||||
<property name="text">
|
||||
<string>First port name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="uiFirstPortNameLineEdit"/>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="uiReplicateNetworkConnectionStateCheckBox">
|
||||
<property name="text">
|
||||
@@ -677,6 +655,57 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QPushButton" name="uiCustomAdaptersConfigurationPushButton">
|
||||
<property name="text">
|
||||
<string>&Configure custom adapters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QSpinBox" name="uiPortSegmentSizeSpinBox">
|
||||
<property name="maximum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiCustomAdaptersLabel">
|
||||
<property name="text">
|
||||
<string>Custom adapters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="uiMacAddrLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiAdaptersLabel">
|
||||
<property name="text">
|
||||
<string>Adapters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiMacAddrLabel">
|
||||
<property name="text">
|
||||
<string>Base MAC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiAdvancedSettingsTab">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -13,7 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_QemuVMConfigPageWidget(object):
|
||||
def setupUi(self, QemuVMConfigPageWidget):
|
||||
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
|
||||
QemuVMConfigPageWidget.resize(941, 939)
|
||||
QemuVMConfigPageWidget.resize(478, 530)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
|
||||
@@ -70,17 +70,17 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiCPUSpinBox.setMaximum(255)
|
||||
self.uiCPUSpinBox.setObjectName("uiCPUSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 1, 1, 1)
|
||||
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 1)
|
||||
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiQemuPlatformLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiQemuPlatformLabel.setObjectName("uiQemuPlatformLabel")
|
||||
self.gridLayout_4.addWidget(self.uiQemuPlatformLabel, 6, 0, 1, 1)
|
||||
self.uiQemuPlatformComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 1, 1, 1)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuPlatformComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuPlatformComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuPlatformComboBox.setObjectName("uiQemuPlatformComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiQemuPlatformComboBox, 6, 1, 1, 1)
|
||||
self.uiBootPriorityLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiBootPriorityLabel.setObjectName("uiBootPriorityLabel")
|
||||
self.gridLayout_4.addWidget(self.uiBootPriorityLabel, 7, 0, 1, 1)
|
||||
@@ -111,12 +111,53 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.horizontalLayout_2.addWidget(self.uiConsoleAutoStartCheckBox)
|
||||
self.gridLayout_4.addLayout(self.horizontalLayout_2, 9, 1, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_4.addItem(spacerItem, 10, 1, 1, 1)
|
||||
self.gridLayout_4.addItem(spacerItem, 11, 1, 1, 1)
|
||||
self.uiAuxTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiAuxTypeLabel.setObjectName("uiAuxTypeLabel")
|
||||
self.gridLayout_4.addWidget(self.uiAuxTypeLabel, 10, 0, 1, 1)
|
||||
self.uiAuxTypeComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiAuxTypeComboBox.setObjectName("uiAuxTypeComboBox")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.uiAuxTypeComboBox.addItem("")
|
||||
self.gridLayout_4.addWidget(self.uiAuxTypeComboBox, 10, 1, 1, 1)
|
||||
self.uiQemutabWidget.addTab(self.uiGeneralSettingsTab, "")
|
||||
self.uiHddTab = QtWidgets.QWidget()
|
||||
self.uiHddTab.setObjectName("uiHddTab")
|
||||
self.gridLayout_11 = QtWidgets.QGridLayout(self.uiHddTab)
|
||||
self.gridLayout_11.setObjectName("gridLayout_11")
|
||||
self.uiHdcGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
|
||||
self.uiHdcGroupBox.setObjectName("uiHdcGroupBox")
|
||||
self.gridLayout_8 = QtWidgets.QGridLayout(self.uiHdcGroupBox)
|
||||
self.gridLayout_8.setObjectName("gridLayout_8")
|
||||
self.uiHdcDiskImageLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageLabel.setObjectName("uiHdcDiskImageLabel")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskImageLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
|
||||
self.uiHdcDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageLineEdit.setObjectName("uiHdcDiskImageLineEdit")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageLineEdit)
|
||||
self.uiHdcDiskImageToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiHdcDiskImageToolButton.setObjectName("uiHdcDiskImageToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageToolButton)
|
||||
self.uiHdcDiskImageCreateToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageCreateToolButton.setObjectName("uiHdcDiskImageCreateToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageCreateToolButton)
|
||||
self.uiHdcDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageResizeToolButton.setObjectName("uiHdcDiskImageResizeToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageResizeToolButton)
|
||||
self.gridLayout_8.addLayout(self.horizontalLayout_9, 0, 1, 1, 1)
|
||||
self.uiHdcDiskInterfaceLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskInterfaceLabel.setObjectName("uiHdcDiskInterfaceLabel")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceLabel, 1, 0, 1, 1)
|
||||
self.uiHdcDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskInterfaceComboBox.setObjectName("uiHdcDiskInterfaceComboBox")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceComboBox, 1, 1, 1, 1)
|
||||
self.gridLayout_11.addWidget(self.uiHdcGroupBox, 2, 0, 1, 1)
|
||||
self.uiHdaGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
|
||||
self.uiHdaGroupBox.setObjectName("uiHdaGroupBox")
|
||||
self.gridLayout_6 = QtWidgets.QGridLayout(self.uiHdaGroupBox)
|
||||
@@ -177,46 +218,22 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiHdbDiskInterfaceComboBox.setObjectName("uiHdbDiskInterfaceComboBox")
|
||||
self.gridLayout_7.addWidget(self.uiHdbDiskInterfaceComboBox, 1, 1, 1, 1)
|
||||
self.gridLayout_11.addWidget(self.uiHdbgroupBox, 1, 0, 1, 1)
|
||||
self.uiHdcGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
|
||||
self.uiHdcGroupBox.setObjectName("uiHdcGroupBox")
|
||||
self.gridLayout_8 = QtWidgets.QGridLayout(self.uiHdcGroupBox)
|
||||
self.gridLayout_8.setObjectName("gridLayout_8")
|
||||
self.uiHdcDiskImageLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageLabel.setObjectName("uiHdcDiskImageLabel")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskImageLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
|
||||
self.uiHdcDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageLineEdit.setObjectName("uiHdcDiskImageLineEdit")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageLineEdit)
|
||||
self.uiHdcDiskImageToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiHdcDiskImageToolButton.setObjectName("uiHdcDiskImageToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageToolButton)
|
||||
self.uiHdcDiskImageCreateToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageCreateToolButton.setObjectName("uiHdcDiskImageCreateToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageCreateToolButton)
|
||||
self.uiHdcDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskImageResizeToolButton.setObjectName("uiHdcDiskImageResizeToolButton")
|
||||
self.horizontalLayout_9.addWidget(self.uiHdcDiskImageResizeToolButton)
|
||||
self.gridLayout_8.addLayout(self.horizontalLayout_9, 0, 1, 1, 1)
|
||||
self.uiHdcDiskInterfaceLabel = QtWidgets.QLabel(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskInterfaceLabel.setObjectName("uiHdcDiskInterfaceLabel")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceLabel, 1, 0, 1, 1)
|
||||
self.uiHdcDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHdcGroupBox)
|
||||
self.uiHdcDiskInterfaceComboBox.setObjectName("uiHdcDiskInterfaceComboBox")
|
||||
self.gridLayout_8.addWidget(self.uiHdcDiskInterfaceComboBox, 1, 1, 1, 1)
|
||||
self.gridLayout_11.addWidget(self.uiHdcGroupBox, 2, 0, 1, 1)
|
||||
self.uiHddGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
|
||||
self.uiHddGroupBox.setObjectName("uiHddGroupBox")
|
||||
self.gridLayout_9 = QtWidgets.QGridLayout(self.uiHddGroupBox)
|
||||
self.gridLayout_9.setObjectName("gridLayout_9")
|
||||
self.uiHddDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHddGroupBox)
|
||||
self.uiHddDiskInterfaceComboBox.setObjectName("uiHddDiskInterfaceComboBox")
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceComboBox, 1, 1, 1, 1)
|
||||
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
|
||||
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 1, 0, 1, 1)
|
||||
self.uiCreateConfigDiskCheckBox = QtWidgets.QCheckBox(self.uiHddGroupBox)
|
||||
self.uiCreateConfigDiskCheckBox.setObjectName("uiCreateConfigDiskCheckBox")
|
||||
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 0, 0, 1, 2)
|
||||
self.gridLayout_9.addWidget(self.uiCreateConfigDiskCheckBox, 2, 0, 1, 2)
|
||||
self.uiHddDiskImageLabel = QtWidgets.QLabel(self.uiHddGroupBox)
|
||||
self.uiHddDiskImageLabel.setObjectName("uiHddDiskImageLabel")
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 1, 0, 1, 1)
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskImageLabel, 0, 0, 1, 1)
|
||||
self.horizontalLayout_10 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
|
||||
self.uiHddDiskImageLineEdit = QtWidgets.QLineEdit(self.uiHddGroupBox)
|
||||
@@ -232,16 +249,10 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiHddDiskImageResizeToolButton = QtWidgets.QToolButton(self.uiHddGroupBox)
|
||||
self.uiHddDiskImageResizeToolButton.setObjectName("uiHddDiskImageResizeToolButton")
|
||||
self.horizontalLayout_10.addWidget(self.uiHddDiskImageResizeToolButton)
|
||||
self.gridLayout_9.addLayout(self.horizontalLayout_10, 1, 1, 1, 1)
|
||||
self.uiHddDiskInterfaceLabel = QtWidgets.QLabel(self.uiHddGroupBox)
|
||||
self.uiHddDiskInterfaceLabel.setObjectName("uiHddDiskInterfaceLabel")
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceLabel, 2, 0, 1, 1)
|
||||
self.uiHddDiskInterfaceComboBox = QtWidgets.QComboBox(self.uiHddGroupBox)
|
||||
self.uiHddDiskInterfaceComboBox.setObjectName("uiHddDiskInterfaceComboBox")
|
||||
self.gridLayout_9.addWidget(self.uiHddDiskInterfaceComboBox, 2, 1, 1, 1)
|
||||
self.gridLayout_9.addLayout(self.horizontalLayout_10, 0, 1, 1, 1)
|
||||
self.gridLayout_11.addWidget(self.uiHddGroupBox, 3, 0, 1, 1)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(438, 257, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_11.addItem(spacerItem1, 4, 0, 1, 1)
|
||||
self.gridLayout_11.addItem(spacerItem1, 5, 0, 1, 1)
|
||||
self.uiQemutabWidget.addTab(self.uiHddTab, "")
|
||||
self.uiCdromTab = QtWidgets.QWidget()
|
||||
self.uiCdromTab.setObjectName("uiCdromTab")
|
||||
@@ -273,50 +284,10 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiNetworkTab.setObjectName("uiNetworkTab")
|
||||
self.gridLayout_5 = QtWidgets.QGridLayout(self.uiNetworkTab)
|
||||
self.gridLayout_5.setObjectName("gridLayout_5")
|
||||
self.uiPortSegmentSizeLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiPortSegmentSizeLabel.setObjectName("uiPortSegmentSizeLabel")
|
||||
self.gridLayout_5.addWidget(self.uiPortSegmentSizeLabel, 3, 0, 1, 1)
|
||||
self.uiPortSegmentSizeSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
|
||||
self.uiPortSegmentSizeSpinBox.setMaximum(128)
|
||||
self.uiPortSegmentSizeSpinBox.setSingleStep(4)
|
||||
self.uiPortSegmentSizeSpinBox.setObjectName("uiPortSegmentSizeSpinBox")
|
||||
self.gridLayout_5.addWidget(self.uiPortSegmentSizeSpinBox, 3, 1, 1, 2)
|
||||
self.uiAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiAdaptersLabel.setObjectName("uiAdaptersLabel")
|
||||
self.gridLayout_5.addWidget(self.uiAdaptersLabel, 0, 0, 1, 1)
|
||||
self.uiFirstPortNameLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiFirstPortNameLabel.setObjectName("uiFirstPortNameLabel")
|
||||
self.gridLayout_5.addWidget(self.uiFirstPortNameLabel, 1, 0, 1, 1)
|
||||
self.uiFirstPortNameLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
|
||||
self.uiFirstPortNameLineEdit.setObjectName("uiFirstPortNameLineEdit")
|
||||
self.gridLayout_5.addWidget(self.uiFirstPortNameLineEdit, 1, 1, 1, 2)
|
||||
self.uiPortNameFormatLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiPortNameFormatLabel.setObjectName("uiPortNameFormatLabel")
|
||||
self.gridLayout_5.addWidget(self.uiPortNameFormatLabel, 2, 0, 1, 1)
|
||||
self.uiPortNameFormatLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
|
||||
self.uiPortNameFormatLineEdit.setText("")
|
||||
self.uiPortNameFormatLineEdit.setObjectName("uiPortNameFormatLineEdit")
|
||||
self.gridLayout_5.addWidget(self.uiPortNameFormatLineEdit, 2, 1, 1, 2)
|
||||
self.uiMacAddrLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
|
||||
self.gridLayout_5.addWidget(self.uiMacAddrLabel, 4, 0, 1, 1)
|
||||
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
|
||||
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
|
||||
self.gridLayout_5.addWidget(self.uiMacAddrLineEdit, 4, 1, 1, 2)
|
||||
self.uiAdapterTypesLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiAdapterTypesLabel.setObjectName("uiAdapterTypesLabel")
|
||||
self.gridLayout_5.addWidget(self.uiAdapterTypesLabel, 5, 0, 1, 1)
|
||||
self.uiCustomAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
|
||||
self.gridLayout_5.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
|
||||
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.uiNetworkTab)
|
||||
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
|
||||
self.gridLayout_5.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 2)
|
||||
self.uiLegacyNetworkingCheckBox = QtWidgets.QCheckBox(self.uiNetworkTab)
|
||||
self.uiLegacyNetworkingCheckBox.setObjectName("uiLegacyNetworkingCheckBox")
|
||||
self.gridLayout_5.addWidget(self.uiLegacyNetworkingCheckBox, 8, 0, 1, 3)
|
||||
spacerItem3 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_5.addItem(spacerItem3, 9, 2, 1, 1)
|
||||
self.uiAdapterTypesComboBox = QtWidgets.QComboBox(self.uiNetworkTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -325,6 +296,9 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiAdapterTypesComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiAdapterTypesComboBox.setObjectName("uiAdapterTypesComboBox")
|
||||
self.gridLayout_5.addWidget(self.uiAdapterTypesComboBox, 5, 1, 1, 2)
|
||||
self.uiAdapterTypesLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiAdapterTypesLabel.setObjectName("uiAdapterTypesLabel")
|
||||
self.gridLayout_5.addWidget(self.uiAdapterTypesLabel, 5, 0, 1, 1)
|
||||
self.uiAdaptersSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -335,9 +309,43 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiAdaptersSpinBox.setMaximum(275)
|
||||
self.uiAdaptersSpinBox.setObjectName("uiAdaptersSpinBox")
|
||||
self.gridLayout_5.addWidget(self.uiAdaptersSpinBox, 0, 1, 1, 2)
|
||||
self.uiPortSegmentSizeLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiPortSegmentSizeLabel.setObjectName("uiPortSegmentSizeLabel")
|
||||
self.gridLayout_5.addWidget(self.uiPortSegmentSizeLabel, 3, 0, 1, 1)
|
||||
spacerItem3 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_5.addItem(spacerItem3, 8, 2, 1, 1)
|
||||
self.uiFirstPortNameLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiFirstPortNameLabel.setObjectName("uiFirstPortNameLabel")
|
||||
self.gridLayout_5.addWidget(self.uiFirstPortNameLabel, 1, 0, 1, 1)
|
||||
self.uiFirstPortNameLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
|
||||
self.uiFirstPortNameLineEdit.setObjectName("uiFirstPortNameLineEdit")
|
||||
self.gridLayout_5.addWidget(self.uiFirstPortNameLineEdit, 1, 1, 1, 2)
|
||||
self.uiReplicateNetworkConnectionStateCheckBox = QtWidgets.QCheckBox(self.uiNetworkTab)
|
||||
self.uiReplicateNetworkConnectionStateCheckBox.setObjectName("uiReplicateNetworkConnectionStateCheckBox")
|
||||
self.gridLayout_5.addWidget(self.uiReplicateNetworkConnectionStateCheckBox, 7, 0, 1, 3)
|
||||
self.uiCustomAdaptersConfigurationPushButton = QtWidgets.QPushButton(self.uiNetworkTab)
|
||||
self.uiCustomAdaptersConfigurationPushButton.setObjectName("uiCustomAdaptersConfigurationPushButton")
|
||||
self.gridLayout_5.addWidget(self.uiCustomAdaptersConfigurationPushButton, 6, 1, 1, 2)
|
||||
self.uiPortSegmentSizeSpinBox = QtWidgets.QSpinBox(self.uiNetworkTab)
|
||||
self.uiPortSegmentSizeSpinBox.setMaximum(128)
|
||||
self.uiPortSegmentSizeSpinBox.setSingleStep(4)
|
||||
self.uiPortSegmentSizeSpinBox.setObjectName("uiPortSegmentSizeSpinBox")
|
||||
self.gridLayout_5.addWidget(self.uiPortSegmentSizeSpinBox, 3, 1, 1, 2)
|
||||
self.uiCustomAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiCustomAdaptersLabel.setObjectName("uiCustomAdaptersLabel")
|
||||
self.gridLayout_5.addWidget(self.uiCustomAdaptersLabel, 6, 0, 1, 1)
|
||||
self.uiMacAddrLineEdit = QtWidgets.QLineEdit(self.uiNetworkTab)
|
||||
self.uiMacAddrLineEdit.setObjectName("uiMacAddrLineEdit")
|
||||
self.gridLayout_5.addWidget(self.uiMacAddrLineEdit, 4, 1, 1, 2)
|
||||
self.uiAdaptersLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiAdaptersLabel.setObjectName("uiAdaptersLabel")
|
||||
self.gridLayout_5.addWidget(self.uiAdaptersLabel, 0, 0, 1, 1)
|
||||
self.uiPortNameFormatLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiPortNameFormatLabel.setObjectName("uiPortNameFormatLabel")
|
||||
self.gridLayout_5.addWidget(self.uiPortNameFormatLabel, 2, 0, 1, 1)
|
||||
self.uiMacAddrLabel = QtWidgets.QLabel(self.uiNetworkTab)
|
||||
self.uiMacAddrLabel.setObjectName("uiMacAddrLabel")
|
||||
self.gridLayout_5.addWidget(self.uiMacAddrLabel, 4, 0, 1, 1)
|
||||
self.uiQemutabWidget.addTab(self.uiNetworkTab, "")
|
||||
self.uiAdvancedSettingsTab = QtWidgets.QWidget()
|
||||
self.uiAdvancedSettingsTab.setObjectName("uiAdvancedSettingsTab")
|
||||
@@ -470,7 +478,7 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiRamLabel.setText(_translate("QemuVMConfigPageWidget", "RAM:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
|
||||
self.uiCPULabel.setText(_translate("QemuVMConfigPageWidget", "vCPUs:"))
|
||||
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
|
||||
self.uiQemuPlatformLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu platform:"))
|
||||
self.uiBootPriorityLabel.setText(_translate("QemuVMConfigPageWidget", "Boot priority:"))
|
||||
self.uiOnCloseLabel.setText(_translate("QemuVMConfigPageWidget", "On close:"))
|
||||
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
|
||||
@@ -480,48 +488,53 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiConsoleTypeComboBox.setItemText(3, _translate("QemuVMConfigPageWidget", "spice+agent"))
|
||||
self.uiConsoleTypeComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "none"))
|
||||
self.uiConsoleAutoStartCheckBox.setText(_translate("QemuVMConfigPageWidget", "Auto start console"))
|
||||
self.uiAuxTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Auxiliary console type:"))
|
||||
self.uiAuxTypeComboBox.setItemText(0, _translate("QemuVMConfigPageWidget", "telnet"))
|
||||
self.uiAuxTypeComboBox.setItemText(1, _translate("QemuVMConfigPageWidget", "vnc"))
|
||||
self.uiAuxTypeComboBox.setItemText(2, _translate("QemuVMConfigPageWidget", "spice"))
|
||||
self.uiAuxTypeComboBox.setItemText(3, _translate("QemuVMConfigPageWidget", "spice+agent"))
|
||||
self.uiAuxTypeComboBox.setItemText(4, _translate("QemuVMConfigPageWidget", "none"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiGeneralSettingsTab), _translate("QemuVMConfigPageWidget", "General settings"))
|
||||
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Primary Master)"))
|
||||
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHdaDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHdaDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHdaDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHdaDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiHdbgroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDB (Primary Slave)"))
|
||||
self.uiHdbDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHdbDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHdbDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHdbDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHdbDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiHdcGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDC (Secondary Master)"))
|
||||
self.uiHdcGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDC (Disk 2)"))
|
||||
self.uiHdcDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHdcDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHdcDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHdcDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHdcDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Secondary Slave)"))
|
||||
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Disk 0)"))
|
||||
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHdaDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHdaDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHdaDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHdaDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiHdbgroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDB (Disk 1)"))
|
||||
self.uiHdbDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHdbDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHdbDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHdbDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHdbDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiHddGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDD (Disk 3)"))
|
||||
self.uiHddDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiCreateConfigDiskCheckBox.setText(_translate("QemuVMConfigPageWidget", "Automatically create a config disk on HDD"))
|
||||
self.uiHddDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
self.uiHddDiskImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiHddDiskImageCreateToolButton.setText(_translate("QemuVMConfigPageWidget", "Create..."))
|
||||
self.uiHddDiskImageResizeToolButton.setText(_translate("QemuVMConfigPageWidget", "Resize..."))
|
||||
self.uiHddDiskInterfaceLabel.setText(_translate("QemuVMConfigPageWidget", "Disk interface:"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiHddTab), _translate("QemuVMConfigPageWidget", "HDD"))
|
||||
self.uiCdromGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "CD/DVD-ROM"))
|
||||
self.uiCdromImageLabel.setText(_translate("QemuVMConfigPageWidget", "Image:"))
|
||||
self.uiCdromImageToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiCdromTab), _translate("QemuVMConfigPageWidget", "CD/DVD"))
|
||||
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
|
||||
self.uiPortSegmentSizeLabel.setText(_translate("QemuVMConfigPageWidget", "Segment size:"))
|
||||
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
|
||||
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
|
||||
self.uiReplicateNetworkConnectionStateCheckBox.setText(_translate("QemuVMConfigPageWidget", "Replicate network connection states in Qemu"))
|
||||
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("QemuVMConfigPageWidget", "&Configure custom adapters"))
|
||||
self.uiCustomAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Custom adapters:"))
|
||||
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
|
||||
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
|
||||
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
|
||||
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
|
||||
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
|
||||
self.uiCustomAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Custom adapters:"))
|
||||
self.uiCustomAdaptersConfigurationPushButton.setText(_translate("QemuVMConfigPageWidget", "&Configure custom adapters"))
|
||||
self.uiLegacyNetworkingCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use the legacy networking mode"))
|
||||
self.uiReplicateNetworkConnectionStateCheckBox.setText(_translate("QemuVMConfigPageWidget", "Replicate network connection states in Qemu"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
|
||||
self.uiLinuxBootGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "Linux boot specific settings"))
|
||||
self.uiKernelCommandLineLabel.setText(_translate("QemuVMConfigPageWidget", "Kernel command line:"))
|
||||
|
||||
@@ -106,32 +106,25 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiLegacyASACheckBox">
|
||||
<property name="text">
|
||||
<string>This is a legacy ASA VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiBinaryMemoryWizardPage">
|
||||
<widget class="QWizardPage" name="uiPlatformMemoryWizardPage">
|
||||
<property name="title">
|
||||
<string>QEMU binary and memory</string>
|
||||
<string>QEMU platform and memory</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please check the Qemu binary is correctly set and the virtual machine has enough memory to work.</string>
|
||||
<string>Please select the platform and check the virtual machine has enough memory to work.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiQemuListLabel">
|
||||
<widget class="QLabel" name="uiQemuPlatformLabel">
|
||||
<property name="text">
|
||||
<string>Qemu binary:</string>
|
||||
<string>Platform:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="uiQemuListComboBox">
|
||||
<widget class="QComboBox" name="uiQemuPlatformComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -423,7 +416,7 @@
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiNameLineEdit</tabstop>
|
||||
<tabstop>uiQemuListComboBox</tabstop>
|
||||
<tabstop>uiQemuPlatformComboBox</tabstop>
|
||||
<tabstop>uiRamSpinBox</tabstop>
|
||||
<tabstop>uiHdaDiskImageLineEdit</tabstop>
|
||||
<tabstop>uiHdaDiskImageToolButton</tabstop>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
# Created by: PyQt5 UI code generator 5.14.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuVMWizard(object):
|
||||
def setupUi(self, QemuVMWizard):
|
||||
QemuVMWizard.setObjectName("QemuVMWizard")
|
||||
@@ -60,35 +62,32 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiLegacyASACheckBox = QtWidgets.QCheckBox(self.uiNameWizardPage)
|
||||
self.uiLegacyASACheckBox.setObjectName("uiLegacyASACheckBox")
|
||||
self.gridLayout.addWidget(self.uiLegacyASACheckBox, 1, 0, 1, 2)
|
||||
QemuVMWizard.addPage(self.uiNameWizardPage)
|
||||
self.uiBinaryMemoryWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiBinaryMemoryWizardPage.setObjectName("uiBinaryMemoryWizardPage")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiBinaryMemoryWizardPage)
|
||||
self.uiPlatformMemoryWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiPlatformMemoryWizardPage.setObjectName("uiPlatformMemoryWizardPage")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiPlatformMemoryWizardPage)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.uiQemuListLabel = QtWidgets.QLabel(self.uiBinaryMemoryWizardPage)
|
||||
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
|
||||
self.gridLayout_2.addWidget(self.uiQemuListLabel, 0, 0, 1, 1)
|
||||
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiBinaryMemoryWizardPage)
|
||||
self.uiQemuPlatformLabel = QtWidgets.QLabel(self.uiPlatformMemoryWizardPage)
|
||||
self.uiQemuPlatformLabel.setObjectName("uiQemuPlatformLabel")
|
||||
self.gridLayout_2.addWidget(self.uiQemuPlatformLabel, 0, 0, 1, 1)
|
||||
self.uiQemuPlatformComboBox = QtWidgets.QComboBox(self.uiPlatformMemoryWizardPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiQemuListComboBox, 0, 1, 1, 1)
|
||||
self.uiRamLabel = QtWidgets.QLabel(self.uiBinaryMemoryWizardPage)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuPlatformComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuPlatformComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuPlatformComboBox.setObjectName("uiQemuPlatformComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiQemuPlatformComboBox, 0, 1, 1, 1)
|
||||
self.uiRamLabel = QtWidgets.QLabel(self.uiPlatformMemoryWizardPage)
|
||||
self.uiRamLabel.setObjectName("uiRamLabel")
|
||||
self.gridLayout_2.addWidget(self.uiRamLabel, 1, 0, 1, 1)
|
||||
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiBinaryMemoryWizardPage)
|
||||
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiPlatformMemoryWizardPage)
|
||||
self.uiRamSpinBox.setMinimum(32)
|
||||
self.uiRamSpinBox.setMaximum(65535)
|
||||
self.uiRamSpinBox.setProperty("value", 256)
|
||||
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
|
||||
self.gridLayout_2.addWidget(self.uiRamSpinBox, 1, 1, 1, 1)
|
||||
QemuVMWizard.addPage(self.uiBinaryMemoryWizardPage)
|
||||
QemuVMWizard.addPage(self.uiPlatformMemoryWizardPage)
|
||||
self.uiConsoleTypeWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiConsoleTypeWizardPage.setObjectName("uiConsoleTypeWizardPage")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiConsoleTypeWizardPage)
|
||||
@@ -206,8 +205,8 @@ class Ui_QemuVMWizard(object):
|
||||
|
||||
self.retranslateUi(QemuVMWizard)
|
||||
QtCore.QMetaObject.connectSlotsByName(QemuVMWizard)
|
||||
QemuVMWizard.setTabOrder(self.uiNameLineEdit, self.uiQemuListComboBox)
|
||||
QemuVMWizard.setTabOrder(self.uiQemuListComboBox, self.uiRamSpinBox)
|
||||
QemuVMWizard.setTabOrder(self.uiNameLineEdit, self.uiQemuPlatformComboBox)
|
||||
QemuVMWizard.setTabOrder(self.uiQemuPlatformComboBox, self.uiRamSpinBox)
|
||||
QemuVMWizard.setTabOrder(self.uiRamSpinBox, self.uiHdaDiskImageLineEdit)
|
||||
QemuVMWizard.setTabOrder(self.uiHdaDiskImageLineEdit, self.uiHdaDiskImageToolButton)
|
||||
|
||||
@@ -225,10 +224,9 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiNameWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM name"))
|
||||
self.uiNameWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a descriptive name for your new QEMU virtual machine."))
|
||||
self.uiNameLabel.setText(_translate("QemuVMWizard", "Name:"))
|
||||
self.uiLegacyASACheckBox.setText(_translate("QemuVMWizard", "This is a legacy ASA VM"))
|
||||
self.uiBinaryMemoryWizardPage.setTitle(_translate("QemuVMWizard", "QEMU binary and memory"))
|
||||
self.uiBinaryMemoryWizardPage.setSubTitle(_translate("QemuVMWizard", "Please check the Qemu binary is correctly set and the virtual machine has enough memory to work."))
|
||||
self.uiQemuListLabel.setText(_translate("QemuVMWizard", "Qemu binary:"))
|
||||
self.uiPlatformMemoryWizardPage.setTitle(_translate("QemuVMWizard", "QEMU platform and memory"))
|
||||
self.uiPlatformMemoryWizardPage.setSubTitle(_translate("QemuVMWizard", "Please select the platform and check the virtual machine has enough memory to work."))
|
||||
self.uiQemuPlatformLabel.setText(_translate("QemuVMWizard", "Platform:"))
|
||||
self.uiRamLabel.setText(_translate("QemuVMWizard", "RAM:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("QemuVMWizard", " MB"))
|
||||
self.uiConsoleTypeWizardPage.setTitle(_translate("QemuVMWizard", "Console type"))
|
||||
@@ -254,4 +252,3 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiKernelImageLabel.setText(_translate("QemuVMWizard", "Kernel image (vmlinuz):"))
|
||||
self.uiKernelImageToolButton.setText(_translate("QemuVMWizard", "&Browse..."))
|
||||
self.uiInitrdLabel.setText(_translate("QemuVMWizard", "Initial RAM disk (initrd):"))
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
TraceNG module implementation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
|
||||
from ..module import Module
|
||||
from .traceng_node import TraceNGNode
|
||||
from .settings import TRACENG_SETTINGS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TraceNG(Module):
|
||||
"""
|
||||
TraceNG module.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._working_dir = ""
|
||||
self._loadSettings()
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
Loads the settings from the persistent settings file.
|
||||
"""
|
||||
|
||||
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, TRACENG_SETTINGS)
|
||||
if not os.path.exists(self._settings["traceng_path"]):
|
||||
traceng_path = shutil.which("traceng")
|
||||
if traceng_path:
|
||||
self._settings["traceng_path"] = os.path.abspath(traceng_path)
|
||||
else:
|
||||
self._settings["traceng_path"] = ""
|
||||
|
||||
def _saveSettings(self):
|
||||
"""
|
||||
Saves the settings to the persistent settings file.
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
server_settings = {}
|
||||
if self._settings["traceng_path"]:
|
||||
# save some settings to the server config file
|
||||
server_settings["traceng_path"] = os.path.normpath(self._settings["traceng_path"])
|
||||
|
||||
config = LocalServerConfig.instance()
|
||||
config.saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
@staticmethod
|
||||
def getNodeClass(node_type, platform=None):
|
||||
"""
|
||||
Returns the class corresponding to node type.
|
||||
|
||||
:param node_type: node type (string)
|
||||
:param platform: not used
|
||||
|
||||
:returns: class or None
|
||||
"""
|
||||
|
||||
if node_type == "traceng":
|
||||
return TraceNGNode
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def configurationPage():
|
||||
"""
|
||||
Returns the configuration page for this module.
|
||||
|
||||
:returns: QWidget object
|
||||
"""
|
||||
|
||||
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
|
||||
return TraceNGNodeConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def classes():
|
||||
"""
|
||||
Returns all the node classes supported by this module.
|
||||
|
||||
:returns: list of classes
|
||||
"""
|
||||
|
||||
return [TraceNGNode]
|
||||
|
||||
@staticmethod
|
||||
def preferencePages():
|
||||
"""
|
||||
Returns the preference pages for this module.
|
||||
|
||||
:returns: QWidget object list
|
||||
"""
|
||||
|
||||
from .pages.traceng_preferences_page import TraceNGPreferencesPage
|
||||
from .pages.traceng_node_preferences_page import TraceNGNodePreferencesPage
|
||||
return [TraceNGPreferencesPage, TraceNGNodePreferencesPage]
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of TraceNG module.
|
||||
|
||||
:returns: instance of TraceNG
|
||||
"""
|
||||
|
||||
if not hasattr(TraceNG, "_instance"):
|
||||
TraceNG._instance = TraceNG()
|
||||
return TraceNG._instance
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns the module name.
|
||||
"""
|
||||
|
||||
return "traceng"
|
||||
@@ -1,85 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Wizard for TraceNG nodes.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ipaddress
|
||||
|
||||
from gns3.qt import QtGui, QtWidgets
|
||||
from gns3.node import Node
|
||||
from gns3.dialogs.vm_wizard import VMWizard
|
||||
|
||||
from ..ui.traceng_node_wizard_ui import Ui_TraceNGNodeWizard
|
||||
|
||||
|
||||
class TraceNGNodeWizard(VMWizard, Ui_TraceNGNodeWizard):
|
||||
"""
|
||||
Wizard to create a TraceNG node.
|
||||
|
||||
:param parent: parent widget
|
||||
"""
|
||||
|
||||
def __init__(self, traceng_nodes, parent):
|
||||
|
||||
super().__init__(traceng_nodes, parent)
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/traceng.png"))
|
||||
self.uiNameWizardPage.registerField("name*", self.uiNameLineEdit)
|
||||
|
||||
# TraceNG is only supported on a local server
|
||||
self.uiRemoteRadioButton.setEnabled(False)
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Validates the server.
|
||||
"""
|
||||
|
||||
if super().validateCurrentPage() is False:
|
||||
return False
|
||||
|
||||
if self.currentPage() == self.uiNameWizardPage:
|
||||
|
||||
if not sys.platform.startswith("win"):
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG", "TraceNG can only run on Windows with a local server")
|
||||
return False
|
||||
|
||||
ip_address = self.uiIPAddressLineEdit.text()
|
||||
if ip_address:
|
||||
try:
|
||||
ipaddress.IPv4Address(ip_address)
|
||||
except ipaddress.AddressValueError:
|
||||
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
|
||||
return False
|
||||
return True
|
||||
|
||||
def getSettings(self):
|
||||
"""
|
||||
Returns the settings set in this Wizard.
|
||||
|
||||
:return: settings dict
|
||||
"""
|
||||
|
||||
settings = {"name": self.uiNameLineEdit.text(),
|
||||
"ip_address": self.uiIPAddressLineEdit.text(),
|
||||
"symbol": ":/symbols/traceng.svg",
|
||||
"category": Node.end_devices,
|
||||
"compute_id": self._compute_id}
|
||||
|
||||
return settings
|
||||
@@ -1,155 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Configuration page for TraceNG nodes
|
||||
"""
|
||||
|
||||
import ipaddress
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_server import LocalServer
|
||||
from gns3.node import Node
|
||||
from gns3.controller import Controller
|
||||
|
||||
from ..ui.traceng_node_configuration_page_ui import Ui_TraceNGNodeConfigPageWidget
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.dialogs.node_properties_dialog import ConfigurationError
|
||||
|
||||
|
||||
class TraceNGNodeConfigurationPage(QtWidgets.QWidget, Ui_TraceNGNodeConfigPageWidget):
|
||||
|
||||
"""
|
||||
QWidget configuration page for TraceNG nodes.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
|
||||
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]
|
||||
if Controller.instance().isRemote():
|
||||
self.uiScriptFileToolButton.hide()
|
||||
|
||||
# add the categories
|
||||
for name, category in Node.defaultCategories().items():
|
||||
self.uiCategoryComboBox.addItem(name, category)
|
||||
|
||||
def _symbolBrowserSlot(self):
|
||||
"""
|
||||
Slot to open the symbol browser and select a new symbol.
|
||||
"""
|
||||
|
||||
symbol_path = self.uiSymbolLineEdit.text()
|
||||
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
new_symbol_path = dialog.getSymbol()
|
||||
self.uiSymbolLineEdit.setText(new_symbol_path)
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
|
||||
|
||||
def loadSettings(self, settings, node=None, group=False):
|
||||
"""
|
||||
Loads the TraceNG node settings.
|
||||
|
||||
:param settings: the settings (dictionary)
|
||||
:param node: Node instance
|
||||
:param group: indicates the settings apply to a group of routers
|
||||
"""
|
||||
|
||||
if not group:
|
||||
self.uiNameLineEdit.setText(settings["name"])
|
||||
self.uiIPAddressLineEdit.setText(settings["ip_address"])
|
||||
self.uiDefaultDestinationLineEdit.setText(settings["default_destination"])
|
||||
else:
|
||||
self.uiIPAddressLabel.hide()
|
||||
self.uiIPAddressLineEdit.hide()
|
||||
self.uiDefaultDestinationLabel.hide()
|
||||
self.uiDefaultDestinationLineEdit.hide()
|
||||
self.uiNameLabel.hide()
|
||||
self.uiNameLineEdit.hide()
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
self.uiNameLabel.setText("Template name:")
|
||||
|
||||
# load the default name format
|
||||
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
|
||||
|
||||
# load the symbol
|
||||
self.uiSymbolLineEdit.setText(settings["symbol"])
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
|
||||
|
||||
# load the category
|
||||
index = self.uiCategoryComboBox.findData(settings["category"])
|
||||
if index != -1:
|
||||
self.uiCategoryComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
self.uiDefaultNameFormatLineEdit.hide()
|
||||
self.uiSymbolLabel.hide()
|
||||
self.uiSymbolLineEdit.hide()
|
||||
self.uiSymbolToolButton.hide()
|
||||
self.uiCategoryComboBox.hide()
|
||||
self.uiCategoryLabel.hide()
|
||||
self.uiCategoryComboBox.hide()
|
||||
|
||||
def saveSettings(self, settings, node=None, group=False):
|
||||
"""
|
||||
Saves the TraceNG node settings.
|
||||
|
||||
:param settings: the settings (dictionary)
|
||||
:param node: Node instance
|
||||
:param group: indicates the settings apply to a group of routers
|
||||
"""
|
||||
|
||||
# these settings cannot be shared by nodes and updated
|
||||
# in the node properties dialog.
|
||||
if not group:
|
||||
# set the node name
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtWidgets.QMessageBox.critical(self, "Name", "TraceNG node name cannot be empty!")
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
ip_address = self.uiIPAddressLineEdit.text().strip()
|
||||
if ip_address:
|
||||
try:
|
||||
ipaddress.IPv4Address(ip_address)
|
||||
settings["ip_address"] = ip_address
|
||||
except ipaddress.AddressValueError:
|
||||
QtWidgets.QMessageBox.critical(self, "IP address", "Invalid IP address format")
|
||||
if node:
|
||||
raise ConfigurationError()
|
||||
|
||||
settings["default_destination"] = self.uiDefaultDestinationLineEdit.text().strip()
|
||||
|
||||
if not node:
|
||||
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
|
||||
if '{0}' not in default_name_format and '{id}' not in default_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
|
||||
else:
|
||||
settings["default_name_format"] = default_name_format
|
||||
|
||||
symbol_path = self.uiSymbolLineEdit.text()
|
||||
settings["symbol"] = symbol_path
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
return settings
|
||||
@@ -1,213 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Configuration page for TraceNG node preferences.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets, qpartial
|
||||
|
||||
from gns3.main_window import MainWindow
|
||||
from gns3.dialogs.configuration_dialog import ConfigurationDialog
|
||||
from gns3.compute_manager import ComputeManager
|
||||
from gns3.template_manager import TemplateManager
|
||||
from gns3.controller import Controller
|
||||
from gns3.template import Template
|
||||
|
||||
from ..settings import TRACENG_NODES_SETTINGS
|
||||
from ..ui.traceng_node_preferences_page_ui import Ui_TraceNGNodePageWidget
|
||||
from ..pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
|
||||
from ..dialogs.traceng_node_wizard import TraceNGNodeWizard
|
||||
|
||||
|
||||
class TraceNGNodePreferencesPage(QtWidgets.QWidget, Ui_TraceNGNodePageWidget):
|
||||
"""
|
||||
QWidget preference page for TraceNG node preferences.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self._main_window = MainWindow.instance()
|
||||
self._traceng_nodes = {}
|
||||
self._items = []
|
||||
|
||||
self.uiNewTraceNGPushButton.clicked.connect(self._newTraceNGSlot)
|
||||
self.uiEditTraceNGPushButton.clicked.connect(self._editTraceNGSlot)
|
||||
self.uiDeleteTraceNGPushButton.clicked.connect(self._deleteTraceNGSlot)
|
||||
self.uiTraceNGTreeWidget.itemSelectionChanged.connect(self._tracengChangedSlot)
|
||||
|
||||
def _createSectionItem(self, name):
|
||||
"""
|
||||
Adds a new section to the tree widget.
|
||||
|
||||
:param name: section name
|
||||
"""
|
||||
|
||||
section_item = QtWidgets.QTreeWidgetItem(self.uiTraceNGInfoTreeWidget)
|
||||
section_item.setText(0, name)
|
||||
font = section_item.font(0)
|
||||
font.setBold(True)
|
||||
section_item.setFont(0, font)
|
||||
return section_item
|
||||
|
||||
def _refreshInfo(self, traceng_node):
|
||||
"""
|
||||
Refreshes the content of the tree widget.
|
||||
"""
|
||||
|
||||
self.uiTraceNGInfoTreeWidget.clear()
|
||||
|
||||
# fill out the General section
|
||||
section_item = self._createSectionItem("General")
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", traceng_node["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template ID:", traceng_node.get("template_id", "none")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["IP address:", traceng_node["ip_address"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", traceng_node["default_name_format"]])
|
||||
try:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ComputeManager.instance().getCompute(traceng_node["compute_id"]).name()])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.uiTraceNGInfoTreeWidget.expandAll()
|
||||
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(0)
|
||||
self.uiTraceNGInfoTreeWidget.resizeColumnToContents(1)
|
||||
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
|
||||
|
||||
def _tracengChangedSlot(self):
|
||||
"""
|
||||
Loads a selected TraceNG node from the tree widget.
|
||||
"""
|
||||
|
||||
selection = self.uiTraceNGTreeWidget.selectedItems()
|
||||
self.uiDeleteTraceNGPushButton.setEnabled(len(selection) != 0)
|
||||
single_selected = len(selection) == 1
|
||||
self.uiEditTraceNGPushButton.setEnabled(single_selected)
|
||||
|
||||
if single_selected:
|
||||
key = selection[0].data(0, QtCore.Qt.UserRole)
|
||||
traceng_node = self._traceng_nodes[key]
|
||||
self._refreshInfo(traceng_node)
|
||||
else:
|
||||
self.uiTraceNGInfoTreeWidget.clear()
|
||||
|
||||
def _newTraceNGSlot(self):
|
||||
"""
|
||||
Creates a new TraceNG node.
|
||||
"""
|
||||
|
||||
wizard = TraceNGNodeWizard(self._traceng_nodes, parent=self)
|
||||
wizard.show()
|
||||
if wizard.exec_():
|
||||
new_traceng_node_settings = wizard.getSettings()
|
||||
key = "{server}:{name}".format(server=new_traceng_node_settings["compute_id"], name=new_traceng_node_settings["name"])
|
||||
self._traceng_nodes[key] = TRACENG_NODES_SETTINGS.copy()
|
||||
self._traceng_nodes[key].update(new_traceng_node_settings)
|
||||
|
||||
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
|
||||
item.setText(0, self._traceng_nodes[key]["name"])
|
||||
Controller.instance().getSymbolIcon(self._traceng_nodes[key]["symbol"], qpartial(self._setItemIcon, item))
|
||||
item.setData(0, QtCore.Qt.UserRole, key)
|
||||
self._items.append(item)
|
||||
self.uiTraceNGTreeWidget.setCurrentItem(item)
|
||||
|
||||
def _editTraceNGSlot(self):
|
||||
"""
|
||||
Edits a TraceNG node.
|
||||
"""
|
||||
|
||||
item = self.uiTraceNGTreeWidget.currentItem()
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
traceng_node = self._traceng_nodes[key]
|
||||
dialog = ConfigurationDialog(traceng_node["name"], traceng_node, TraceNGNodeConfigurationPage(), parent=self)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
# update the icon
|
||||
Controller.instance().getSymbolIcon(traceng_node["symbol"], qpartial(self._setItemIcon, item))
|
||||
if traceng_node["name"] != item.text(0):
|
||||
new_key = "{server}:{name}".format(server=traceng_node["compute_id"], name=traceng_node["name"])
|
||||
if new_key in self._traceng_nodes:
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG node", "TraceNG node name {} already exists for server {}".format(traceng_node["name"],
|
||||
traceng_node["compute_id"]))
|
||||
traceng_node["name"] = item.text(0)
|
||||
return
|
||||
self._traceng_nodes[new_key] = self._traceng_nodes[key]
|
||||
del self._traceng_nodes[key]
|
||||
item.setText(0, traceng_node["name"])
|
||||
item.setData(0, QtCore.Qt.UserRole, new_key)
|
||||
self._refreshInfo(traceng_node)
|
||||
|
||||
def _deleteTraceNGSlot(self):
|
||||
"""
|
||||
Deletes a TraceNG node.
|
||||
"""
|
||||
|
||||
for item in self.uiTraceNGTreeWidget.selectedItems():
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
del self._traceng_nodes[key]
|
||||
self.uiTraceNGTreeWidget.takeTopLevelItem(self.uiTraceNGTreeWidget.indexOfTopLevelItem(item))
|
||||
|
||||
def loadPreferences(self):
|
||||
"""
|
||||
Loads the TraceNG node preferences.
|
||||
"""
|
||||
|
||||
self._traceng_nodes = {}
|
||||
templates = TemplateManager.instance().templates()
|
||||
for template_id, template in templates.items():
|
||||
if template.template_type() == "traceng" and not template.builtin():
|
||||
name = template.name()
|
||||
server = template.compute_id()
|
||||
#TODO: use template id for the key
|
||||
key = "{server}:{name}".format(server=server, name=name)
|
||||
self._traceng_nodes[key] = copy.deepcopy(template.settings())
|
||||
|
||||
self._items.clear()
|
||||
for key, node in self._traceng_nodes.items():
|
||||
item = QtWidgets.QTreeWidgetItem(self.uiTraceNGTreeWidget)
|
||||
item.setText(0, node["name"])
|
||||
Controller.instance().getSymbolIcon(node["symbol"], qpartial(self._setItemIcon, item))
|
||||
item.setData(0, QtCore.Qt.UserRole, key)
|
||||
self._items.append(item)
|
||||
|
||||
if self._items:
|
||||
self.uiTraceNGTreeWidget.setCurrentItem(self._items[0])
|
||||
self.uiTraceNGTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
|
||||
|
||||
def _setItemIcon(self, item, icon):
|
||||
item.setIcon(0, icon)
|
||||
self.uiTraceNGTreeWidget.setMaximumWidth(self.uiTraceNGTreeWidget.sizeHintForColumn(0) + 20)
|
||||
|
||||
def savePreferences(self):
|
||||
"""
|
||||
Saves the TraceNG node preferences.
|
||||
"""
|
||||
|
||||
templates = []
|
||||
for template in TemplateManager.instance().templates().values():
|
||||
if template.template_type() != "traceng":
|
||||
templates.append(template)
|
||||
for template_settings in self._traceng_nodes.values():
|
||||
templates.append(Template(template_settings))
|
||||
TemplateManager.instance().updateList(templates)
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Configuration page for TraceNG preferences.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
|
||||
from .. import TraceNG
|
||||
from ..ui.traceng_preferences_page_ui import Ui_TraceNGPreferencesPageWidget
|
||||
from ..settings import TRACENG_SETTINGS
|
||||
|
||||
|
||||
class TraceNGPreferencesPage(QtWidgets.QWidget, Ui_TraceNGPreferencesPageWidget):
|
||||
"""
|
||||
QWidget preference page for TraceNG
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
# connect signals
|
||||
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
|
||||
self.uiTraceNGPathToolButton.clicked.connect(self._tracengPathBrowserSlot)
|
||||
|
||||
def _tracengPathBrowserSlot(self):
|
||||
"""
|
||||
Slot to open a file browser and select traceng
|
||||
"""
|
||||
|
||||
filter = ""
|
||||
if sys.platform.startswith("win"):
|
||||
filter = "Executable (*.exe);;All files (*.*)"
|
||||
traceng_path = shutil.which("traceng")
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select TraceNG", traceng_path, filter)
|
||||
if not path:
|
||||
return
|
||||
|
||||
if self._checkTraceNGPath(path):
|
||||
self.uiTraceNGPathLineEdit.setText(os.path.normpath(path))
|
||||
|
||||
def _checkTraceNGPath(self, path):
|
||||
"""
|
||||
Checks that the TraceNG path is valid.
|
||||
|
||||
:param path: TraceNG path
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
if not os.path.exists(path):
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG", '"{}" does not exist'.format(path))
|
||||
return False
|
||||
|
||||
if not os.access(path, os.X_OK):
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG", "{} is not an executable".format(os.path.basename(path)))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _restoreDefaultsSlot(self):
|
||||
"""
|
||||
Slot to populate the page widgets with the default settings.
|
||||
"""
|
||||
|
||||
self._populateWidgets(TRACENG_SETTINGS)
|
||||
|
||||
def _useLocalServerSlot(self, state):
|
||||
"""
|
||||
Slot to enable or not local server settings.
|
||||
"""
|
||||
|
||||
if state:
|
||||
self.uiTraceNGPathLineEdit.setEnabled(True)
|
||||
self.uiTraceNGPathToolButton.setEnabled(True)
|
||||
else:
|
||||
self.uiTraceNGPathLineEdit.setEnabled(False)
|
||||
self.uiTraceNGPathToolButton.setEnabled(False)
|
||||
|
||||
def _populateWidgets(self, settings):
|
||||
"""
|
||||
Populates the widgets with the settings.
|
||||
|
||||
:param settings: TraceNG settings
|
||||
"""
|
||||
|
||||
self.uiTraceNGPathLineEdit.setText(settings["traceng_path"])
|
||||
|
||||
def loadPreferences(self):
|
||||
"""
|
||||
Loads TraceNG preferences.
|
||||
"""
|
||||
|
||||
traceng_settings = TraceNG.instance().settings()
|
||||
self._populateWidgets(traceng_settings)
|
||||
|
||||
def savePreferences(self):
|
||||
"""
|
||||
Saves TraceNG preferences.
|
||||
"""
|
||||
|
||||
traceng_path = self.uiTraceNGPathLineEdit.text().strip()
|
||||
if traceng_path and not self._checkTraceNGPath(traceng_path):
|
||||
return
|
||||
new_settings = {"traceng_path": traceng_path}
|
||||
TraceNG.instance().setSettings(new_settings)
|
||||
@@ -1,37 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Default TraceNG settings.
|
||||
"""
|
||||
|
||||
from gns3.node import Node
|
||||
|
||||
TRACENG_SETTINGS = {
|
||||
"traceng_path": "",
|
||||
}
|
||||
|
||||
TRACENG_NODES_SETTINGS = {
|
||||
"name": "",
|
||||
"ip_address": "",
|
||||
"default_destination": "",
|
||||
"default_name_format": "TraceNG{0}",
|
||||
"console_type": "none",
|
||||
"symbol": ":/symbols/traceng.svg",
|
||||
"category": Node.end_devices,
|
||||
"node_type": "traceng"
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
TraceNG node implementation.
|
||||
"""
|
||||
|
||||
from gns3.node import Node
|
||||
from gns3.qt import QtWidgets
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TraceNGNode(Node):
|
||||
"""
|
||||
TraceNG node.
|
||||
|
||||
:param module: parent module for this node
|
||||
:param server: GNS3 server instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
URL_PREFIX = "traceng"
|
||||
|
||||
def __init__(self, module, server, project):
|
||||
super().__init__(module, server, project)
|
||||
|
||||
traceng_settings = {"console_type": "none",
|
||||
"ip_address": "",
|
||||
"default_destination": ""}
|
||||
|
||||
self._last_destination = ""
|
||||
self.settings().update(traceng_settings)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts this node instance.
|
||||
"""
|
||||
|
||||
if self.isStarted():
|
||||
log.debug("{} is already running".format(self.name()))
|
||||
return
|
||||
|
||||
if self._last_destination:
|
||||
destination = self._last_destination
|
||||
else:
|
||||
destination = self.settings()["default_destination"]
|
||||
destination, ok = QtWidgets.QInputDialog.getText(self.parent(), "TraceNG", "Destination host or IP address:", text=destination)
|
||||
if ok:
|
||||
if not destination:
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG", "Please provide a host or IP address to trace")
|
||||
return
|
||||
ip_address = self.settings()["ip_address"]
|
||||
if destination == ip_address:
|
||||
QtWidgets.QMessageBox.critical(self, "TraceNG", "Destination cannot be the same as this node IP address ({})".format(ip_address))
|
||||
return
|
||||
self._last_destination = destination
|
||||
params = {"destination": destination}
|
||||
log.debug("{} is starting".format(self.name()))
|
||||
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, body=params, timeout=None, showProgress=False)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
Returns information about this TraceNG node.
|
||||
|
||||
:returns: formatted string
|
||||
"""
|
||||
|
||||
info = """Node {name} is {state}
|
||||
Running on server {host} with port {port}
|
||||
Local ID is {id} and server ID is {node_id}
|
||||
Console is on port {console}
|
||||
IP address is {ip_address}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
node_id=self._node_id,
|
||||
state=self.state(),
|
||||
host=self.compute().name(),
|
||||
port=self.compute().port(),
|
||||
console=self._settings["console"],
|
||||
ip_address=self._settings["ip_address"])
|
||||
|
||||
port_info = ""
|
||||
for port in self._ports:
|
||||
if port.isFree():
|
||||
port_info += " {port_name} is empty\n".format(port_name=port.name())
|
||||
else:
|
||||
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
|
||||
port_description=port.description())
|
||||
|
||||
return info + port_info
|
||||
|
||||
def configPage(self):
|
||||
"""
|
||||
Returns the configuration page widget to be used by the node properties dialog.
|
||||
|
||||
:returns: QWidget object
|
||||
"""
|
||||
|
||||
from .pages.traceng_node_configuration_page import TraceNGNodeConfigurationPage
|
||||
return TraceNGNodeConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
Returns the default symbol path for this node.
|
||||
|
||||
:returns: symbol path (or resource).
|
||||
"""
|
||||
|
||||
return ":/symbols/traceng.svg"
|
||||
|
||||
@staticmethod
|
||||
def categories():
|
||||
"""
|
||||
Returns the node categories the node is part of (used by the node panel).
|
||||
|
||||
:returns: list of node categories
|
||||
"""
|
||||
|
||||
return [Node.end_devices]
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return "TraceNG node"
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceNGNodeConfigPageWidget</class>
|
||||
<widget class="QWidget" name="TraceNGNodeConfigPageWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>846</width>
|
||||
<height>340</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>TraceNG node template configuration</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiIPAddressLabel">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiDefaultDestinationLabel">
|
||||
<property name="text">
|
||||
<string>Default destination:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QLabel" name="uiDefaultNameFormatLabel">
|
||||
<property name="text">
|
||||
<string>Default name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiSymbolToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="3">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="3">
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>263</width>
|
||||
<height>212</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QLineEdit" name="uiDefaultDestinationLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QLineEdit" name="uiIPAddressLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>uiNameLabel</zorder>
|
||||
<zorder>uiNameLineEdit</zorder>
|
||||
<zorder>uiDefaultNameFormatLabel</zorder>
|
||||
<zorder>uiDefaultNameFormatLineEdit</zorder>
|
||||
<zorder>uiSymbolLabel</zorder>
|
||||
<zorder>uiCategoryLabel</zorder>
|
||||
<zorder>uiCategoryComboBox</zorder>
|
||||
<zorder>uiIPAddressLabel</zorder>
|
||||
<zorder>uiIPAddressLineEdit</zorder>
|
||||
<zorder>uiDefaultDestinationLabel</zorder>
|
||||
<zorder>uiDefaultDestinationLineEdit</zorder>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.11.3
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_TraceNGNodeConfigPageWidget(object):
|
||||
def setupUi(self, TraceNGNodeConfigPageWidget):
|
||||
TraceNGNodeConfigPageWidget.setObjectName("TraceNGNodeConfigPageWidget")
|
||||
TraceNGNodeConfigPageWidget.resize(846, 340)
|
||||
self.gridLayout = QtWidgets.QGridLayout(TraceNGNodeConfigPageWidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiNameLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiIPAddressLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
|
||||
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
|
||||
self.uiDefaultDestinationLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiDefaultDestinationLabel.setObjectName("uiDefaultDestinationLabel")
|
||||
self.gridLayout.addWidget(self.uiDefaultDestinationLabel, 2, 0, 1, 2)
|
||||
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
|
||||
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 3, 0, 1, 3)
|
||||
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
|
||||
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
|
||||
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 3, 3, 1, 1)
|
||||
self.uiSymbolLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
|
||||
self.gridLayout.addWidget(self.uiSymbolLabel, 4, 0, 1, 1)
|
||||
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
|
||||
self.uiSymbolLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
|
||||
self.uiSymbolLineEdit.setObjectName("uiSymbolLineEdit")
|
||||
self.horizontalLayout_7.addWidget(self.uiSymbolLineEdit)
|
||||
self.uiSymbolToolButton = QtWidgets.QToolButton(TraceNGNodeConfigPageWidget)
|
||||
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
|
||||
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_7, 4, 3, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(TraceNGNodeConfigPageWidget)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout.addWidget(self.uiCategoryLabel, 5, 0, 1, 2)
|
||||
self.uiCategoryComboBox = QtWidgets.QComboBox(TraceNGNodeConfigPageWidget)
|
||||
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
|
||||
self.gridLayout.addWidget(self.uiCategoryComboBox, 5, 3, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 212, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem, 6, 1, 1, 3)
|
||||
self.uiDefaultDestinationLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
|
||||
self.uiDefaultDestinationLineEdit.setObjectName("uiDefaultDestinationLineEdit")
|
||||
self.gridLayout.addWidget(self.uiDefaultDestinationLineEdit, 2, 3, 1, 1)
|
||||
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
|
||||
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
|
||||
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 3, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(TraceNGNodeConfigPageWidget)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 3, 1, 1)
|
||||
self.uiNameLabel.raise_()
|
||||
self.uiNameLineEdit.raise_()
|
||||
self.uiDefaultNameFormatLabel.raise_()
|
||||
self.uiDefaultNameFormatLineEdit.raise_()
|
||||
self.uiSymbolLabel.raise_()
|
||||
self.uiCategoryLabel.raise_()
|
||||
self.uiCategoryComboBox.raise_()
|
||||
self.uiIPAddressLabel.raise_()
|
||||
self.uiIPAddressLineEdit.raise_()
|
||||
self.uiDefaultDestinationLabel.raise_()
|
||||
self.uiDefaultDestinationLineEdit.raise_()
|
||||
|
||||
self.retranslateUi(TraceNGNodeConfigPageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeConfigPageWidget)
|
||||
|
||||
def retranslateUi(self, TraceNGNodeConfigPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
TraceNGNodeConfigPageWidget.setWindowTitle(_translate("TraceNGNodeConfigPageWidget", "TraceNG node template configuration"))
|
||||
self.uiNameLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Name:"))
|
||||
self.uiIPAddressLabel.setText(_translate("TraceNGNodeConfigPageWidget", "IP address:"))
|
||||
self.uiDefaultDestinationLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default destination:"))
|
||||
self.uiDefaultNameFormatLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Default name format:"))
|
||||
self.uiSymbolLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Symbol:"))
|
||||
self.uiSymbolToolButton.setText(_translate("TraceNGNodeConfigPageWidget", "&Browse..."))
|
||||
self.uiCategoryLabel.setText(_translate("TraceNGNodeConfigPageWidget", "Category:"))
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceNGNodePageWidget</class>
|
||||
<widget class="QWidget" name="TraceNGNodePageWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>455</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>TraceNG nodes</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>TraceNG node templates</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTreeWidget" name="uiTraceNGTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="uiTraceNGInfoTreeWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiNewTraceNGPushButton">
|
||||
<property name="text">
|
||||
<string>&New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiEditTraceNGPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="uiDeleteTraceNGPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiNewTraceNGPushButton</tabstop>
|
||||
<tabstop>uiDeleteTraceNGPushButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<designerdata>
|
||||
<property name="gridDeltaX">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="gridDeltaY">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="gridSnapX">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="gridSnapY">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="gridVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</designerdata>
|
||||
</ui>
|
||||
@@ -1,83 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_TraceNGNodePageWidget(object):
|
||||
def setupUi(self, TraceNGNodePageWidget):
|
||||
TraceNGNodePageWidget.setObjectName("TraceNGNodePageWidget")
|
||||
TraceNGNodePageWidget.resize(546, 455)
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(TraceNGNodePageWidget)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(TraceNGNodePageWidget)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.uiTraceNGTreeWidget = QtWidgets.QTreeWidget(self.splitter)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiTraceNGTreeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiTraceNGTreeWidget.setSizePolicy(sizePolicy)
|
||||
self.uiTraceNGTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.uiTraceNGTreeWidget.setFont(font)
|
||||
self.uiTraceNGTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.uiTraceNGTreeWidget.setIconSize(QtCore.QSize(32, 32))
|
||||
self.uiTraceNGTreeWidget.setRootIsDecorated(False)
|
||||
self.uiTraceNGTreeWidget.setObjectName("uiTraceNGTreeWidget")
|
||||
self.uiTraceNGTreeWidget.headerItem().setText(0, "1")
|
||||
self.uiTraceNGTreeWidget.header().setVisible(False)
|
||||
self.layoutWidget = QtWidgets.QWidget(self.splitter)
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiTraceNGInfoTreeWidget = QtWidgets.QTreeWidget(self.layoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiTraceNGInfoTreeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiTraceNGInfoTreeWidget.setSizePolicy(sizePolicy)
|
||||
self.uiTraceNGInfoTreeWidget.setIndentation(10)
|
||||
self.uiTraceNGInfoTreeWidget.setAllColumnsShowFocus(True)
|
||||
self.uiTraceNGInfoTreeWidget.setObjectName("uiTraceNGInfoTreeWidget")
|
||||
self.uiTraceNGInfoTreeWidget.header().setVisible(False)
|
||||
self.verticalLayout.addWidget(self.uiTraceNGInfoTreeWidget)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.uiNewTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
|
||||
self.uiNewTraceNGPushButton.setObjectName("uiNewTraceNGPushButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiNewTraceNGPushButton)
|
||||
self.uiEditTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
|
||||
self.uiEditTraceNGPushButton.setEnabled(False)
|
||||
self.uiEditTraceNGPushButton.setObjectName("uiEditTraceNGPushButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiEditTraceNGPushButton)
|
||||
self.uiDeleteTraceNGPushButton = QtWidgets.QPushButton(self.layoutWidget)
|
||||
self.uiDeleteTraceNGPushButton.setEnabled(False)
|
||||
self.uiDeleteTraceNGPushButton.setObjectName("uiDeleteTraceNGPushButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiDeleteTraceNGPushButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_5)
|
||||
self.verticalLayout_2.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(TraceNGNodePageWidget)
|
||||
QtCore.QMetaObject.connectSlotsByName(TraceNGNodePageWidget)
|
||||
TraceNGNodePageWidget.setTabOrder(self.uiNewTraceNGPushButton, self.uiDeleteTraceNGPushButton)
|
||||
|
||||
def retranslateUi(self, TraceNGNodePageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
TraceNGNodePageWidget.setWindowTitle(_translate("TraceNGNodePageWidget", "TraceNG nodes"))
|
||||
TraceNGNodePageWidget.setAccessibleName(_translate("TraceNGNodePageWidget", "TraceNG node templates"))
|
||||
self.uiTraceNGInfoTreeWidget.headerItem().setText(0, _translate("TraceNGNodePageWidget", "1"))
|
||||
self.uiTraceNGInfoTreeWidget.headerItem().setText(1, _translate("TraceNGNodePageWidget", "2"))
|
||||
self.uiNewTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&New"))
|
||||
self.uiEditTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Edit"))
|
||||
self.uiDeleteTraceNGPushButton.setText(_translate("TraceNGNodePageWidget", "&Delete"))
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TraceNGNodeWizard</class>
|
||||
<widget class="QWizard" name="TraceNGNodeWizard">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>706</width>
|
||||
<height>452</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>New TraceNG node template</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWizardPage" name="uiServerWizardPage">
|
||||
<property name="title">
|
||||
<string>Server</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please choose a server type to run the TraceNG node.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="uiServerTypeGroupBox">
|
||||
<property name="title">
|
||||
<string>Server type</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiRemoteRadioButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run the TraceNG node on a remote computer</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiVMRadioButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run the TraceNG node on the GNS3 VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiLocalRadioButton">
|
||||
<property name="text">
|
||||
<string>Run the TraceNG node on your local computer</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="uiRemoteServersGroupBox">
|
||||
<property name="title">
|
||||
<string>Remote server</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiRemoteServersLabel">
|
||||
<property name="text">
|
||||
<string>Run on:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="uiRemoteServersComboBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiNameWizardPage">
|
||||
<property name="title">
|
||||
<string>Name and IP address</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please choose a descriptive name and IP address for the new TraceNG node.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiIPAddressLabel">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiIPAddressLineEdit">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>uiNameLineEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/traceng/ui/traceng_node_wizard.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_TraceNGNodeWizard(object):
|
||||
def setupUi(self, TraceNGNodeWizard):
|
||||
TraceNGNodeWizard.setObjectName("TraceNGNodeWizard")
|
||||
TraceNGNodeWizard.resize(706, 452)
|
||||
TraceNGNodeWizard.setModal(True)
|
||||
self.uiServerWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiServerWizardPage)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteRadioButton.setEnabled(False)
|
||||
self.uiRemoteRadioButton.setChecked(False)
|
||||
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
|
||||
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
|
||||
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
|
||||
self.verticalLayout.addWidget(self.uiVMRadioButton)
|
||||
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
|
||||
self.verticalLayout.addWidget(self.uiLocalRadioButton)
|
||||
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
|
||||
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
|
||||
self.gridLayout_7 = QtWidgets.QGridLayout(self.uiRemoteServersGroupBox)
|
||||
self.gridLayout_7.setObjectName("gridLayout_7")
|
||||
self.uiRemoteServersLabel = QtWidgets.QLabel(self.uiRemoteServersGroupBox)
|
||||
self.uiRemoteServersLabel.setObjectName("uiRemoteServersLabel")
|
||||
self.gridLayout_7.addWidget(self.uiRemoteServersLabel, 0, 0, 1, 1)
|
||||
self.uiRemoteServersComboBox = QtWidgets.QComboBox(self.uiRemoteServersGroupBox)
|
||||
self.uiRemoteServersComboBox.setEnabled(False)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiRemoteServersComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiRemoteServersComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiRemoteServersComboBox.setObjectName("uiRemoteServersComboBox")
|
||||
self.gridLayout_7.addWidget(self.uiRemoteServersComboBox, 0, 1, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.uiRemoteServersGroupBox, 1, 0, 1, 1)
|
||||
TraceNGNodeWizard.addPage(self.uiServerWizardPage)
|
||||
self.uiNameWizardPage = QtWidgets.QWizardPage()
|
||||
self.uiNameWizardPage.setObjectName("uiNameWizardPage")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.uiNameWizardPage)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.uiNameLabel = QtWidgets.QLabel(self.uiNameWizardPage)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiIPAddressLabel = QtWidgets.QLabel(self.uiNameWizardPage)
|
||||
self.uiIPAddressLabel.setObjectName("uiIPAddressLabel")
|
||||
self.gridLayout.addWidget(self.uiIPAddressLabel, 1, 0, 1, 1)
|
||||
self.uiIPAddressLineEdit = QtWidgets.QLineEdit(self.uiNameWizardPage)
|
||||
self.uiIPAddressLineEdit.setText("")
|
||||
self.uiIPAddressLineEdit.setObjectName("uiIPAddressLineEdit")
|
||||
self.gridLayout.addWidget(self.uiIPAddressLineEdit, 1, 1, 1, 1)
|
||||
TraceNGNodeWizard.addPage(self.uiNameWizardPage)
|
||||
|
||||
self.retranslateUi(TraceNGNodeWizard)
|
||||
QtCore.QMetaObject.connectSlotsByName(TraceNGNodeWizard)
|
||||
|
||||
def retranslateUi(self, TraceNGNodeWizard):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
TraceNGNodeWizard.setWindowTitle(_translate("TraceNGNodeWizard", "New TraceNG node template"))
|
||||
self.uiServerWizardPage.setTitle(_translate("TraceNGNodeWizard", "Server"))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a server type to run the TraceNG node."))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("TraceNGNodeWizard", "Server type"))
|
||||
self.uiRemoteRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on a remote computer"))
|
||||
self.uiVMRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on the GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("TraceNGNodeWizard", "Run the TraceNG node on your local computer"))
|
||||
self.uiRemoteServersGroupBox.setTitle(_translate("TraceNGNodeWizard", "Remote server"))
|
||||
self.uiRemoteServersLabel.setText(_translate("TraceNGNodeWizard", "Run on:"))
|
||||
self.uiNameWizardPage.setTitle(_translate("TraceNGNodeWizard", "Name and IP address"))
|
||||
self.uiNameWizardPage.setSubTitle(_translate("TraceNGNodeWizard", "Please choose a descriptive name and IP address for the new TraceNG node."))
|
||||
self.uiNameLabel.setText(_translate("TraceNGNodeWizard", "Name:"))
|
||||
self.uiIPAddressLabel.setText(_translate("TraceNGNodeWizard", "IP address:"))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user