Compare commits

..

143 Commits
v1.0 ... v1.2

Author SHA1 Message Date
Jeremy
319ac95f8a Bump to version 1.2 2014-11-19 19:28:21 -07:00
Jeremy
0370d3084f Change how the local server is shutdown on Linux/OSX.
Some more style tweaks.
2014-11-19 19:11:51 -07:00
Jeremy
30cd030943 Charcoal style tweak for Mac OS X. 2014-11-19 18:08:51 -07:00
Jeremy
ee98a78264 Style improvements. 2014-11-19 17:34:02 -07:00
Jeremy
2abf316192 Classic style. 2014-11-19 14:55:28 -07:00
Jeremy Grossmann
25d1abcd6c Merge pull request #147 from vikram186/ui_fix
module/ios_preferences: Fix button state
2014-11-19 14:05:04 -07:00
Vikram Narayanan
3b823d9696 module/ios_preferences: Fix button state
Make the setEnabled to False when the list is empty

Signed-off-by: Vikram Narayanan <vikram186@gmail.com>
2014-11-19 21:03:24 +01:00
grossmj
7e13c2c67a Restore dock widgets. 2014-11-19 10:22:09 -07:00
Jeremy
ad57e3e9b6 Improvement for the charcoal style & style options move to general preferences. 2014-11-17 19:29:22 -07:00
Jeremy
ceacb5aceb Adds back missing start, stop and pause icons for the contextual menu. 2014-11-17 12:29:42 -07:00
Jeremy
dc9f012d25 Bump to version 1.2.dev3 2014-11-15 16:47:30 -07:00
Jeremy
a21a397397 Linked clone support for VirtualBox (still problems with temporary projects). 2014-11-15 16:05:55 -07:00
Jeremy
fa86a04acc Fixes "don't show this again" for the getting started dialog. 2014-11-15 12:42:21 -07:00
Jeremy
4cf3f595d6 Fixes remote server issue when creating a new project while already in a project. 2014-11-14 19:59:06 -07:00
Jeremy
e619e56537 Shows cancel button in Wizards on OSX. Fixes crash on Windows 32-bit. 2014-11-14 10:18:36 -07:00
Jeremy Grossmann
0c9c81e9dc Merge pull request #142 from planctechnologies/pr3
Add support for Qemu devices on cloud instances (gui)
2014-11-12 21:20:51 -07:00
Jeremy Grossmann
3b5184b007 Merge pull request #141 from planctechnologies/pr2
Support IOU devices on cloud instances
2014-11-12 21:20:24 -07:00
Jeremy
0c6bfdafc3 Prepare new dark style. 2014-11-12 19:47:49 -07:00
Jerry Seutter
f64838ac59 Fix pull branch 2014-11-12 14:18:16 -07:00
Nasrullah Taha
53cfec5ce0 Merge pull request #43 from planctechnologies/gns-129
Move image path manipulation to server side
2014-11-12 11:56:42 -07:00
Nasrullah Taha
c9dfd99697 Merge branch 'dev' of github.com:planctechnologies/gns3-gui into dev 2014-11-12 11:41:30 -07:00
Jerry Seutter
3b29e898e4 Merge branch 'dev' of github.com:planctechnologies/gns3-gui into dev 2014-11-12 11:07:24 -07:00
Jerry Seutter
e89ad3ec4f Handle pre-existing ssh keys when creating an instance 2014-11-12 11:07:07 -07:00
Nasrullah Taha
c2a193cb8a Merge branch 'gns-125' into dev 2014-11-12 11:00:15 -07:00
Nasrullah Taha
26b668099c Merge pull request #38 from planctechnologies/gns-123
Add support for running IOU devices on cloud instances
2014-11-12 10:53:31 -07:00
Jeremy
f67f0f3768 Option to test IOS image from the wizard. 2014-11-10 18:55:41 -07:00
Jeremy
860873ac21 Adds gns3-converter as a dependency and finish integration. 2014-11-10 14:58:28 -07:00
Jeremy Grossmann
0472e4fddd Merge pull request #140 from planctechnologies/pr1
Instance inspector improvements
2014-11-10 11:56:28 -07:00
Jerry Seutter
4aa8f6f49a Do path manipulation server side, and remove UploadFileThread class 2014-11-10 11:30:11 -07:00
jseutter
4de3c78cfe Merge pull request #40 from planctechnologies/gns-106
Gns 106 list_instance failure causes missing server instances
2014-11-10 11:01:59 -07:00
jseutter
7df9316539 Merge pull request #42 from planctechnologies/gns-37a
Highlight devices running on instance
2014-11-10 11:00:15 -07:00
jseutter
c2b7a8e86f Merge pull request #41 from planctechnologies/gns-37
Gns 34 -  display the number of devices running on the instance
2014-11-10 09:21:02 -07:00
Massimiliano Pippi
144576ee86 emit signal when table is clicked, not when selection changes 2014-11-10 17:19:38 +01:00
Massimiliano Pippi
cabd57e5ab select nodes running on the selected cloud instance 2014-11-10 15:50:38 +01:00
Massimiliano Pippi
06c2d8d3e6 signal when an instance was selected/deselected 2014-11-10 15:50:38 +01:00
Massimiliano Pippi
f0845e2fff show number of devices running on each cloud instance, fixes gns-34 2014-11-10 11:45:35 +01:00
Massimiliano Pippi
e48d9d76b4 pass the instance id all the way down to the cloud server 2014-11-10 11:44:43 +01:00
Massimiliano Pippi
b250af6f26 populate model lazily, working even when first call to list_instances fails 2014-11-10 10:42:56 +01:00
Massimiliano Pippi
a7688cec8b return None instead of raising KeyError 2014-11-10 10:31:10 +01:00
Jeremy
ec07873990 Option to allow console connections to any local IP address when using the local server. 2014-11-09 23:01:13 -07:00
Jeremy
75cb485e6f Allows Qemu VM to have 0 interface. 2014-11-09 18:27:40 -07:00
Jeremy
4a7cc14270 Adds "open a project" and "recent projects" buttons to the new project dialog.
Option to deactivate the new project dialog at startup.
2014-11-09 18:27:16 -07:00
Jeremy
eca99ccd2c New host node (cloud with all Ethernet & TAP interfaces added). 2014-11-09 16:09:42 -07:00
Jeremy Grossmann
dd03139706 Merge pull request #138 from planctechnologies/dev
Fix cloud instance ready indicator
2014-11-09 11:51:49 -07:00
Jeremy
8ad391d2ab Base for VirtualBox linked clones (not completed yet). 2014-11-09 11:50:47 -07:00
Jerry Seutter
f7e53f685b Add support for running Qemu devices on a cloud instance 2014-11-08 09:02:51 -07:00
Jerry Seutter
0698b3942b Merge branch 'dev' into gns-125 2014-11-06 18:06:59 -07:00
Jerry Seutter
d866cedbca Merge branch 'master' into dev 2014-11-06 18:06:49 -07:00
Jerry Seutter
6105a1629f Merge branch 'dev' into gns-123 2014-11-06 16:28:12 -07:00
jseutter
8a49d90400 Merge pull request #37 from planctechnologies/gns-126
On cloud instances, only show the green icon when the build process is complete
2014-11-06 16:25:26 -07:00
Jerry Seutter
10dfd59203 Add support for running IOU devices on cloud instances 2014-11-06 16:07:40 -07:00
grossmj
afa91ef2f3 Merge remote-tracking branch 'origin/master' 2014-11-06 13:59:05 -07:00
grossmj
862d97b0a4 Rename "enable console" to "remote console". 2014-11-06 13:56:19 -07:00
Massimiliano Pippi
05a437e2fc fixed instance state handling 2014-11-06 09:33:13 +01:00
Massimiliano Pippi
6b679fc89e added a method to retrieve an instance by id 2014-11-06 09:33:13 +01:00
Jerry Seutter
9f3d831d4c Fixed bug when opening existing cloud projects 2014-11-05 10:07:13 -07:00
Jeremy Grossmann
1403c35443 Merge pull request #137 from planctechnologies/dev
Better tracking of live cloud instances
2014-11-05 09:25:12 -07:00
Jerry Seutter
d440d10371 Remove commented out code 2014-11-05 09:21:28 -07:00
jseutter
58a218f826 Merge pull request #36 from planctechnologies/gns-127
Better tracking of live cloud instances
2014-11-05 09:20:01 -07:00
Jerry Seutter
0756f9104e Call super in __init__ to keep Qt object tree intact 2014-11-05 09:19:45 -07:00
Jeremy
2847296cd4 Settings management refactoring and checks for name collisions. 2014-11-04 18:10:58 -07:00
Jeremy
61317fced0 New news dock widget. 2014-11-04 14:17:33 -07:00
Jeremy Grossmann
767d756ed4 Merge pull request #136 from planctechnologies/dev
Add a ENABLE_CLOUD global, add telnet over ssh support
2014-11-04 10:37:11 -07:00
grossmj
43f1c04d3e Merge remote-tracking branch 'origin/master' 2014-11-03 22:24:47 -07:00
grossmj
c985298c9a Fixes bug when editing a Qemu VM configured to run on a remote server. 2014-11-03 22:24:29 -07:00
Jerry Seutter
8e9574871b Better tracking of live cloud instances 2014-11-03 19:51:13 -07:00
jseutter
3536c0fc19 Merge pull request #35 from planctechnologies/gns-109
Gns 109 / Gns 95: Set up ssh tunnel for telnet session
2014-11-03 19:40:14 -07:00
Jeremy Grossmann
d196db8303 Merge pull request #135 from planctechnologies/cloud-run-merge
Run IOS devices on a cloud instance
2014-11-03 19:18:52 -07:00
Jeremy
18a4eeb6e9 Integrate GNS3 converter. 2014-11-03 17:35:23 -07:00
Jerry Seutter
cb29466e43 Add a ENABLE_CLOUD global 2014-11-03 16:40:42 -07:00
Jerry Seutter
58ad493e9e Merge branch 'master' into dev 2014-11-03 15:40:17 -07:00
grossmj
36b8092e53 Check for duplicate node names in Preferences. 2014-11-03 15:06:08 -07:00
Jerry Seutter
855b9901c7 Remove old comment 2014-11-03 11:45:13 -07:00
grossmj
f0f110b277 Show the Wireshark path when failing to open it. 2014-11-02 10:10:13 -07:00
grossmj
f0e51c59ff Make sure local paths are not sent to remote servers (i.e. Dynamips path). 2014-11-01 16:51:31 -06:00
Jeremy Grossmann
37b1c839e9 Merge pull request #134 from planctechnologies/cloud-export
Project import/export/delete in cloud files
2014-11-01 11:20:06 -06:00
Jerry Seutter
c4226315a5 Merge branch 'master' into cloud-export 2014-10-31 14:08:58 -06:00
Massimiliano Pippi
9fbf593db8 create a new endpoint on the tunnel when connecting via telnet to cloud devices 2014-10-31 17:10:55 +01:00
Massimiliano Pippi
79feec83fb return Endpoint instance instead of Endpoint ID when creating a tunnel 2014-10-31 17:09:34 +01:00
Jerry Seutter
b3b934847d Merge branch 'master' into dev 2014-10-31 10:03:04 -06:00
Massimiliano Pippi
9d71161a32 property to retrieve the tunnel 2014-10-31 16:38:52 +01:00
Massimiliano Pippi
7485fb968a disconnect tunnel when wss is closed 2014-10-31 16:38:39 +01:00
Massimiliano Pippi
4a39aad83d pass the ssh pkey all the way down to the wss and setup a tunnel upon wss connection 2014-10-31 16:35:19 +01:00
Massimiliano Pippi
56faee748e using relative import 2014-10-31 15:09:21 +01:00
Massimiliano Pippi
6577340e05 determine if node is on the cloud 2014-10-31 14:48:50 +01:00
Massimiliano Pippi
d5eef5a5f6 make iTerm work in a separate thread 2014-10-31 13:02:34 +01:00
Massimiliano Pippi
3347180a5a support Terminal sessions in a separate thread 2014-10-31 12:56:36 +01:00
Massimiliano Pippi
c4396fcac0 pass a callback to the telnet command 2014-10-31 12:56:04 +01:00
Massimiliano Pippi
418b912379 run telnet console in a separate thread 2014-10-31 12:39:16 +01:00
grossmj
7b6e32373a Fixes issue when getting the VirtualBox VM list. 2014-10-30 21:10:14 -06:00
Jeremy
c54de58a0d New VirtualBox support (under testing). 2014-10-30 18:53:17 -06:00
Jerry Seutter
7c21c7b29f Merge branch 'dev' of github.com:planctechnologies/gns3-gui into dev 2014-10-30 17:17:31 -06:00
Jerry Seutter
57ee86d251 Fix bug where images not found
Improve ssh_to_server script.
2014-10-30 17:15:59 -06:00
Nasrullah Taha
7c94595313 Merge pull request #34 from planctechnologies/gns-121
Start ios devices on a cloud instance
2014-10-30 13:16:10 -06:00
Jerry Seutter
2bbfefb5e1 Fix merge-induced error 2014-10-30 12:23:31 -06:00
Nasrullah Taha
e9850ee962 -adding root logger handler when no default exists
-fixes to ssh_to_server script for linux
-fixed callback for upload image thread
2014-10-30 12:10:14 -06:00
Jerry Seutter
4828b3a73b Fix bug where image wasn't being downloaded from cloud files 2014-10-29 15:18:23 -06:00
Jerry Seutter
de5e43578a Fix upload path 2014-10-29 10:36:44 -06:00
Jerry Seutter
15380ae80a Code cleanup 2014-10-28 12:11:22 -06:00
Jerry Seutter
e39c1f9579 Merge branch 'gns-110' into gns-121 2014-10-28 11:33:50 -06:00
Jerry Seutter
c0a7ed7a2c Merge branch 'dev' into gns-110 2014-10-28 11:00:14 -06:00
Jerry Seutter
e2be6bd0a9 Merge branch 'master' into dev 2014-10-28 09:32:38 -06:00
Jerry Seutter
6513acf141 Merge branch 'dev' into gns-118 2014-10-28 09:06:27 -06:00
grossmj
1c4ce90093 qemu-system-i386 is the new default on 32-bit platforms. 2014-10-27 21:58:32 -06:00
grossmj
adace0d6a1 Fixes platform detection issue with Cisco IOS image file name. 2014-10-27 19:58:07 -06:00
Jerry Seutter
a960ee05ce Fix missing dynamips symlink 2014-10-27 18:33:03 -06:00
Jerry Seutter
0b46653c79 Pull the server code from github. 2014-10-27 18:23:10 -06:00
Jerry Seutter
120cb89526 IOS devices can be deployed on cloud instances. 2014-10-27 18:11:39 -06:00
Jeremy
72c48968df Update README. 2014-10-27 15:58:13 -06:00
grossmj
2195a6199c Delay (default 500 ms) when Telneting to all nodes. 2014-10-26 19:28:08 -06:00
grossmj
509d5f8b82 Bump version to 1.2.dev1 2014-10-25 18:01:14 -06:00
jseutter
35fe0514ba Merge pull request #33 from planctechnologies/gns-60
GNS-60 Delete cloud project
2014-10-24 12:15:47 -06:00
Nasrullah Taha
98fd7b73b2 added delete cloud project option 2014-10-23 23:43:31 -06:00
grossmj
176bfe88ca Bump version to 1.1 2014-10-22 22:43:48 -06:00
grossmj
c261d2229d Warning message when using a remote server with IOU. 2014-10-22 22:42:36 -06:00
grossmj
99388cde15 Fixes more issues with EtherSwitch router. 2014-10-22 22:29:55 -06:00
grossmj
c07a81d228 Serial console for VirtualBox. 2014-10-22 21:59:11 -06:00
grossmj
6843b7acf2 Fixes EtherSwitch (until we come with a default template for it). 2014-10-22 20:48:46 -06:00
grossmj
1395a624f6 New Idle-PC dialog. 2014-10-22 18:06:37 -06:00
grossmj
bf27483965 Fixes #123 (Qemu binaries list not showing up). 2014-10-22 16:17:17 -06:00
Nasrullah Taha
00dfddde8a fixed tests broken by 9d0bd31 2014-10-22 15:37:07 -06:00
Nasrullah Taha
9d0bd31eea added project import dialog 2014-10-22 15:03:13 -06:00
grossmj
f721efd21c Fixes #119. 2014-10-22 13:18:56 -06:00
grossmj
571f7ac5af Fixes #126 (broken remote server feature). 2014-10-22 12:00:46 -06:00
grossmj
a409361049 Fixes #120 (broken cloud) 2014-10-22 10:56:34 -06:00
Massimiliano Pippi
b4e42f50ec redirect std error for gns3-server 2014-10-22 18:35:51 +02:00
Nasrullah Taha
c8ef818e1c merged dev into gns-59 2014-10-21 16:52:34 -06:00
Nasrullah Taha
d59a2bd7f9 added menu action and empty slot for importing project 2014-10-21 16:43:40 -06:00
Nasrullah Taha
e6cb49a19b added signals to update ui on progress 2014-10-21 16:43:03 -06:00
Jerry Seutter
fa8c166c18 Merge branch 'gns-121' into gns-110 2014-10-21 15:33:37 -06:00
Jerry Seutter
5ad6433e91 Handle opening existing projects 2014-10-21 15:17:49 -06:00
Jerry Seutter
ff4af1e22d Reuse cloud instances that are running when the gui is restarted 2014-10-21 14:39:08 -06:00
grossmj
46ece451ff Bump version to 1.1.dev1 (fixes #118). 2014-10-21 12:36:32 -06:00
Jerry Seutter
8a02288e28 Merge branch 'dev' into gns-121 2014-10-21 10:38:27 -06:00
grossmj
54f6e264cb Bump version to 1.1.dev1. 2014-10-21 10:02:07 -06:00
Jerry Seutter
bf1a797519 Build instance dynamically from default ubuntu image (gns-121) 2014-10-20 17:07:15 -06:00
Nasrullah Taha
fddbe69ba0 added downloading image files used by the imported project 2014-10-17 10:38:38 -06:00
Nasrullah Taha
1a154df621 changed download file method to skip downloading files if the same file already exists at the same local path 2014-10-16 13:15:08 -06:00
Jerry Seutter
e527b13930 Merge remote-tracking branch 'origin/gns-59' into gns-110 2014-10-15 15:34:32 -06:00
Jerry Seutter
aa3ec13e45 Move get_provider() out of utils.py 2014-10-15 15:33:51 -06:00
Nasrullah Taha
b7d7706682 added download file method to BaseCloudCtrl 2014-10-15 12:55:12 -06:00
Nasrullah Taha
20bb456fa8 added method to BaseCloudCtrl that lists project zip files in projects folder in cloud storage 2014-10-14 16:13:57 -06:00
Jerry Seutter
865ae8445d Merge branch 'dev' into gns-118 2014-10-10 09:54:53 -06:00
268 changed files with 35693 additions and 20313 deletions

View File

@@ -1,20 +1,31 @@
GNS3-gui
========
New GNS3 GUI repository (beta stage).
GNS3 GUI repository (beta stage).
Warning: this is not the repository for the stable version of GNS3 (0.8.6), please go to the gns3-legacy repository for it.
Linux (Debian based)
--------------------
Linux/Unix
----------
The following instructions have been tested with Ubuntu and Mint.
You must be connected to the Internet in order to install the dependencies.
Dependencies:
- Python version 3.3 or above
- pip & setuptools must be installed, please see http://pip.readthedocs.org/en/latest/installing.html
(or sudo apt-get install python3-pip but install more packages)
- PyQt must be installed, to install on Debian-like Linux: sudo apt-get install python3-pyqt4
- Dynamips version 0.2.11 or above (http://github.com/GNS3/dynamips)
- Python 3.3 or above
- Setuptools
- PyQt libraries
- Apache Libcloud library
- Requests library
- Paramiko library
The following commands will install some of these dependencies:
.. code:: bash
sudo apt-get install python3-setuptools
sudo apt-get install python3-pyqt4
Finally these commands will install the GUI as well as the rest of the dependencies:
.. code:: bash
@@ -34,7 +45,7 @@ Please use our DMG package or you can manually install using the following steps
`First install homebrew <http://brew.sh/>`_.
Then install GNS3 dependencies.
Then install the GNS3 dependencies.
.. code:: bash

View File

@@ -26,10 +26,10 @@ from collections import namedtuple
import hashlib
import os
import logging
from io import StringIO
from io import StringIO, BytesIO
from libcloud.compute.base import NodeAuthSSHKey
from libcloud.storage.types import ContainerAlreadyExistsError
from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError, ObjectDoesNotExistError
from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed
from .exceptions import OverLimit, BadRequest, ServiceUnavailable
@@ -216,11 +216,11 @@ class BaseCloudCtrl(object):
return self.driver.list_key_pairs()
def upload_file(self, file_path, folder):
def upload_file(self, file_path, cloud_object_name):
"""
Uploads file to cloud storage (if it is not identical to a file already in cloud storage).
:param file_path: path to file to upload
:param folder: folder in cloud storage to save file in
:param cloud_object_name: name of file saved in cloud storage
:return: True if file was uploaded, False if it was skipped because it already existed and was identical
"""
try:
@@ -231,7 +231,6 @@ class BaseCloudCtrl(object):
with open(file_path, 'rb') as file:
local_file_hash = hashlib.md5(file.read()).hexdigest()
cloud_object_name = folder + '/' + os.path.basename(file_path)
cloud_hash_name = cloud_object_name + '.md5'
cloud_objects = [obj.name for obj in gns3_container.list_objects()]
@@ -250,3 +249,93 @@ class BaseCloudCtrl(object):
self.storage_driver.upload_object_via_stream(file, gns3_container, cloud_object_name)
self.storage_driver.upload_object_via_stream(StringIO(local_file_hash), gns3_container, cloud_hash_name)
return True
def list_projects(self):
"""
Lists projects in cloud storage
:return: Dictionary where project names are keys and values are names of objects in storage
"""
try:
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
projects = {
obj.name.replace('projects/', '').replace('.zip', ''): obj.name
for obj in gns3_container.list_objects()
if obj.name.startswith('projects/') and obj.name[-4:] == '.zip'
}
return projects
except ContainerDoesNotExistError:
return []
def download_file(self, file_name, destination=None):
"""
Downloads file from cloud storage. If a file exists at destination, and it is identical to the file in cloud
storage, it is not downloaded.
:param file_name: name of file in cloud storage to download
:param destination: local path to save file to (if None, returns file contents as a file-like object)
:return: A file-like object if file contents are returned, or None if file is saved to filesystem
"""
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
storage_object = gns3_container.get_object(file_name)
if destination is not None:
if os.path.isfile(destination):
# if a file exists at destination and its hash matches that of the
# file in cloud storage, don't download it
with open(destination, 'rb') as f:
local_file_hash = hashlib.md5(f.read()).hexdigest()
hash_object = gns3_container.get_object(file_name + '.md5')
cloud_object_hash = ''
for chunk in hash_object.as_stream():
cloud_object_hash += chunk.decode('utf8')
if local_file_hash == cloud_object_hash:
return
storage_object.download(destination)
else:
contents = b''
for chunk in storage_object.as_stream():
contents += chunk
return BytesIO(contents)
def find_storage_image_names(self, images_to_find):
"""
Maps names of image files to their full name in cloud storage
:param images_to_find: list of image names to find
:return: A dictionary where keys are image names, and values are the corresponding names of
the files in cloud storage
"""
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
images_in_storage = [obj.name for obj in gns3_container.list_objects() if obj.name.startswith('images/')]
images = {}
for image_name in images_to_find:
images_with_same_name =\
list(filter(lambda storage_image_name: storage_image_name.endswith(image_name), images_in_storage))
if len(images_with_same_name) == 1:
images[image_name] = images_with_same_name[0]
else:
raise Exception('Image does not exist in cloud storage or is duplicated')
return images
def delete_file(self, file_name):
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
try:
object_to_delete = gns3_container.get_object(file_name)
object_to_delete.delete()
except ObjectDoesNotExistError:
pass
try:
hash_object = gns3_container.get_object(file_name + '.md5')
hash_object.delete()
except ObjectDoesNotExistError:
pass

View File

@@ -17,7 +17,7 @@
""" Interacts with Rackspace API to create and manage cloud instances. """
from gns3.cloud.base_cloud_ctrl import BaseCloudCtrl
from .base_cloud_ctrl import BaseCloudCtrl
import json
import requests
from libcloud.compute.drivers.rackspace import ENDPOINT_ARGS_MAP
@@ -276,3 +276,36 @@ class RackspaceCtrl(BaseCloudCtrl):
def get_image(self, image_id):
return self.driver.get_image(image_id)
def get_provider(cloud_settings):
"""
Utility function to retrieve a cloud provider instance already authenticated and with the
region set
:param cloud_settings: cloud settings dictionary
:return: a provider instance or None on errors
"""
try:
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
ias_url = cloud_settings['gns3_ias_url']
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey, ias_url)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")
return
if not region:
region = provider.list_regions().values()[0]
if not provider.set_region(region):
log.error("Unable to set cloud provider region")
return
return provider

View File

@@ -1,16 +1,19 @@
from contextlib import contextmanager
import io
import json
from socket import error as socket_error
import logging
import os
import zipfile
import select
import tempfile
import time
import zipfile
from PyQt4 import QtCore
from PyQt4.QtCore import QThread
from PyQt4.QtCore import pyqtSignal
from .rackspace_ctrl import RackspaceCtrl
from .exceptions import KeyPairExists
from .rackspace_ctrl import RackspaceCtrl, get_provider
from ..topology import Topology
from ..servers import Servers
@@ -51,39 +54,6 @@ def ssh_client(host, key_string):
client.close()
def get_provider(cloud_settings):
"""
Utility function to retrieve a cloud provider instance already authenticated and with the
region set
:param cloud_settings: cloud settings dictionary
:return: a provider instance or None on errors
"""
try:
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
ias_url = cloud_settings['gns3_ias_url']
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey, ias_url)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")
return
if not region:
region = provider.list_regions().values()[0]
if not provider.set_region(region):
log.error("Unable to set cloud provider region")
return
return provider
class ListInstancesThread(QThread):
"""
Helper class to retrieve data from the provider in a separate thread,
@@ -98,9 +68,7 @@ class ListInstancesThread(QThread):
def run(self):
try:
instances = self._provider.list_instances()
log.debug('Instance list:')
for instance in instances:
log.debug(' name={}, state={}'.format(instance.name, instance.state))
log.debug('Instance list: {}'.format([(i.name, i.state) for i in instances]))
self.instancesReady.emit(instances)
except Exception as e:
log.info('list_instances error: {}'.format(e))
@@ -120,8 +88,19 @@ class CreateInstanceThread(QThread):
self._image_id = image_id
def run(self):
k = self._provider.create_key_pair(self._name)
log.debug("Creating cloud keypair with name {}".format(self._name))
try:
k = self._provider.create_key_pair(self._name)
except KeyPairExists:
log.debug("Cloud keypair with name {} exists. Recreating.".format(self._name))
# delete keypairs if they already exist
self._provider.delete_key_pair_by_name(self._name)
k = self._provider.create_key_pair(self._name)
log.debug("Creating cloud server with name {}".format(self._name))
i = self._provider.create_instance(self._name, self._flavor_id, self._image_id, k)
log.debug("Cloud server {} created".format(self._name))
self.instanceCreated.emit(i, k)
@@ -148,6 +127,48 @@ class StartGNS3ServerThread(QThread):
"""
gns3server_started = pyqtSignal(str, str, str)
# This is for testing without pushing to github
# commands = '''
# DEBIAN_FRONTEND=noninteractive dpkg --configure -a
# DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
# DEBIAN_FRONTEND=noninteractive apt-get -y update
# DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
# DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
# DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
# ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
# mkdir -p /opt/gns3
# tar xzf /tmp/gns3-server.tgz -C /opt/gns3
# cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
# cd /opt/gns3/gns3-server; python3 ./setup.py install
# ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
# wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
# python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
# hostname gns3-iouvm
# tar xzf iouyap.tar.gz -C /usr/local/bin
# killall python3 gns3server gns3dms
# '''
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
killall python3 gns3server gns3dms
'''
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
super(QThread, self).__init__(parent)
self._host = host
@@ -158,23 +179,64 @@ class StartGNS3ServerThread(QThread):
self._region = region
self._dead_time = dead_time
def exec_command(self, client, cmd, wait_time=-1):
cmd += '; exit $?'
stdout_data = b''
stderr_data = b''
log.debug('cmd: {}'.format(cmd))
# Send the command (non-blocking)
stdin, stdout, stderr = client.exec_command(cmd)
# Wait for the command to terminate
wait = int(wait_time)
while not stdout.channel.exit_status_ready() and wait != 0:
time.sleep(1)
wait -= 1
stdout_data = stdout.read()
stderr_data = stderr.read()
log.debug('exit status: {}'.format(stdout.channel.exit_status))
log.debug('stdout: {}'.format(stdout_data.decode('utf-8')))
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
def run(self):
with ssh_client(self._host, self._private_key_string) as client:
if client is not None:
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
ssh_connected = False
while not ssh_connected:
with ssh_client(self._host, self._private_key_string) as client:
if client is None:
time.sleep(1)
continue
ssh_connected = True
# This is for testing without pushing to github
# os.system('rm -rf /tmp/gns3-server')
# os.system('cp -a /Users/jseutter/projects/gns3-server /tmp/gns3-server')
# os.system('cd /tmp; tar czf /tmp/gns3-server.tgz gns3-server')
# sftp = client.open_sftp()
# sftp.put('/tmp/gns3-server.tgz', '/tmp/gns3-server.tgz')
# sftp.close()
for cmd in [l for l in self.commands.splitlines() if l.strip()]:
self.exec_command(client, cmd)
data = {
'instance_id': self._server_id,
'cloud_user_name': self._username,
'cloud_api_key': self._api_key,
'region': self._region,
'cloud_region': self._region,
'dead_time': self._dead_time,
}
# TODO: Properly escape the data portion of the command line
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --data="{}" 2>/tmp/gns3_stderr.log'.format(data)
log.debug(start_cmd)
stdin, stdout, stderr = client.exec_command(start_cmd)
response = stdout.read().decode('ascii')
log.debug('ssh response: {}'.format(response))
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._host, data)
stdout, stderr = self.exec_command(client, start_cmd, wait_time=15)
response = stdout.decode('utf-8')
self.gns3server_started.emit(str(self._server_id), str(self._host), str(response))
@@ -185,13 +247,18 @@ class WSConnectThread(QThread):
"""
established = pyqtSignal(str)
def __init__(self, parent, provider, server_id, host, port, ca_file):
def __init__(self, parent, provider, server_id, host, port, ca_file,
auth_user, auth_password, ssh_pkey, instance_id):
super(QThread, self).__init__(parent)
self._provider = provider
self._server_id = server_id
self._host = host
self._port = port
self._ca_file = ca_file
self._auth_user = auth_user
self._auth_password = auth_password
self._ssh_pkey = ssh_pkey
self._instance_id = instance_id
def run(self):
"""
@@ -200,7 +267,9 @@ class WSConnectThread(QThread):
log.debug('WSConnectThread.run() begin')
servers = Servers.instance()
server = servers.getCloudServer(self._host, self._port, self._ca_file)
server = servers.getCloudServer(self._host, self._port, self._ca_file,
self._auth_user, self._auth_password, self._ssh_pkey,
self._instance_id)
log.debug('after getCloudServer call. {}'.format(server))
self.established.emit(str(self._server_id))
@@ -215,14 +284,15 @@ class UploadProjectThread(QThread):
"""
# signals to update the progress dialog.
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, project_settings, cloud_settings):
def __init__(self, cloud_settings, project_path, images_path):
super().__init__()
self.project_settings = project_settings
self.cloud_settings = cloud_settings
self.project_path = project_path
self.images_path = images_path
def run(self):
try:
@@ -234,7 +304,7 @@ class UploadProjectThread(QThread):
self.update.emit(10) # update progress to 10%
provider = get_provider(self.cloud_settings)
provider.upload_file(zipped_project_file, 'projects')
provider.upload_file(zipped_project_file, 'projects/' + os.path.basename(zipped_project_file))
self.update.emit(20) # update progress to 20%
@@ -242,22 +312,22 @@ class UploadProjectThread(QThread):
images = set([node.settings()["image"] for node in topology.nodes() if 'image' in node.settings()])
for i, image in enumerate(images):
provider.upload_file(image, 'images')
provider.upload_file(image, 'images/' + os.path.relpath(image, self.images_path))
self.update.emit(20 + (float(i) / len(images) * 80))
self.completed.emit()
except Exception as e:
log.exception("Error exporting project to cloud")
self.error.emit("Error exporting project {}".format(str(e)), True)
self.error.emit("Error exporting project: {}".format(str(e)), True)
def zip_project_dir(self):
"""
Zips project files
:return: path to zipped project file
"""
project_name = os.path.basename(self.project_settings["project_path"])
project_name = os.path.basename(self.project_path)
output_filename = os.path.join(tempfile.gettempdir(), project_name + ".zip")
project_dir = os.path.dirname(self.project_settings["project_path"])
project_dir = os.path.dirname(self.project_path)
relroot = os.path.abspath(os.path.join(project_dir, os.pardir))
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
for root, dirs, files in os.walk(project_dir):
@@ -281,3 +351,116 @@ class UploadProjectThread(QThread):
def stop(self):
self.quit()
class UploadFilesThread(QThread):
"""
Upload multiple files to cloud files
uploads - A list of 2-tuples of (local_src_path, remote_dst_path)
"""
completed = pyqtSignal()
def __init__(self, parent, cloud_settings, uploads):
super(QThread, self).__init__(parent)
self._cloud_settings = cloud_settings
self._uploads = uploads
def run(self):
for src, dst in self._uploads:
log.debug('Upload from {} to {}'.format(src, dst))
provider = get_provider(self._cloud_settings)
provider.upload_file(src, dst)
log.debug('Upload image completed')
self.completed.emit()
class DownloadProjectThread(QThread):
"""
Downloads project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super().__init__()
self.project_name = cloud_project_file_name
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
self.cloud_settings = cloud_settings
def run(self):
try:
self.update.emit(0)
provider = get_provider(self.cloud_settings)
zip_file = provider.download_file(self.project_name)
zip_file = zipfile.ZipFile(zip_file, mode='r')
zip_file.extractall(self.project_dest_path)
zip_file.close()
project_name = zip_file.namelist()[0].strip('/')
self.update.emit(20)
with open(os.path.join(self.project_dest_path, project_name, project_name + '.gns3'), 'r') as f:
project_settings = json.loads(f.read())
images = set()
for node in project_settings["topology"].get("nodes", []):
if "properties" in node and "image" in node["properties"]:
images.add(node["properties"]["image"])
image_names_in_cloud = provider.find_storage_image_names(images)
for i, image in enumerate(images):
dest_path = os.path.join(self.images_dest_path, *image_names_in_cloud[image].split('/')[1:])
if not os.path.exists(os.path.dirname(dest_path)):
os.makedirs(os.path.dirname(dest_path))
provider.download_file(image_names_in_cloud[image], dest_path)
self.update.emit(20 + (float(i) / len(images) * 80))
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(str(e)), True)
def stop(self):
self.quit()
class DeleteProjectThread(QThread):
"""
Deletes project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, project_file_name, cloud_settings):
super().__init__()
self.project_file_name = project_file_name
self.cloud_settings = cloud_settings
def run(self):
try:
provider = get_provider(self.cloud_settings)
provider.delete_file(self.project_file_name)
self.completed.emit()
except Exception as e:
log.exception("Error deleting project")
self.error.emit("Error deleting project: {}".format(str(e)), True)
def stop(self):
pass
def get_cloud_projects(cloud_settings):
provider = get_provider(cloud_settings)
return provider.list_projects()

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import ast
import logging
import os
from PyQt4.QtGui import QWidget
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QMenu
@@ -19,19 +20,20 @@ from .topology import Topology
# this widget was promoted on Creator, must use absolute imports
from gns3.ui.cloud_inspector_view_ui import Ui_CloudInspectorView
from gns3.cloud_instances import CloudInstances
log = logging.getLogger(__name__)
POLLING_TIMER = 10000 # in milliseconds
class RunningInstanceState():
class RunningInstanceState(NodeState):
"""
GNS3 states for running instances
"""
IDLE = 0
GNS3SERVER_STARTED = 1
WS_CONNECTED = 2
GNS3SERVER_STARTING = 10
GNS3SERVER_STARTED = 11
WS_CONNECTED = 12
class InstanceTableModel(QAbstractTableModel):
@@ -55,16 +57,18 @@ class InstanceTableModel(QAbstractTableModel):
self._ids = []
self.reset()
def _get_status_icon_path(self, state):
def _get_status_icon_path(self, instance):
"""
Return a string pointing to the graphic resource
"""
if state == NodeState.RUNNING:
if instance.state == RunningInstanceState.WS_CONNECTED:
return ':/icons/led_green.svg'
elif state in (NodeState.REBOOTING, NodeState.PENDING, NodeState.UNKNOWN):
return ':/icons/led_yellow.svg'
else:
elif instance.state in (RunningInstanceState.STOPPED,
RunningInstanceState.TERMINATED,
RunningInstanceState.UNKNOWN):
return ':/icons/led_red.svg'
else:
return ':/icons/led_yellow.svg'
def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
return len(self._instances)
@@ -79,7 +83,7 @@ class InstanceTableModel(QAbstractTableModel):
if role == Qt.DecorationRole:
if col == 1:
# status
return QIcon(self._get_status_icon_path(instance.state))
return QIcon(self._get_status_icon_path(instance))
elif role == Qt.DisplayRole:
if col == 0:
@@ -98,7 +102,13 @@ class InstanceTableModel(QAbstractTableModel):
return 'Unknown'
elif col == 3:
# devices
return 0
count = 0
topology = Topology.instance()
for node in topology.nodes():
id = node._server.instance_id or 0
if instance.id == id:
count += 1
return count
return None
def headerData(self, section, orientation, role=None):
@@ -156,7 +166,7 @@ class InstanceTableModel(QAbstractTableModel):
self.addInstance(instance)
def getInstanceById(self, instance_id):
return self._instances[instance_id]
return self._instances.get(instance_id, None)
class CloudInspectorView(QWidget, Ui_CloudInspectorView):
@@ -167,7 +177,7 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
instanceSelected(int) Emitted when users click and select an instance on the inspector.
Param int is the ID of the instance
"""
instanceSelected = pyqtSignal(int)
instanceSelected = pyqtSignal(str)
def __init__(self, parent):
super(QWidget, self).__init__(parent)
@@ -185,7 +195,7 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
self.uiInstancesTableView.horizontalHeader().setStretchLastSection(True)
# connections
self.uiInstancesTableView.customContextMenuRequested.connect(self._contextMenu)
self.uiInstancesTableView.selectionModel().currentRowChanged.connect(self._rowChanged)
self.uiInstancesTableView.clicked.connect(self._rowChanged)
self.uiCreateInstanceButton.clicked.connect(self._create_new_instance)
self._pollingTimer = QTimer(self)
@@ -194,8 +204,8 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
# map flavor ids to combobox indexes
self.flavor_index_id = []
# internal status for running instances
self._running_instances = {}
# TODO: Delete me
self._running = {}
def _get_flavor_index(self, flavor_id):
try:
@@ -211,15 +221,12 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
self._provider = main_win.cloudProvider
self._settings = main_win.cloudSettings()
log.info('CloudInspectorView.load')
# TODO: If a network error occurs in the first ListInstances call,
# the instance will *never* appear in the cloud inspector and will
# not get cleaned up when the gui exits. Fix this bug.
for i in instances:
self._project_instances_id.append(i["id"])
update_thread = ListInstancesThread(self, self._provider)
update_thread.instancesReady.connect(self._populate_model)
update_thread.instancesReady.connect(self._update_model)
update_thread.start()
self._pollingTimer.start(POLLING_TIMER)
# fill sizes comboboxes
@@ -269,13 +276,18 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
instance.name = 'Deleting...'
self._model.updateInstanceFields(instance, ['name',])
def _rowChanged(self, current, previous):
def _rowChanged(self, index):
"""
This slot is invoked every time users change the current selected row on the
inspector
"""
if current.isValid():
instance = self._model.getInstance(current.row())
selection = self.uiInstancesTableView.selectionModel().selection()
if selection.isEmpty():
return
item = selection.indexes()[0]
if item.isValid():
instance = self._model.getInstance(item.row())
self.instanceSelected.emit(instance.id)
def _polling_slot(self):
@@ -298,7 +310,11 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
:param host_ip: the host ip of the instance
:param start_response: the output of the server start script on the remote host
"""
self._running_instances[id] = RunningInstanceState.GNS3SERVER_STARTED
# instance state transition: GNS3SERVER_STARTING --> GNS3SERVER_STARTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.GNS3SERVER_STARTED
self._model.updateInstanceFields(instance, ['state'])
data = ast.literal_eval(start_response)
# TODO: have the server return the port it is running on
@@ -308,21 +324,37 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
# TODO: Store the cert file in an appropriate spot
ca_file = '/tmp/cloud_server_{}.crt'.format(host_ip)
open(ca_file, 'w').write(ssl_cert)
ca_filename = 'cloud_server_{}.crt'.format(host_ip)
# TODO: Move this directory into projectSettings.
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(id)
top_instance.set_later_attributes(host_ip, port, ssl_cert, ca_file)
ssh_pkey = top_instance.private_key
log.debug('Cloud server gns3server started.')
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file)
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file,
username, password, ssh_pkey, id)
wss_thread.established.connect(self._wss_connected_slot)
wss_thread.start()
def _wss_connected_slot(self, id):
"""
This slot is called when the WSConnectThread succesfully connected to
This slot is called when the WSConnectThread successfully connected to
the websocket on the remote host
"""
self._running_instances[id] = RunningInstanceState.WS_CONNECTED
# instance state transition: GNS3SERVER_STARTED --> WS_CONNECTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.WS_CONNECTED
self._model.updateInstanceFields(instance, ['state'])
def _get_public_ip(self, ip_list):
"""
@@ -341,10 +373,18 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
if not instances:
return
# filter instances for current project
# populate underlying model if this is the first call
if self._model.rowCount() == 0 and len(instances) > 0:
self._populate_model(instances)
instance_manager = CloudInstances.instance()
instance_manager.update_instances(instances)
# filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
self._model.updateInstanceFields(i, ['state'])
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
# cleanup removed instances
real = set(i.id for i in project_instances)
@@ -353,35 +393,26 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
self._model.removeInstanceById(i)
self.uiInstancesTableView.resizeColumnsToContents()
# handle state for running instances
topology = Topology.instance()
# start gns3server if needed
for i in project_instances:
if i.state != NodeState.RUNNING:
self._running_instances = {}
continue
# get the real instance state from self._model
model_instance = self._model.getInstanceById(i.id)
topology_instance = topology.getInstance(i.id)
if topology_instance is None:
continue
if model_instance.state == RunningInstanceState.RUNNING:
# instance state transition: RUNNING --> GNS3SERVER_STARTING
model_instance.state = RunningInstanceState.GNS3SERVER_STARTING
self._model.updateInstanceFields(model_instance, ['state'])
state = self._running_instances.setdefault(i.id, RunningInstanceState.IDLE)
if state == RunningInstanceState.IDLE:
public_ip = self._get_public_ip(i.public_ips)
# start GNS3 server and deadman switch
public_ip = self._get_public_ip(i.public_ips)
instance_manager.update_host_for_instance(i.id, public_ip)
topology_instance = instance_manager.get_instance(i.id)
ssh_thread = StartGNS3ServerThread(
self, public_ip, topology_instance.private_key, i.id,
self._provider.username, self._provider.api_key, self._provider.region,
1800)
ssh_thread.gns3server_started.connect(self._gns3server_started_slot)
ssh_thread.start()
elif state == RunningInstanceState.GNS3SERVER_STARTED:
# start WSS connection
# TODO: Don't think this is used, remove.
# wss_thread = WSConnectThread(self, i.id)
# wss_thread.established.connect(self._wss_connected_slot)
# wss_thread.start()
pass
def _populate_model(self, instances):
log.info('CloudInspectorView._populate_model')
@@ -403,6 +434,10 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
"then wait for the instance to appear in the inspector.")
if ok:
if not name.endswith("-gns3"):
name += "-gns3"
create_thread = CreateInstanceThread(self, self._provider, name, flavor_id, image_id)
create_thread.instanceCreated.connect(self._main_window.add_instance_to_project)
create_thread.instanceCreated.connect(CloudInstances.instance().add_instance)
create_thread.start()

145
gns3/cloud_instances.py Normal file
View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
"""
Keeps track of all cloud instances the app has started.
"""
from .qt import QtCore
from gns3.topology import TopologyInstance
import logging
log = logging.getLogger(__name__)
class CloudInstances(QtCore.QObject):
"""
This class stores the instances that gns3 gui has started. This can be different than the list
of instances in the topology that can be changed when switching projects. This list is not touched
when switching projects and is stored in the .ini file.
"""
def __init__(self, *args, **kwargs):
super(CloudInstances, self).__init__(*args, **kwargs)
self._instances = []
@staticmethod
def instance():
"""
Singleton to return only one instance of CloudInstances.
:returns: instance of CloudInstances
"""
if not hasattr(CloudInstances, "_instance"):
CloudInstances._instance = CloudInstances()
return CloudInstances._instance
@property
def instances(self):
return self._instances
def clear(self):
self._instances.clear()
def add(self, topology_instance):
self._instances.append(topology_instance)
def add_instance(self, instance, keypair):
if instance is None:
return
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
def update_instances(self, instances):
save_needed = False
# Look for instances that have been deleted
for static in self._instances:
found = False
for dynamic in instances:
if static.id == dynamic.id:
found = True
break
if not found:
self._instances.remove(static)
save_needed = True
if save_needed:
self.save()
def update_host_for_instance(self, instance_id, host):
for instance in self.instances:
if instance.id == instance_id:
if instance.host != host:
instance.host = host
self.save()
def save(self):
"""
Save the list of cloud instances to the config file
"""
log.debug('Saving cloud instances')
settings = QtCore.QSettings()
settings.beginGroup("CloudInstances")
settings.remove("")
# Save the instances
settings.beginWriteArray("cloud_instance", len(self._instances))
index = 0
for instance in self._instances:
settings.setArrayIndex(index)
for name in instance.fields():
value = getattr(instance, name) if not None else ""
log.debug('{}={}'.format(name, str(value)[0:60]))
settings.setValue(name, value)
index += 1
settings.endArray()
settings.endGroup()
def load(self):
"""
Load instance info from the config file to the topology
"""
log.debug('Loading cloud instances')
settings = QtCore.QSettings()
settings.beginGroup("CloudInstances")
# Load the instances
size = settings.beginReadArray("cloud_instance")
for index in range(0, size):
settings.setArrayIndex(index)
info = {}
for name in TopologyInstance.fields():
value = settings.value(name, "")
log.debug('{}={}'.format(name, str(value)[0:60]))
info[name] = value
ti = TopologyInstance(**info)
self._instances.append(ti)
def get_instance(self, instance_id):
"""
Retrieve a TopologyInstance objects if present
"""
for i in self._instances:
if i.id == instance_id:
return i
return None

View File

@@ -39,7 +39,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
self.uiWebView.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self.uiWebView.loadFinished.connect(self._loadFinishedSlot)
self.uiCheckBox.setChecked(QtCore.QSettings().value("GUI/show_getting_started_dialog", True, type=bool))
self.uiCheckBox.setChecked(QtCore.QSettings().value("GUI/hide_getting_started_dialog", False, type=bool))
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self._loadFinishedSlot)
self._timer.setSingleShot(True)
@@ -53,7 +53,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
:returns: boolean
"""
return self.uiCheckBox.isChecked()
return not self.uiCheckBox.isChecked()
def done(self, result):
"""
@@ -62,7 +62,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
:param result: ignored
"""
QtCore.QSettings().setValue("GUI/show_getting_started_dialog", self.uiCheckBox.isChecked())
QtCore.QSettings().setValue("GUI/hide_getting_started_dialog", self.uiCheckBox.isChecked())
QtGui.QDialog.done(self, result)
def _urlClickedSlot(self, url):
@@ -88,9 +88,15 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
if result is False:
# load a local resource if the page is not available
resource_name = os.path.join("static", "getting_started.html")
getting_started = None
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
getting_started = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
getting_started_page = pkg_resources.resource_filename("gns3", resource_name)
getting_started = os.path.normpath(getting_started_page)
self.uiWebView.load(QtCore.QUrl("file://{}".format(getting_started)))
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl("file://{}".format(getting_started)))
else:
self.uiCheckBox.setChecked(True)
self.accept()

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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 re
from ..qt import QtGui
from ..topology import Topology
from ..ui.idlepc_dialog_ui import Ui_IdlePCDialog
class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
"""
Idle-PC dialog.
"""
def __init__(self, router, idlepcs, parent):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applySlot)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Help).clicked.connect(self._helpSlot)
self._router = router
self._idlepcs = idlepcs
for value in self._idlepcs:
match = re.search(r"^(0x[0-9a-f]+)\s+\[(\d+)\]$", value)
if match:
idlepc = match.group(1)
count = int(match.group(2))
if 50 <= count <= 60:
value += "*"
self.uiComboBox.addItem(value, idlepc)
def _helpSlot(self):
"""
Shows the help for Idle-PC.
"""
help_text = "Finding the right idlepc value is a trial and error process, consisting of applying " \
"different Idle-PC values and monitoring the CPU usage.\n\nBest Idle-PC values are usually " \
"obtained when IOS is in idle state, the following message being displayed " \
"on the console: {} con0 is now available ... Press RETURN to get started.".format(self._router.name())
QtGui.QMessageBox.information(self, "Hints for Idle-PC", help_text)
def _applySlot(self):
"""
Applies an Idle-PC value.
"""
if not self.uiComboBox.count():
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry could not find a valid Idle-PC value, please check again with Cisco IOS in a different state")
return
idlepc = self.uiComboBox.itemData(self.uiComboBox.currentIndex())
# apply Idle-PC to all routers with the same IOS image
ios_image = self._router.settings()["image"]
for node in Topology.instance().nodes():
if hasattr(node, "idlepcs") and node.settings()["image"] == ios_image:
node.setIdlepc(idlepc)
def done(self, result):
"""
Called when the dialog is closed.
:param result: boolean (accepted or rejected)
"""
if result:
self._applySlot()
QtGui.QDialog.done(self, result)

View File

@@ -0,0 +1,68 @@
"""
Dialog for importing cloud projects
"""
from ..ui.import_cloud_project_dialog_ui import Ui_ImportCloudProjectDialog
from ..qt import QtGui
from ..cloud.utils import get_cloud_projects, DownloadProjectThread, DeleteProjectThread
from ..utils.progress_dialog import ProgressDialog
class ImportCloudProjectDialog(QtGui.QDialog, Ui_ImportCloudProjectDialog):
"""
Import cloud project dialog implementation.
"""
def __init__(self, parent, project_dest_path, images_dest_path, cloud_settings):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
self.cloud_settings = cloud_settings
self.uiImportProjectAction.clicked.connect(self._importProject)
self.uiDeleteProjectAction.clicked.connect(self._deleteProject)
self._listCloudProjects()
def _listCloudProjects(self):
self.listWidget.clear()
self.projects = get_cloud_projects(self.cloud_settings)
self.listWidget.addItems(list(self.projects.keys()))
def _importProject(self):
project_file_name = self.projects[self.listWidget.currentItem().text()]
download_thread = DownloadProjectThread(
project_file_name,
self.project_dest_path,
self.images_dest_path,
self.cloud_settings
)
progress_dialog = ProgressDialog(download_thread, "Importing project", "Downloading project files...", "Cancel",
parent=self.parent())
progress_dialog.show()
progress_dialog.exec_()
self.close()
def _deleteProject(self):
project_file_name = self.projects[self.listWidget.currentItem().text()]
button_clicked = QtGui.QMessageBox.question(
self,
"Delete project",
"Are you sure you want to delete project " + self.listWidget.currentItem().text(),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.Yes
)
if button_clicked == QtGui.QMessageBox.Yes:
delete_project_thread = DeleteProjectThread(project_file_name, self.cloud_settings)
progress_dialog = ProgressDialog(delete_project_thread, "Deleting project", "Deleting project files...",
"Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
self._listCloudProjects()

View File

@@ -19,14 +19,19 @@ import os
import shutil
from ..qt import QtCore, QtGui
from ..ui.new_project_dialog_ui import Ui_NewProjectDialog
from ..settings import ENABLE_CLOUD
class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
"""
New project dialog.
:param parent: parent widget.
:param showed_from_startup: boolean to indicate if this dialog
has been opened automatically when GNS3 started.
"""
def __init__(self, parent):
def __init__(self, parent, showed_from_startup=False):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
@@ -39,6 +44,14 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
self.uiNameLineEdit.textEdited.connect(self._projectNameSlot)
self.uiLocationBrowserToolButton.clicked.connect(self._projectPathSlot)
self.uiOpenProjectPushButton.clicked.connect(self._openProjectActionSlot)
self.uiRecentProjectsPushButton.clicked.connect(self._showRecentProjectsSlot)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
if not showed_from_startup:
self.uiOpenProjectPushButton.hide()
self.uiRecentProjectsPushButton.hide()
def keyPressEvent(self, e):
"""
@@ -68,6 +81,35 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
return self._project_settings
def _menuTriggeredSlot(self, action):
"""
Closes this dialog when a recent project
has been opened.
:param action: ignored.
"""
self.reject()
def _openProjectActionSlot(self):
"""
Opens a project and closes this dialog.
"""
self._main_window.openProjectActionSlot()
self.reject()
def _showRecentProjectsSlot(self):
"""
lot to show all the recent projects in a menu.
"""
menu = QtGui.QMenu()
menu.triggered.connect(self._menuTriggeredSlot)
for action in self._main_window._recent_file_actions:
menu.addAction(action)
menu.exec_(QtGui.QCursor.pos())
def done(self, result):
if result:

View File

@@ -26,6 +26,7 @@ from ..pages.general_preferences_page import GeneralPreferencesPage
from ..pages.cloud_preferences_page import CloudPreferencesPage
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
from ..modules import MODULES
from ..settings import ENABLE_CLOUD
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
@@ -58,8 +59,9 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
GeneralPreferencesPage,
ServerPreferencesPage,
PacketCapturePreferencesPage,
#CloudPreferencesPage,
]
if ENABLE_CLOUD:
pages.append(CloudPreferencesPage)
for page in pages:
preferences_page = page()
@@ -87,6 +89,9 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
if cls is preference_pages[0]:
parent = item
# expand all items by default
self.uiTreeWidget.expandAll()
def _showPreferencesPageSlot(self, current, previous):
"""
Shows a preference page in the current dialog.

View File

@@ -19,6 +19,7 @@
Graphical view on the scene where items are drawn.
"""
import logging
import os
import pickle
@@ -37,6 +38,7 @@ from .ports.port import Port
from .dialogs.style_editor_dialog import StyleEditorDialog
from .dialogs.text_editor_dialog import TextEditorDialog
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
from .dialogs.idlepc_dialog import IdlePCDialog
from .utils.connect_to_server import ConnectToServer
# link items
@@ -51,6 +53,8 @@ from .items.rectangle_item import RectangleItem
from .items.ellipse_item import EllipseItem
from .items.image_item import ImageItem
log = logging.getLogger(__name__)
class GraphicsView(QtGui.QGraphicsView):
"""
@@ -604,16 +608,8 @@ class GraphicsView(QtGui.QGraphicsView):
item = self.itemAt(event.pos())
if not self._adding_link and isinstance(item, NodeItem) and item.node().initialized():
item.setSelected(True)
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
node = item.node()
name = node.name()
console_port = node.console()
console_host = node.server().host
try:
from .telnet_console import telnetConsole
telnetConsole(name, console_host, console_port)
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
if isinstance(item, NodeItem):
self.consoleToNode(item.node())
else:
self.configureSlot()
else:
@@ -743,7 +739,7 @@ class GraphicsView(QtGui.QGraphicsView):
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "start"), items)):
start_action = QtGui.QAction("Start", menu)
start_action.setIcon(QtGui.QIcon(':/icons/play.svg'))
start_action.setIcon(QtGui.QIcon(':/icons/start.svg'))
start_action.triggered.connect(self.startActionSlot)
menu.addAction(start_action)
@@ -870,26 +866,68 @@ class GraphicsView(QtGui.QGraphicsView):
dialog.show()
dialog.exec_()
def consoleToNode(self, node):
"""
Start a console application to connect to a node.
:param node: Node instance
:returns: False if the console application could not be started
"""
if not hasattr(node, "console") or not node.initialized() or node.status() != Node.started:
# returns True to ignore this node.
return True
if hasattr(node, "serialConsole") and node.serialConsole():
try:
from .serial_console import serialConsole
serialConsole(node.name())
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
return False
else:
name = node.name()
console_port = node.console()
console_host = node.server().host
try:
from .telnet_console import telnetConsole
telnet_callback = None
try:
if node.server().isCloud():
# override target host with localhost
console_host = '127.0.0.1'
ep = node.server().tunnel.add_endpoint(console_host, console_port)
# override target port with local tunneled port
local_addr, _ = ep.get()
console_port = local_addr[1]
def cb(*args, **kwargs):
node.server().tunnel.remove_endpoint(ep)
log.debug('Console DONE on port {}'.format(args[2]))
telnet_callback = cb
except AttributeError:
pass
telnetConsole(name, console_host, console_port, telnet_callback)
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
return False
return True
def consoleActionSlot(self):
"""
Slot to receive events from the console action in the
contextual menu.
"""
from .telnet_console import telnetConsole
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized():
node = item.node()
if node.status() != Node.started:
if isinstance(item, NodeItem):
if self.consoleToNode(item.node()):
continue
name = node.name()
console_port = node.console()
console_host = node.server().host
try:
telnetConsole(name, console_host, console_port)
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
break
def captureActionSlot(self):
"""
@@ -948,7 +986,7 @@ class GraphicsView(QtGui.QGraphicsView):
def _showIdlepcError(self, node_id, code, message):
"""
Shows an error message if the idle-pc values cannot be computed.
Shows an error message if the Idle-PC values cannot be computed.
"""
self._idlepc_progress_dialog.reject()
@@ -968,17 +1006,11 @@ class GraphicsView(QtGui.QGraphicsView):
router.server_error_signal.disconnect(self._showIdlepcError)
idlepcs = router.idlepcs()
if idlepcs and idlepcs[0] != "0x0":
idlepc, ok = QtGui.QInputDialog.getItem(self, "Idle-PC values", "Please select an idle-pc value", idlepcs, 0, False)
if ok:
idlepc = idlepc.split()[0]
# apply idle-pc to all routers with the same IOS image
ios_image = router.settings()["image"]
for node in self._topology.nodes():
if hasattr(node, "idlepcs") and node.settings()["image"] == ios_image:
node.setIdlepc(idlepc)
dialog = IdlePCDialog(router, idlepcs, parent=self)
dialog.show()
dialog.exec_()
else:
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no idle-pc values could be computed, please check again with Cisco IOS in a different state")
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
def duplicateActionSlot(self):
"""
@@ -1095,6 +1127,7 @@ class GraphicsView(QtGui.QGraphicsView):
"""
try:
log.debug('In createNode')
node_module = None
for module in MODULES:
instance = module.instance()
@@ -1106,10 +1139,17 @@ class GraphicsView(QtGui.QGraphicsView):
if not node_module:
raise ModuleError("Could not find any module for {}".format(node_class))
if node_data["server"] == "local":
if not "server" in node_data:
server = node_module.allocateServer(node_class)
elif node_data["server"] == "local":
server = Servers.instance().localServer()
elif node_data["server"] == "cloud":
server = Servers.instance().anyCloudServer()
else:
host, port = node_data["server"].rsplit(":", 1)
try:
host, port = node_data["server"].rsplit(":", 1)
except ValueError:
raise ModuleError("Wrong format for server: '{}', please recreate the node in preferences".format(node_data["server"]))
server = Servers.instance().getRemoteServer(host, port)
if not server.connected() and ConnectToServer(self, server) is False:
return

View File

@@ -224,7 +224,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
when a the node has been updated.
"""
self._node_label.setPlainText(self._node.name())
if self._node_label:
self._node_label.setPlainText(self._node.name())
self.setUnsavedState()
# update the link tooltips in case the

View File

@@ -293,7 +293,7 @@ class ShapeItem:
border_color.setAlpha(border_transparency)
pen.setColor(border_color)
if border_width is not None:
pen.setWidth(border_width)
pen.setWidth(int(border_width))
if border_style:
pen.setStyle(QtCore.Qt.PenStyle(border_style))
self.setPen(pen)

View File

@@ -94,7 +94,7 @@ def main():
lines = traceback.format_exception(exception, value, tb)
print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
print("\nPLEASE REPORT ON http://forum.gns3.net/development-f14.html OR http://github.com/GNS3/gns3-gui/issues\n")
print("\nPLEASE REPORT ON https://community.gns3.com/community/support/bug\n")
print("".join(lines))
try:
curdate = time.strftime("%d %b %Y %H:%M:%S")
@@ -184,7 +184,7 @@ def main():
app.setApplicationVersion(__version__)
# save client logging info to a file
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "GNS3_client.log")
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "GNS3_client.log") # FIXME: does it work?
try:
try:
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
@@ -194,7 +194,11 @@ def main():
if options.debug:
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_handler = root_logger.handlers[0]
if len(root_logger.handlers) > 0:
root_handler = root_logger.handlers[0]
else:
root_handler = logging.StreamHandler()
root_logger.addHandler(root_handler)
root_handler.setLevel(logging.DEBUG)
else:
handler.setLevel(logging.INFO)

View File

@@ -19,6 +19,8 @@
Main window for the GUI.
"""
import traceback
import sys
import os
import platform
import time
@@ -28,6 +30,7 @@ import shutil
import json
import glob
import logging
import functools
from pkg_resources import parse_version
@@ -41,7 +44,8 @@ from .dialogs.about_dialog import AboutDialog
from .dialogs.new_project_dialog import NewProjectDialog
from .dialogs.preferences_dialog import PreferencesDialog
from .dialogs.snapshots_dialog import SnapshotsDialog
from .settings import GENERAL_SETTINGS, GENERAL_SETTING_TYPES, CLOUD_SETTINGS, CLOUD_SETTINGS_TYPES
from .dialogs.import_cloud_project_dialog import ImportCloudProjectDialog
from .settings import GENERAL_SETTINGS, GENERAL_SETTING_TYPES, CLOUD_SETTINGS, CLOUD_SETTINGS_TYPES, ENABLE_CLOUD
from .utils.progress_dialog import ProgressDialog
from .utils.process_files_thread import ProcessFilesThread
from .utils.wait_for_connection_thread import WaitForConnectionThread
@@ -54,8 +58,10 @@ from .items.shape_item import ShapeItem
from .items.image_item import ImageItem
from .items.note_item import NoteItem
from .topology import Topology, TopologyInstance
from .cloud.utils import get_provider, UploadProjectThread
from .cloud.utils import UploadProjectThread
from .cloud.rackspace_ctrl import get_provider
from .cloud.exceptions import KeyPairExists
from .cloud_instances import CloudInstances
log = logging.getLogger(__name__)
@@ -105,18 +111,26 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
try:
from .news_dock_widget import NewsDockWidget
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.RightDockWidgetArea), NewsDockWidget(self))
self._uiNewsDockWidget = NewsDockWidget(self)
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.RightDockWidgetArea), self._uiNewsDockWidget)
except ImportError:
pass
self._uiNewsDockWidget = None
# restore the geometry and state of the main window.
settings = QtCore.QSettings()
self.restoreGeometry(settings.value("GUI/geometry", QtCore.QByteArray()))
self.restoreState(settings.value("GUI/state", QtCore.QByteArray()))
# do not show the nodes dock widget my default
self.uiNodesDockWidget.setVisible(False)
if not ENABLE_CLOUD:
self.uiNodesDockWidget.setVisible(False)
# populate the view -> docks menu
self.uiDocksMenu.addAction(self.uiTopologySummaryDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiConsoleDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiNodesDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiCloudInspectorDockWidget.toggleViewAction())
if ENABLE_CLOUD:
self.uiDocksMenu.addAction(self.uiCloudInspectorDockWidget.toggleViewAction())
# set the images directory
self.uiGraphicsView.updateImageFilesDir(self.imagesDirPath())
@@ -125,7 +139,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
for i in range(0, self._max_recent_files):
action = QtGui.QAction(self.uiFileMenu)
action.setVisible(False)
action.triggered.connect(self._openRecentFileSlot)
action.triggered.connect(self.openRecentFileSlot)
self._recent_file_actions.append(action)
self.uiFileMenu.insertActions(self.uiQuitAction, self._recent_file_actions)
self._recent_file_actions_separator = self.uiFileMenu.insertSeparator(self.uiQuitAction)
@@ -133,13 +147,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self._updateRecentFileActions()
self._cloud_provider = None
CloudInstances.instance().clear()
CloudInstances.instance().load()
# set the window icon
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico"))
#FIXME: hide the cloud dock for release
self.uiCloudInspectorDockWidget.hide()
# Network Manager (used to check for update)
self._network_manager = QtNetwork.QNetworkAccessManager(self)
@@ -157,12 +170,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Loads the settings from the persistent settings file.
"""
# restore the geometry and state of the main window.
settings = QtCore.QSettings()
self.restoreGeometry(settings.value("GUI/geometry", QtCore.QByteArray()))
self.restoreState(settings.value("GUI/state", QtCore.QByteArray()))
# restore the general settings
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in GENERAL_SETTINGS.items():
self._settings[name] = settings.value(name, value, type=GENERAL_SETTING_TYPES[name])
@@ -174,6 +183,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self._cloud_settings[name] = settings.value(name, value, type=CLOUD_SETTINGS_TYPES[name])
settings.endGroup()
# restore the style
self._setStyle(self._settings["style"])
# restore packet capture settings
Port.loadPacketCaptureSettings()
@@ -215,6 +227,11 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if new_settings.get("images_path", '') != self.imagesDirPath():
self.uiGraphicsView.updateImageFilesDir(self.imagesDirPath())
style = new_settings.get("style")
if style and new_settings["style"] != self._settings["style"]:
if not self._setStyle(style):
self._setLegacyStyle()
# save the settings
self._settings.update(new_settings)
settings = QtCore.QSettings()
@@ -251,10 +268,11 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# file menu connections
self.uiNewProjectAction.triggered.connect(self._newProjectActionSlot)
self.uiOpenProjectAction.triggered.connect(self._openProjectActionSlot)
self.uiOpenProjectAction.triggered.connect(self.openProjectActionSlot)
self.uiSaveProjectAction.triggered.connect(self._saveProjectActionSlot)
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
self.uiExportProjectAction.triggered.connect(self._exportProjectActionSlot)
#self.uiImportProjectAction.triggered.connect(self._importProjectActionSlot)
self.uiImportExportConfigsAction.triggered.connect(self._importExportConfigsActionSlot)
self.uiScreenshotAction.triggered.connect(self._screenshotActionSlot)
self.uiSnapshotAction.triggered.connect(self._snapshotActionSlot)
@@ -274,10 +292,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.uiShowNamesAction.triggered.connect(self._showNamesActionSlot)
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
# style menu connections
self.uiDefaultStyleAction.triggered.connect(self._defaultStyleActionSlot)
self.uiEnergySavingStyleAction.triggered.connect(self._energySavingStyleActionSlot)
# control menu connections
self.uiStartAllAction.triggered.connect(self._startAllActionSlot)
self.uiSuspendAllAction.triggered.connect(self._suspendAllActionSlot)
@@ -318,6 +332,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.project_about_to_close_signal.connect(self.shutdown_cloud_instances)
self.project_new_signal.connect(self.project_created)
# cloud inspector
self.CloudInspectorView.instanceSelected.connect(self._cloud_instance_selected)
def telnetConsoleCommand(self):
"""
Returns the Telnet console command line.
@@ -327,6 +344,15 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
return self._settings["telnet_console_command"]
def serialConsoleCommand(self):
"""
Returns the Serial console command line.
:returns: command (string)
"""
return self._settings["serial_console_command"]
def setUnsavedState(self):
"""
Sets the project in a unsaved state.
@@ -345,6 +371,33 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self._ignore_unsaved_state = value
def _createNewProject(self, new_project_settings):
"""
Crates a new project.
:param new_project_settings: project settings (dict)
"""
self.uiGraphicsView.reset()
# create the destination directory for project files
try:
os.makedirs(new_project_settings["project_files_dir"])
except FileExistsError:
pass
except OSError as e:
QtGui.QMessageBox.critical(self, "New project", "Could not create project files directory {}: {}".format(new_project_settings["project_files_dir"]), str(e))
return
# let all modules know about the new project files directory
self.uiGraphicsView.updateProjectFilesDir(new_project_settings["project_files_dir"])
topology = Topology.instance()
for instance in CloudInstances.instance().instances:
topology.addInstance2(instance)
self._project_settings.update(new_project_settings)
self.saveProject(new_project_settings["project_path"])
def _newProjectActionSlot(self):
"""
Slot called to create a new project.
@@ -358,48 +411,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.project_about_to_close_signal.emit(self._project_settings["project_path"])
if create_new_project:
self.uiGraphicsView.reset()
new_project_settings = project_dialog.getNewProjectSettings()
# create the destination directory for project files
try:
os.makedirs(new_project_settings["project_files_dir"])
except FileExistsError:
pass
except OSError as e:
QtGui.QMessageBox.critical(self, "New project", "Could not create project files directory {}: {}".format(new_project_settings["project_files_dir"]), str(e))
return
# let all modules know about the new project files directory
self.uiGraphicsView.updateProjectFilesDir(new_project_settings["project_files_dir"])
if new_project_settings["project_type"] == "cloud":
provider = self.cloudProvider
if provider is None:
log.error("Unable to get a cloud provider")
return
# create an instance for this project
default_flavor = self.cloudSettings()['default_flavor']
default_image_id = self.cloudSettings()['default_image']
instance, keypair = self._create_instance(new_project_settings["project_name"],
default_flavor,
default_image_id)
if instance:
topology = Topology.instance()
topology.addInstance(instance.name, instance.id,
default_flavor, default_image_id,
keypair.private_key, keypair.public_key)
self._project_settings.update(new_project_settings)
self.saveProject(new_project_settings["project_path"])
self._createNewProject(new_project_settings)
else:
self._createTemporaryProject()
self.project_new_signal.emit(self._project_settings["project_path"])
def _openProjectActionSlot(self):
def openProjectActionSlot(self):
"""
Slot called to open a project.
"""
@@ -407,14 +426,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self,
"Open project",
self.projectsDirPath(),
"All files (*.*);;GNS3 project files (*.gns3)",
"All files (*.*);;GNS3 project files (*.gns3);;NET files (*.net)",
"GNS3 project files (*.gns3)")
if path and self.checkForUnsavedChanges():
self.project_about_to_close_signal.emit(self._project_settings["project_path"])
if self.loadProject(path):
self.project_new_signal.emit(path)
def _openRecentFileSlot(self):
def openRecentFileSlot(self):
"""
Slot called to open recent file from the File menu.
"""
@@ -633,22 +652,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if isinstance(item, LinkItem):
item.adjust()
def _defaultStyleActionSlot(self):
"""
Slot called to set the default style.
"""
self.setStyleSheet("")
self.uiEnergySavingStyleAction.setChecked(False)
def _energySavingStyleActionSlot(self):
"""
Slot called to set the energy saving style.
"""
self.setStyleSheet("QMainWindow {} QMenuBar { background: black; } QDockWidget { background: black; color: white; } QToolBar { background: black; } QFrame { background: gray; } QToolButton { width: 30px; height: 30px; /*border:solid 1px black opacity 0.4;*/ /*background-none;*/ } QStatusBar { /* background-image: url(:/pictures/pictures/texture_blackgrid.png);*/ background: black; color: rgb(255,255,255); }")
self.uiDefaultStyleAction.setChecked(False)
def _startAllActionSlot(self):
"""
Slot called when starting all the nodes.
@@ -706,20 +709,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Slot called when connecting to all the nodes using the console.
"""
from .telnet_console import telnetConsole
delay = self._settings["delay_console_all"]
counter = 0
for item in self.uiGraphicsView.scene().items():
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized():
node = item.node()
if node.status() != Node.started:
continue
name = node.name()
console_port = node.console()
console_host = node.server().host
try:
telnetConsole(name, console_host, console_port)
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", 'could not start console: {}'.format(e))
break
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
callback = functools.partial(self.uiGraphicsView.consoleToNode, item.node())
QtCore.QTimer.singleShot(counter, callback)
counter += delay
def _addNoteActionSlot(self):
"""
@@ -838,8 +834,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
return
dialog = GettingStartedDialog(self)
dialog.showit()
if auto is True and dialog.showit():
if auto is True and dialog.showit() is False:
return
dialog.show()
dialog.exec_()
@@ -937,24 +932,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
if not self.uiAddLinkAction.isChecked():
self.uiAddLinkAction.setText("Add a link")
link_icon = QtGui.QIcon()
link_icon.addPixmap(QtGui.QPixmap(":/icons/connection-new.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
link_icon.addPixmap(QtGui.QPixmap(":/icons/connection-new-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
self.uiAddLinkAction.setIcon(link_icon)
self.adding_link_signal.emit(False)
else:
#TODO: handle automatic linking based on the link type
# modifiers = QtGui.QApplication.keyboardModifiers()
# if not globals.GApp.systconf['general'].manual_connection or modifiers == QtCore.Qt.ShiftModifier:
# menu = QtGui.QMenu()
# for linktype in globals.linkTypes.keys():
# menu.addAction(linktype)
# menu.connect(menu, QtCore.SIGNAL("triggered(QAction *)"), self.__setLinkType)
# menu.exec_(QtGui.QCursor.pos())
# else:
# globals.currentLinkType = globals.Enum.LinkType.Manual
self.uiAddLinkAction.setText("Cancel")
self.uiAddLinkAction.setIcon(QtGui.QIcon(':/icons/cancel-connection.svg'))
self.adding_link_signal.emit(True)
def _preferencesActionSlot(self):
@@ -1040,9 +1020,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
Called by QTimer.singleShot to load everything needed at startup.
"""
if self._uiNewsDockWidget and not self._uiNewsDockWidget.isVisible():
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.RightDockWidgetArea), self._uiNewsDockWidget)
self._gettingStartedActionSlot(auto=True)
self._createTemporaryProject()
self._newProjectActionSlot()
# connect to the local server
servers = Servers.instance()
@@ -1108,6 +1089,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
port=server.port,
error=e))
self._createTemporaryProject()
if self._settings["auto_launch_project_dialog"]:
project_dialog = NewProjectDialog(self, showed_from_startup=True)
project_dialog.show()
create_new_project = project_dialog.exec_()
if create_new_project:
new_project_settings = project_dialog.getNewProjectSettings()
self._createNewProject(new_project_settings)
self.project_new_signal.emit(self._project_settings["project_path"])
if self._settings["check_for_update"]:
# automatic check for update every week (604800 seconds)
current_epoch = int(time.mktime(time.localtime()))
@@ -1241,6 +1232,53 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self._setCurrentFile(path)
return True
def _convertOldProject(self, path):
"""
Converts old ini-style GNS3 topologies (<=0.8.7) to the newer version 1+ JSON format.
:param path: path to the project file.
"""
try:
from gns3converter.main import do_conversion, get_snapshots, ConvertError
except ImportError:
QtGui.QMessageBox.critical(self, "GNS3 converter", "Please install gns3-converter in order to open old ini-style GNS3 projects")
return
try:
project_name = os.path.basename(os.path.dirname(path))
project_dir = os.path.join(self._settings["projects_path"], project_name)
while os.path.isdir(project_dir):
text, ok = QtGui.QInputDialog.getText(self,
"GNS3 converter",
"Project '{}' already exists. Please choose an alternative project name:".format(project_name),
text=project_name + "2")
if ok:
project_name = text
project_dir = os.path.join(self._settings["projects_path"], project_name)
else:
return
for snapshot_def in get_snapshots(path):
do_conversion(snapshot_def, project_name, project_dir, quiet=True)
topology_def = {'file': path, 'snapshot': False}
do_conversion(topology_def, project_name, project_dir, quiet=True)
except ConvertError as e:
QtGui.QMessageBox.critical(self, "GNS3 converter", "Could not convert {}: {}".format(path, e))
return
except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
tb = "".join(lines)
MessageBox(self, "GNS3 converter", "Unexpected exception while converting {}".format(path), details=tb)
return
QtGui.QMessageBox.information(self, "GNS3 converter", "Your project has been converted to a new format and can be found in: {}".format(project_dir))
project_path = os.path.join(project_dir, project_name + ".gns3")
self.loadProject(project_path)
def loadProject(self, path):
"""
Loads a project into GNS3.
@@ -1251,6 +1289,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.uiGraphicsView.reset()
topology = Topology.instance()
try:
extension = os.path.splitext(path)[1]
if extension == ".net":
self._convertOldProject(path)
return
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
with open(path, "r") as f:
need_to_save = False
@@ -1271,25 +1315,24 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# if we're opening a cloud project, fire up instances
if json_topology["resources_type"] == "cloud":
self._project_settings["project_type"] = "cloud"
provider = self.cloudProvider
new_instances = []
for instance in json_topology["topology"]["instances"]:
name = instance["name"]
flavor = instance["size_id"]
image = instance["image_id"]
i, k = self._create_instance(name, flavor, image)
new_instances.append({
"name": i.name,
"id": i.id,
"size_id": flavor,
"image_id": image,
"private_key": k.private_key,
"public_key": k.public_key
})
# update topology with new image data
json_topology["topology"]["instances"] = new_instances
# we need to save the updates
need_to_save = True
# new_instances = []
# for instance in json_topology["topology"]["instances"]:
# name = instance["name"]
# flavor = instance["size_id"]
# image = instance["image_id"]
# i, k = self._create_instance(name, flavor, image)
# new_instances.append({
# "name": i.name,
# "id": i.id,
# "size_id": flavor,
# "image_id": image,
# "private_key": k.private_key,
# "public_key": k.public_key
# })
# # update topology with new image data
# json_topology["topology"]["instances"] = new_instances
# # we need to save the updates
# need_to_save = True
else:
self._project_settings["project_type"] = "local"
@@ -1486,25 +1529,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# do nothing if previous project was temporary
return
with open(project) as f:
old_json_topology = json.load(f)
if old_json_topology["resources_type"] != 'cloud':
# do nothing in case of local projects
return
provider = self.cloudProvider
if provider is None:
log.error("Unable to get a cloud provider")
return
for instance in old_json_topology["topology"]["instances"]:
# shutdown the instance, we can pass to libcloud our namedtuple instead of a Node
# object because only instance.id is actually accessed
ti = TopologyInstance(**instance)
self.cloudProvider.delete_instance(ti)
# delete keypairs
self.cloudProvider.delete_key_pair_by_name(instance["name"])
CloudInstances.instance().save()
def project_created(self, project):
"""
@@ -1590,7 +1615,187 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"Cannot export temporary projects, please save current project first.")
return
upload_thread = UploadProjectThread(self._project_settings, self.cloudSettings())
progress_dialog = ProgressDialog(upload_thread, "Exporting Project", "Uploading project flies...", "Cancel", parent=self)
upload_thread = UploadProjectThread(
self._cloud_settings,
self._project_settings['project_path'],
self._settings['images_path']
)
progress_dialog = ProgressDialog(upload_thread, "Exporting Project", "Uploading project files...", "Cancel",
parent=self)
progress_dialog.show()
progress_dialog.exec_()
def _importProjectActionSlot(self):
dialog = ImportCloudProjectDialog(
self,
self._settings['projects_path'],
self._settings['images_path'],
self._cloud_settings
)
dialog.show()
dialog.exec_()
def _cloud_instance_selected(self, instance_id):
"""
Clear selection, then select all the nodes on the graphics view
running on the instance_id instance
"""
self.uiGraphicsView.scene().clearSelection()
for item in self.uiGraphicsView.scene().items():
if isinstance(item, NodeItem):
if item.node()._server.instance_id == instance_id:
item.setSelected(True)
def _setStyle(self, style):
if style == "Charcoal (default)":
self._setCharcoalStyle()
return True
elif style == "Classic":
self._setClassicStyle()
return True
return False
def _getStyleIcon(self, normal_file, active_file):
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(normal_file), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(active_file), QtGui.QIcon.Active, QtGui.QIcon.Off)
return icon
def _setLegacyStyle(self):
"""
Sets the legacy GUI style.
"""
self.setStyleSheet("")
self.uiNewProjectAction.setIcon(QtGui.QIcon(":/icons/new-project.svg"))
self.uiOpenProjectAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
self.uiSaveProjectAction.setIcon(QtGui.QIcon(":/icons/save.svg"))
self.uiSaveProjectAsAction.setIcon(QtGui.QIcon(":/icons/save-as.svg"))
self.uiImportExportConfigsAction.setIcon(QtGui.QIcon(":/icons/import_export_configs.svg"))
self.uiScreenshotAction.setIcon(QtGui.QIcon(":/icons/camera-photo.svg"))
self.uiSnapshotAction.setIcon(QtGui.QIcon(":/icons/snapshot.svg"))
self.uiQuitAction.setIcon(QtGui.QIcon(":/icons/quit.svg"))
self.uiPreferencesAction.setIcon(QtGui.QIcon(":/icons/applications.svg"))
self.uiZoomInAction.setIcon(QtGui.QIcon(":/icons/zoom-in.png"))
self.uiZoomOutAction.setIcon(QtGui.QIcon(":/icons/zoom-out.png"))
self.uiShowPortNamesAction.setIcon(QtGui.QIcon(":/icons/show-interface-names.svg"))
self.uiStartAllAction.setIcon(self._getStyleIcon(":/icons/start.svg", ":/icons/start-hover.svg"))
self.uiSuspendAllAction.setIcon(self._getStyleIcon(":/icons/pause.svg", ":/icons/pause-hover.svg"))
self.uiStopAllAction.setIcon(self._getStyleIcon(":/icons/stop.svg", ":/icons/stop-hover.svg"))
self.uiReloadAllAction.setIcon(QtGui.QIcon(":/icons/reload.svg"))
self.uiAuxConsoleAllAction.setIcon(QtGui.QIcon(":/icons/aux-console.svg"))
self.uiConsoleAllAction.setIcon(QtGui.QIcon(":/icons/console.svg"))
self.uiAddNoteAction.setIcon(QtGui.QIcon(":/icons/add-note.svg"))
self.uiInsertImageAction.setIcon(QtGui.QIcon(":/icons/image.svg"))
self.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/icons/rectangle.svg", ":/icons/rectangle-hover.svg"))
self.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/icons/ellipse.svg", ":/icons/ellipse-hover.svg"))
self.uiOnlineHelpAction.setIcon(QtGui.QIcon(":/icons/help.svg"))
self.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/icons/router.png", ":/icons/router-hover.png"))
self.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/icons/switch.png", ":/icons/switch-hover.png"))
self.uiBrowseEndDevicesAction.setIcon(self._getStyleIcon(":/icons/PC.png", ":/icons/PC-hover.png"))
self.uiBrowseSecurityDevicesAction.setIcon(self._getStyleIcon(":/icons/firewall.png", ":/icons/firewall-hover.png"))
self.uiBrowseAllDevicesAction.setIcon(self._getStyleIcon(":/icons/browse-all-icons.png", ":/icons/browse-all-icons-hover.png"))
self.uiAddLinkAction.setIcon(self._getStyleIcon(":/icons/connection-new.svg", ":/charcoal_icons/connection-new-hover.svg"))
def _setClassicStyle(self):
"""
Sets the classic GUI style.
"""
self.setStyleSheet("")
self.uiNewProjectAction.setIcon(self._getStyleIcon(":/classic_icons/new-project.svg", ":/classic_icons/new-project-hover.svg"))
self.uiOpenProjectAction.setIcon(self._getStyleIcon(":/classic_icons/open.svg", ":/classic_icons/open-hover.svg"))
self.uiSaveProjectAction.setIcon(self._getStyleIcon(":/classic_icons/save-project.svg", ":/classic_icons/save-project-hover.svg"))
self.uiSaveProjectAsAction.setIcon(self._getStyleIcon(":/classic_icons/save-as-project.svg", ":/classic_icons/save-as-project-hover.svg"))
self.uiImportExportConfigsAction.setIcon(self._getStyleIcon(":/classic_icons/import_export_configs.svg", ":/classic_icons/import_export_configs-hover.svg"))
self.uiScreenshotAction.setIcon(self._getStyleIcon(":/classic_icons/camera-photo.svg", ":/classic_icons/camera-photo-hover.svg"))
self.uiSnapshotAction.setIcon(self._getStyleIcon(":/classic_icons/snapshot.svg", ":/classic_icons/snapshot-hover.svg"))
self.uiQuitAction.setIcon(self._getStyleIcon(":/classic_icons/quit.svg", ":/classic_icons/quit-hover.svg"))
self.uiPreferencesAction.setIcon(self._getStyleIcon(":/classic_icons/preferences.svg", ":/classic_icons/preferences-hover.svg"))
self.uiZoomInAction.setIcon(self._getStyleIcon(":/classic_icons/zoom-in.svg", ":/classic_icons/zoom-in-hover.svg"))
self.uiZoomOutAction.setIcon(self._getStyleIcon(":/classic_icons/zoom-out.svg", ":/classic_icons/zoom-out-hover.svg"))
self.uiShowPortNamesAction.setIcon(self._getStyleIcon(":/classic_icons/show-interface-names.svg", ":/classic_icons/show-interface-names-hover.svg"))
self.uiStartAllAction.setIcon(self._getStyleIcon(":/classic_icons/start.svg", ":/classic_icons/start-hover.svg"))
self.uiSuspendAllAction.setIcon(self._getStyleIcon(":/classic_icons/pause.svg", ":/classic_icons/pause-hover.svg"))
self.uiStopAllAction.setIcon(self._getStyleIcon(":/classic_icons/stop.svg", ":/classic_icons/stop-hover.svg"))
self.uiReloadAllAction.setIcon(self._getStyleIcon(":/classic_icons/reload.svg", ":/classic_icons/reload-hover.svg"))
self.uiAuxConsoleAllAction.setIcon(self._getStyleIcon(":/classic_icons/aux-console.svg", ":/classic_icons/aux-console-hover.svg"))
self.uiConsoleAllAction.setIcon(self._getStyleIcon(":/classic_icons/console.svg", ":/classic_icons/console-hover.svg"))
self.uiAddNoteAction.setIcon(self._getStyleIcon(":/classic_icons/add-note.svg", ":/classic_icons/add-note-hover.svg"))
self.uiInsertImageAction.setIcon(self._getStyleIcon(":/classic_icons/image.svg", ":/classic_icons/image-hover.svg"))
self.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/classic_icons/rectangle.svg", ":/classic_icons/rectangle-hover.svg"))
self.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/classic_icons/ellipse.svg", ":/classic_icons/ellipse-hover.svg"))
self.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/classic_icons/help.svg", ":/classic_icons/help-hover.svg"))
self.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/classic_icons/router.svg", ":/classic_icons/router-hover.svg"))
self.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/classic_icons/switch.svg", ":/classic_icons/switch-hover.svg"))
self.uiBrowseEndDevicesAction.setIcon(self._getStyleIcon(":/classic_icons/pc.svg", ":/classic_icons/pc-hover.svg"))
self.uiBrowseSecurityDevicesAction.setIcon(self._getStyleIcon(":/classic_icons/firewall.svg", ":/classic_icons/firewall-hover.svg"))
self.uiBrowseAllDevicesAction.setIcon(self._getStyleIcon(":/classic_icons/browse-all-icons.svg", ":/classic_icons/browse-all-icons-hover.svg"))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/classic_icons/add-link.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(":/classic_icons/add-link-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(":/classic_icons/add-link-cancel.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
self.uiAddLinkAction.setIcon(icon)
def _setCharcoalStyle(self):
"""
Sets the charcoal GUI style.
"""
style = """QWidget {background-color: #535353}
QToolBar {border:0px}
QGraphicsView, QTextEdit, QPlainTextEdit, QTreeWidget, QLineEdit, QSpinBox, QComboBox {background-color: #dedede}
QDockWidget, QMenuBar, QPushButton, QToolButton, QTabWidget {color: #dedede; font: bold 11px}
QLabel, QMenu, QStatusBar, QRadioButton, QCheckBox {color: #dedede}
QMenuBar::item {background-color: #535353}
QMenu::item:selected {color: white; background-color: #5f5f5f}
QToolButton:hover {background-color: #5f5f5f}
QGroupBox {color: #dedede; font: bold 12px; padding: 15px; border-style: none}
QAbstractScrollArea::corner { background: #535353}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: none}
QComboBox {selection-color: black; selection-background-color: #dedede}
"""
if sys.platform.startswith("darwin"):
style += "QDockWidget::title {text-align: center; background-color: #535353}"
self.setStyleSheet(style)
self.uiNewProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/new-project.svg", ":/charcoal_icons/new-project-hover.svg"))
self.uiOpenProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/open.svg", ":/charcoal_icons/open-hover.svg"))
self.uiSaveProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/save-project.svg", ":/charcoal_icons/save-project-hover.svg"))
self.uiSaveProjectAsAction.setIcon(self._getStyleIcon(":/charcoal_icons/save-as-project.svg", ":/charcoal_icons/save-as-project-hover.svg"))
self.uiImportExportConfigsAction.setIcon(self._getStyleIcon(":/charcoal_icons/import_export_configs.svg", ":/charcoal_icons/import_export_configs-hover.svg"))
self.uiScreenshotAction.setIcon(self._getStyleIcon(":/charcoal_icons/camera-photo.svg", ":/charcoal_icons/camera-photo-hover.svg"))
self.uiSnapshotAction.setIcon(self._getStyleIcon(":/charcoal_icons/snapshot.svg", ":/charcoal_icons/snapshot-hover.svg"))
self.uiQuitAction.setIcon(self._getStyleIcon(":/charcoal_icons/quit.svg", ":/charcoal_icons/quit-hover.svg"))
self.uiPreferencesAction.setIcon(self._getStyleIcon(":/charcoal_icons/preferences.svg", ":/charcoal_icons/preferences-hover.svg"))
self.uiZoomInAction.setIcon(self._getStyleIcon(":/charcoal_icons/zoom-in.svg", ":/charcoal_icons/zoom-in-hover.svg"))
self.uiZoomOutAction.setIcon(self._getStyleIcon(":/charcoal_icons/zoom-out.svg", ":/charcoal_icons/zoom-out-hover.svg"))
self.uiShowPortNamesAction.setIcon(self._getStyleIcon(":/charcoal_icons/show-interface-names.svg", ":/charcoal_icons/show-interface-names-hover.svg"))
self.uiStartAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/start.svg", ":/charcoal_icons/start-hover.svg"))
self.uiSuspendAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/pause.svg", ":/charcoal_icons/pause-hover.svg"))
self.uiStopAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/stop.svg", ":/charcoal_icons/stop-hover.svg"))
self.uiReloadAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/reload.svg", ":/charcoal_icons/reload-hover.svg"))
self.uiAuxConsoleAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/aux-console.svg", ":/charcoal_icons/aux-console-hover.svg"))
self.uiConsoleAllAction.setIcon(self._getStyleIcon(":/charcoal_icons/console.svg", ":/charcoal_icons/console-hover.svg"))
self.uiAddNoteAction.setIcon(self._getStyleIcon(":/charcoal_icons/add-note.svg", ":/charcoal_icons/add-note-hover.svg"))
self.uiInsertImageAction.setIcon(self._getStyleIcon(":/charcoal_icons/image.svg", ":/charcoal_icons/image-hover.svg"))
self.uiDrawRectangleAction.setIcon(self._getStyleIcon(":/charcoal_icons/rectangle.svg", ":/charcoal_icons/rectangle-hover.svg"))
self.uiDrawEllipseAction.setIcon(self._getStyleIcon(":/charcoal_icons/ellipse.svg", ":/charcoal_icons/ellipse-hover.svg"))
self.uiOnlineHelpAction.setIcon(self._getStyleIcon(":/charcoal_icons/help.svg", ":/charcoal_icons/help-hover.svg"))
self.uiBrowseRoutersAction.setIcon(self._getStyleIcon(":/charcoal_icons/router.svg", ":/charcoal_icons/router-hover.svg"))
self.uiBrowseSwitchesAction.setIcon(self._getStyleIcon(":/charcoal_icons/switch.svg", ":/charcoal_icons/switch-hover.svg"))
self.uiBrowseEndDevicesAction.setIcon(self._getStyleIcon(":/charcoal_icons/pc.svg", ":/charcoal_icons/pc-hover.svg"))
self.uiBrowseSecurityDevicesAction.setIcon(self._getStyleIcon(":/charcoal_icons/firewall.svg", ":/charcoal_icons/firewall-hover.svg"))
self.uiBrowseAllDevicesAction.setIcon(self._getStyleIcon(":/charcoal_icons/browse-all-icons.svg", ":/charcoal_icons/browse-all-icons-hover.svg"))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/charcoal_icons/add-link-1.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(":/charcoal_icons/add-link-1-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(":/charcoal_icons/add-link-1-cancel.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
self.uiAddLinkAction.setIcon(icon)

View File

@@ -25,6 +25,7 @@ from gns3.servers import Servers
from ..module import Module
from ..module_error import ModuleError
from .cloud import Cloud
from .host import Host
import logging
@@ -241,7 +242,7 @@ class Builtin(Module):
:returns: list of classes
"""
return [Cloud]
return [Cloud, Host]
def nodes(self):
"""

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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 gns3.node import Node
from .cloud import Cloud
import logging
log = logging.getLogger(__name__)
class Host(Cloud):
"""
Pseudo host based on a Dynamips Cloud.
:param module: parent module for this node
:param server: GNS3 server instance
"""
_name_instance_count = 1
def __init__(self, module, server):
Cloud.__init__(self, module, server)
log.info("host is being created")
# create an unique id and name
self._name_id = Host._name_instance_count
Host._name_instance_count += 1
name = "Host {}".format(self._name_id)
self._settings["name"] = name
self.created_signal.connect(self._autoConfigure)
def _autoConfigure(self, node_id):
"""
Auto adds all Ethernet and TAP interfaces.
:param node_id: ignored
"""
new_settings = {"nios": []}
for interface in self._settings["interfaces"]:
if interface["name"].startswith("tap"):
new_settings["nios"].append("nio_tap:{}".format(interface["name"]))
else:
new_settings["nios"].append("nio_gen_eth:{}".format(interface["name"]))
self.update(new_settings)
@staticmethod
def defaultSymbol():
"""
Returns the default symbol path for this host.
:returns: symbol path (or resource).
"""
return ":/symbols/computer.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when the host is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/computer.selected.svg"
@staticmethod
def symbolName():
return "Host"
@staticmethod
def categories():
"""
Returns the node categories the node is part of (used by the device panel).
:returns: list of node category (integer)
"""
return [Node.end_devices]
def __str__(self):
return "Host"

View File

@@ -474,6 +474,9 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiGenericEthernetComboBox.clear()
index = 0
for interface in settings["interfaces"]:
if interface["name"].startswith("tap"):
# do not add TAP interfaces
continue
self.uiGenericEthernetComboBox.addItem(interface["name"])
self.uiGenericEthernetComboBox.setItemData(index, interface["id"], QtCore.Qt.ToolTipRole)
index += 1
@@ -483,7 +486,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiLinuxEthernetComboBox.clear()
index = 0
for interface in settings["interfaces"]:
if not interface["name"].startswith(r"\Device\NPF_"):
if not interface["name"].startswith(r"\Device\NPF_") and not interface["name"].startswith("tap"):
self.uiLinuxEthernetComboBox.addItem(interface["name"])
self.uiLinuxEthernetComboBox.setItemData(index, interface["id"], QtCore.Qt.ToolTipRole)
index += 1

View File

@@ -41,7 +41,9 @@ from .nodes.ethernet_switch import EthernetSwitch
from .nodes.ethernet_hub import EthernetHub
from .nodes.frame_relay_switch import FrameRelaySwitch
from .nodes.atm_switch import ATMSwitch
from .settings import DYNAMIPS_SETTINGS, DYNAMIPS_SETTING_TYPES, PLATFORMS_DEFAULT_RAM
from .settings import DYNAMIPS_SETTINGS, DYNAMIPS_SETTING_TYPES
from .settings import IOS_ROUTER_SETTINGS, IOS_ROUTER_SETTING_TYPES
from .settings import PLATFORMS_DEFAULT_RAM
PLATFORM_TO_CLASS = {
"c1700": C1700,
@@ -111,54 +113,18 @@ class Dynamips(Module):
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
#FIXME: this is ugly
# load the IOS images
size = settings.beginReadArray("ios_router")
for index in range(0, size):
settings.setArrayIndex(index)
name = settings.value("name", "")
path = settings.value("path", "")
image = settings.value("image", "")
startup_config = settings.value("startup_config", "")
private_config = settings.value("private_config", "")
default_symbol = settings.value("default_symbol", ":/symbols/router.normal.svg")
hover_symbol = settings.value("hover_symbol", ":/symbols/router.selected.svg")
category = settings.value("category", Node.routers, type=int)
platform = settings.value("platform", "")
chassis = settings.value("chassis", "")
idlepc = settings.value("idlepc", "")
mac_addr = settings.value("mac_addr", "")
ram = settings.value("ram", 128, type=int)
nvram = settings.value("nvram", 256, type=int)
disk0 = settings.value("disk0", 16, type=int)
disk1 = settings.value("disk1", 0, type=int)
confreg = settings.value("confreg", "0x2102")
system_id = settings.value("system_id", "FTX0945W0MY")
server = settings.value("server", "local")
name = settings.value("name")
server = settings.value("server")
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers:
if key in self._ios_routers or not name or not server:
continue
self._ios_routers[key] = {"name": name,
"path": path,
"image": image,
"default_symbol": default_symbol,
"hover_symbol": hover_symbol,
"category": category,
"startup_config": startup_config,
"private_config": private_config,
"platform": platform,
"chassis": chassis,
"idlepc": idlepc,
"ram": ram,
"nvram": nvram,
"mac_addr": mac_addr,
"disk0": disk0,
"disk1": disk1,
"confreg": confreg,
"system_id": system_id,
"server": server}
self._ios_routers[key] = {}
for setting_name, default_value in IOS_ROUTER_SETTINGS.items():
self._ios_routers[key][setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
@@ -170,10 +136,13 @@ class Dynamips(Module):
if settings.contains(wic):
self._ios_routers[key][wic] = settings.value(wic, "")
platform = self._ios_routers[key]["platform"]
chassis = self._ios_routers[key]["chassis"]
if platform == "c7200":
self._ios_routers[key]["midplane"] = settings.value("midplane", "vxr")
self._ios_routers[key]["npe"] = settings.value("npe", "npe-400")
self._ios_routers[key]["slot0"] = settings.value("slot0", "C7200-IO-2FE")
self._ios_routers[key]["slot0"] = settings.value("slot0", "C7200-IO-FE")
else:
self._ios_routers[key]["iomem"] = 5
@@ -364,6 +333,8 @@ class Dynamips(Module):
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
@@ -386,12 +357,42 @@ class Dynamips(Module):
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
params.update({"project_name": project_name})
server.send_notification("dynamips.settings", params)
def allocateServer(self, node_class, use_cloud=False):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (WebSocketClient instance)
"""
# allocate a server for the node
servers = Servers.instance()
if use_cloud:
from ...topology import Topology
topology = Topology.instance()
top_instance = topology.anyInstance()
server = servers.getCloudServer(top_instance.host, top_instance.port, top_instance.ssl_ca_file)
else:
if self._settings["use_local_server"]:
# use the local server
server = servers.localServer()
else:
# pick up a remote server (round-robin method)
server = next(iter(servers))
if not server:
raise ModuleError("No remote server is configured")
return server
def createNode(self, node_class, server):
"""
Creates a new node.
@@ -433,35 +434,49 @@ class Dynamips(Module):
for ios_key, info in self._ios_routers.items():
if node_name == info["name"]:
ios_router = self._ios_routers[ios_key]
break
# hack for EtherSwitch router
if isinstance(node, EtherSwitchRouter) and node.server() == Servers.instance().localServer():
for info in self._ios_routers.values():
if info["platform"] == "c3725" and info["server"] == "local":
ios_router = {
"platform": "c3725",
"path": info["path"],
"ram": info["ram"],
"startup_config": info["startup_config"],
}
break
if not ios_router:
raise ModuleError("Please create an c3725 IOS router in order to use an EtherSwitch router")
if not ios_router:
raise ModuleError("No IOS image found for platform {}".format(node.settings()["platform"]))
raise ModuleError("No IOS router for platform {}".format(node.settings()["platform"]))
name = ios_router["name"]
settings = {}
# set initial settings like the chassis or an idle-pc value etc.
if ios_router["chassis"]:
# set initial settings like the chassis or an Idle-PC value etc.
if "chassis" in ios_router and ios_router["chassis"]:
settings["chassis"] = ios_router["chassis"]
if ios_router["idlepc"]:
if "idlepc" in ios_router and ios_router["idlepc"]:
settings["idlepc"] = ios_router["idlepc"]
if ios_router["startup_config"]:
settings["startup_config"] = ios_router["startup_config"]
if ios_router["private_config"]:
if "private_config" in ios_router and ios_router["private_config"]:
settings["private_config"] = ios_router["private_config"]
if ios_router["platform"] == "c7200":
settings["midplane"] = ios_router["midplane"]
settings["npe"] = ios_router["npe"]
else:
elif "iomem" in ios_router:
settings["iomem"] = ios_router["iomem"]
if ios_router["nvram"]:
if "nvram" in ios_router and ios_router["nvram"]:
settings["nvram"] = ios_router["nvram"]
if ios_router["disk0"]:
if "disk0" in ios_router and ios_router["disk0"]:
settings["disk0"] = ios_router["disk0"]
if ios_router["disk1"]:
if "disk1" in ios_router and ios_router["disk1"]:
settings["disk1"] = ios_router["disk1"]
for slot_id in range(0, 7):
@@ -473,16 +488,20 @@ class Dynamips(Module):
if wic in ios_router:
settings[wic] = ios_router[wic]
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
if node.server().isCloud():
settings["cloud_path"] = "images/IOS"
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
else:
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
else:
node.setup()
def updateImageIdlepc(self, image_path, idlepc):
"""
Updates the idle-pc for an IOS image.
Updates the Idle-PC for an IOS image.
:param image_path: path to the IOS image
:param idlepc: idle-pc value
:param idlepc: Idle-PC value
"""
for ios_router in self._ios_routers.values():

View File

@@ -19,14 +19,16 @@
Wizard for IOS routers.
"""
import sys
import os
import re
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.utils.message_box import MessageBox
from gns3.dialogs.exec_command_dialog import ExecCommandDialog
from gns3.utils.run_in_terminal import RunInTerminal
from ....settings import ENABLE_CLOUD
from ..ui.ios_router_wizard_ui import Ui_IOSRouterWizard
from ..settings import PLATFORMS_DEFAULT_RAM, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
from .. import Dynamips
@@ -54,14 +56,18 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
Wizard to create an IOS router.
:param parent: parent widget
:param ios_routers: existing IOS routers
"""
def __init__(self, parent):
def __init__(self, ios_routers, parent):
QtGui.QWizard.__init__(self, parent)
self.setupUi(self)
self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/router.normal.svg"))
self.setWizardStyle(QtGui.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtGui.QWizard.NoDefaultButton)
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
@@ -71,6 +77,9 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self.uiPlatformComboBox.currentIndexChanged[str].connect(self._platformChangedSlot)
self.uiPlatformComboBox.addItems(list(PLATFORMS_DEFAULT_RAM.keys()))
#FIXME: hide because of issue on Windows.
self.uiTestIOSImagePushButton.hide()
# Mandatory fields
self.uiNamePlatformWizardPage.registerField("name*", self.uiNameLineEdit)
self.uiIOSImageWizardPage.registerField("image*", self.uiIOSImageLineEdit)
@@ -87,12 +96,16 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
1: self.uiWic1comboBox,
2: self.uiWic2comboBox}
self.uiTestIOSImagePushButton.hide() # hide it because it doesn't work
self._ios_routers = ios_routers
if Dynamips.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
"""
Slot for when the remote server radio button is toggled.
@@ -129,14 +142,25 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self.uiChassisComboBox.addItems(CHASSIS[platform])
def _testIOSImageSlot(self):
"""
Slot to locally test the IOS image.
"""
platform = self.uiPlatformComboBox.currentText()
ram = self.uiRamSpinBox.value()
ios_image = self.uiIOSImageLineEdit.text()
params = ["-P", platform[1:], "-r", str(ram), ios_image]
dialog = ExecCommandDialog(self, "/usr/bin/dynamips", params)
dialog.show()
dialog.exec_()
dynamips = os.path.realpath(Dynamips.instance().settings()["path"])
if not os.path.exists(dynamips):
QtGui.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
return
command = '"{path}" -P {platform} -r {ram} "{ios_image}"'.format(path=dynamips,
platform=platform[1:],
ram=ram,
ios_image=ios_image)
try:
RunInTerminal(command)
except OSError as e:
QtGui.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
def _idlePCFinderSlot(self):
"""
@@ -156,13 +180,13 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
def createdSlot(self, node_id):
"""
The node for the auto idle-pc has been created.
The node for the auto Idle-PC has been created.
:param node_id: not used
"""
self._router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
self._auto_idlepc_progress_dialog = QtGui.QProgressDialog("Searching for an idle-pc value...", "Cancel", 0, 0, parent=self)
self._auto_idlepc_progress_dialog = QtGui.QProgressDialog("Searching for an Idle-PC value...", "Cancel", 0, 0, parent=self)
self._auto_idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._auto_idlepc_progress_dialog.setWindowTitle("Idle-PC finder")
self._auto_idlepc_progress_dialog.show()
@@ -187,7 +211,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self.uiIdlepcLineEdit.setText(result["idlepc"])
else:
logs = "\n".join(result["logs"])
MessageBox(self, "Idle-PC finder", "Could not find an idle-pc value", details=logs)
MessageBox(self, "Idle-PC finder", "Could not find an Idle-PC value", details=logs)
def _iosImageBrowserSlot(self):
"""
@@ -203,7 +227,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
# try to guess the platform
image = os.path.basename(path)
match = re.match("^(c[0-9]+)\\-\w+", image)
match = re.match("^(c[0-9]+)\\-\w+", image.lower())
if not match:
QtGui.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return
@@ -298,28 +322,19 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
chassis = ""
self._populateAdapters(platform, chassis)
if platform == "c7200":
self.uiSlot0comboBox.setCurrentIndex(self.uiSlot0comboBox.findText("C7200-IO-2FE"))
self.uiSlot0comboBox.setCurrentIndex(self.uiSlot0comboBox.findText("C7200-IO-FE"))
def validateCurrentPage(self):
"""
Validates the IOS name.
"""
if self.currentPage() == self.uiServerWizardPage:
#FIXME: prevent users to use "cloud"
if self.uiCloudRadioButton.isChecked():
QtGui.QMessageBox.critical(self, "Cloud", "Sorry not implemented yet!")
return False
# if self.currentPage() == self.uiNameImageWizardPage:
# name = self.uiNameLineEdit.text()
# if not re.search(r"""^[\-\w]+$""", name):
# # 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.
# QtGui.QMessageBox.critical(self, "Name", "Invalid name detected: {}".format(name))
# return False
if self.currentPage() == self.uiNamePlatformWizardPage:
name = self.uiNameLineEdit.text()
for ios_router in self._ios_routers.values():
if ios_router["name"] == name:
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
return True
@@ -347,14 +362,17 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
path = self.uiIOSImageLineEdit.text()
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
else:
server = self.uiRemoteServersComboBox.currentText()
elif self.uiRemoteRadioButton.isChecked():
if self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
else:
server = self.uiRemoteServersComboBox.currentText()
else: # Cloud is selected
server = "cloud"
settings = {
"name": self.uiNameLineEdit.text(),

View File

@@ -337,7 +337,7 @@ class ATMSwitch(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break

View File

@@ -326,7 +326,7 @@ class EthernetHub(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break

View File

@@ -337,7 +337,7 @@ class EthernetSwitch(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break

View File

@@ -35,7 +35,7 @@ class EtherSwitchRouter(Router):
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3745")
Router.__init__(self, module, server, platform="c3725")
self._platform_settings = {"ram": 128,
"nvram": 304,

View File

@@ -336,7 +336,7 @@ class FrameRelaySwitch(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break

View File

@@ -262,6 +262,8 @@ class Router(Node):
params["mac_addr"] = self._settings["mac_addr"] = initial_settings.pop("mac_addr")
if "chassis" in initial_settings:
params["chassis"] = self._settings["chassis"] = initial_settings.pop("chassis")
if "cloud_path" in initial_settings:
params["cloud_path"] = self._settings["cloud_path"] = initial_settings.pop("cloud_path")
# other initial settings will be applied when the router has been created
if initial_settings:
@@ -566,7 +568,7 @@ class Router(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
@@ -606,10 +608,10 @@ class Router(Node):
def computeIdlepcs(self):
"""
Get idle-pc proposals
Get Idle-PC proposals
"""
log.debug("{} is requesting idle-pc proposals".format(self.name()))
log.debug("{} is requesting Idle-PC proposals".format(self.name()))
self._server.send_message("dynamips.vm.idlepcs", {"id": self._router_id}, self._computeIdlepcsCallback)
def _computeIdlepcsCallback(self, result, error=False):
@@ -621,10 +623,10 @@ class Router(Node):
"""
if error:
log.error("error while computing idle-pc proposals {}: {}".format(self.name(), result["message"]))
log.error("error while computing Idle-PC proposals {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
log.info("{} has received idle-pc proposals".format(self.name()))
log.info("{} has received Idle-PC proposals".format(self.name()))
self._idlepcs = result["idlepcs"]
self.idlepc_signal.emit()
@@ -635,21 +637,21 @@ class Router(Node):
:param callback: Callback for the response
"""
log.debug("{} is starting an auto idle-pc lookup".format(self.name()))
log.debug("{} is starting an auto Idle-PC lookup".format(self.name()))
self._server.send_message("dynamips.vm.auto_idlepc", {"id": self._router_id}, callback)
def idlepcs(self):
"""
Returns previously computed idle-pc values.
Returns previously computed Idle-PC values.
:returns: idle-pc values (list)
:returns: Idle-PC values (list)
"""
return self._idlepcs
def idlepc(self):
"""
Returns the current idle-pc value for this router.
Returns the current Idle-PC value for this router.
:returns: idlepc value (string)
"""
@@ -658,7 +660,7 @@ class Router(Node):
def setIdlepc(self, idlepc):
"""
Sets a new idle-pc value for this router.
Sets a new Idle-PC value for this router.
:param idlepc: idlepc value (string)
"""
@@ -864,7 +866,7 @@ class Router(Node):
if self._settings["jit_sharing_group"] != None:
jitsharing_group_info = "JIT blocks sharing group is {group}".format(group=self._settings["jit_sharing_group"])
# get info about idle-pc
# get info about Idle-PC
idlepc_info = "with no idlepc value"
if self._settings["idlepc"]:
idlepc_info = "with idlepc value of {idlepc}, idlemax of {idlemax} and idlesleep of {idlesleep} ms".format(idlepc=self._settings["idlepc"],
@@ -931,14 +933,14 @@ class Router(Node):
for port in self._ports:
ports.append(port.dump())
# make the IOU path relative
# make the IOS path relative
image_path = router["properties"]["image"]
if self.server().isLocal():
if os.path.commonprefix([image_path, self._module.imageFilesDir()]) == self._module.imageFilesDir():
# save only the image name if it is stored the images directory
router["properties"]["image"] = os.path.basename(image_path)
else:
router["properties"]["image"] = os.path.basename(image_path)
router["properties"]["image"] = image_path
return router

View File

@@ -333,7 +333,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiExecAreaLabel.hide()
self.uiExecAreaSpinBox.hide()
# load the idle-pc setting
# load the Idle-PC setting
self.uiIdlepcLineEdit.setText(settings["idlepc"])
if "idlemax" in settings:
@@ -518,7 +518,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
# save the exec area setting
settings["exec_area"] = self.uiExecAreaSpinBox.value()
# save the idle-pc setting
# save the Idle-PC setting
# TODO: check the format?
settings["idlepc"] = self.uiIdlepcLineEdit.text()

View File

@@ -28,15 +28,16 @@ import math
import zipfile
from gns3.qt import QtCore, QtGui
from gns3.node import Node
from gns3.main_window import MainWindow
from gns3.utils.progress_dialog import ProgressDialog
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.cloud.utils import UploadFilesThread
from .. import Dynamips
from ..settings import IOS_ROUTER_SETTINGS
from ..utils.decompress_ios import isIOSCompressed
from ..utils.decompress_ios_thread import DecompressIOSThread
from .. import Dynamips
from ..ui.ios_router_preferences_page_ui import Ui_IOSRouterPreferencesPageWidget
from ..pages.ios_router_configuration_page import IOSRouterConfigurationPage
from ..dialogs.ios_router_wizard import IOSRouterWizard
@@ -86,7 +87,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
Creates a new IOS router.
"""
wizard = IOSRouterWizard(parent=self)
wizard = IOSRouterWizard(self._ios_routers, parent=self)
wizard.show()
if wizard.exec_():
@@ -109,25 +110,34 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
ios_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
private_config = os.path.normpath(ios_base_config_path)
self._ios_routers[key] = {"name": ios_settings["name"],
"path": ios_settings["path"],
"image": ios_settings["image"],
"default_symbol": ":/symbols/router.normal.svg",
"hover_symbol": ":/symbols/router.selected.svg",
"category": Node.routers,
"startup_config": startup_config,
"private_config": private_config,
"platform": ios_settings["platform"],
"chassis": ios_settings["chassis"],
"idlepc": ios_settings["idlepc"],
"ram": ios_settings["ram"],
"nvram": 256,
"mac_addr": "",
"disk0": 1,
"disk1": 0,
"confreg": "0x2102",
"system_id": "FTX0945W0MY",
"server": ios_settings["server"]}
ios_settings["startup_config"] = startup_config
ios_settings["private_config"] = private_config
self._ios_routers[key] = IOS_ROUTER_SETTINGS.copy()
self._ios_routers[key].update(ios_settings)
if ios_settings["server"] == 'cloud':
import logging
log = logging.getLogger(__name__)
log.debug(ios_settings["image"])
# Start uploading the image to cloud files
self._upload_image_progress_dialog = QtGui.QProgressDialog("Uploading image file {}".format(ios_settings['image']), "Cancel", 0, 0, parent=self)
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
self._upload_image_progress_dialog.show()
try:
src = self._ios_routers[key]['path']
# Eg: images/IOS/c3745.img
dst = 'images/IOS/{}'.format(self._ios_routers[key]['image'])
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), [(src, dst)])
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:
self._upload_image_progress_dialog.reject()
log.error(e)
QtGui.QMessageBox.critical(self, "IOS image upload", "Error uploading IOS image: {}".format(e))
if ios_settings["platform"] == "c7200":
self._ios_routers[key]["midplane"] = "vxr"
@@ -153,6 +163,11 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._items.append(item)
self.uiIOSRoutersTreeWidget.setCurrentItem(item)
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():
return
self._upload_image_progress_dialog.accept()
def _iosRouterEditSlot(self):
"""
Edits an IOS router.
@@ -166,11 +181,16 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
dialog.show()
if dialog.exec_():
if ios_router["name"] != item.text(0):
if "{}:{}".format(ios_router["server"], ios_router["name"]) in self._ios_routers:
# FIXME: bug when changing name
QtGui.QMessageBox.critical(self, "New IOS router", "IOS router name {} already exists".format(ios_router["name"]))
new_key = "{server}:{name}".format(server=ios_router["server"], name=ios_router["name"])
if new_key in self._ios_routers:
QtGui.QMessageBox.critical(self, "IOS router", "IOS router name {} already exists for server {}".format(ios_router["name"],
ios_router["server"]))
ios_router["name"] = item.text(0)
return
self._ios_routers[new_key] = self._ios_routers[key]
del self._ios_routers[key]
item.setText(0, ios_router["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(ios_router)
def _iosRouterDeleteSlot(self):
@@ -183,6 +203,10 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
key = item.data(0, QtCore.Qt.UserRole)
del self._ios_routers[key]
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
if self._ios_routers == {}:
self.uiEditIOSRouterPushButton.setEnabled(False)
self.uiDeleteIOSRouterPushButton.setEnabled(False)
self.uiDecompressIOSPushButton.setEnabled(False)
@staticmethod
def getIOSImage(parent):

View File

@@ -19,9 +19,10 @@
Default Dynamips settings.
"""
from gns3.node import Node
import sys
import os
from collections import OrderedDict
# default path to Dynamips executable
@@ -81,6 +82,60 @@ DYNAMIPS_SETTING_TYPES = {
"mmap_support": bool,
}
IOS_ROUTER_SETTINGS = {
"name": "",
"path": "",
"image": "",
"default_symbol": ":/symbols/router.normal.svg",
"hover_symbol": ":/symbols/router.selected.svg",
"category": Node.routers,
"startup_config": "",
"private_config": "",
"platform": "",
"chassis": "",
"idlepc": "",
"idlemax": 500,
"idlesleep": 30,
"exec_area": 64,
"mmap": True,
"sparsemem": True,
"ram": 128,
"nvram": 256,
"mac_addr": "",
"disk0": 1,
"disk1": 0,
"confreg": "0x2102",
"system_id": "FTX0945W0MY",
"server": "local"
}
IOS_ROUTER_SETTING_TYPES = {
"name": str,
"path": str,
"image": str,
"default_symbol": str,
"hover_symbol": str,
"category": int,
"startup_config": str,
"private_config": str,
"platform": str,
"chassis": str,
"idlepc": str,
"idlemax": int,
"idlesleep": int,
"exec_area": int,
"mmap": bool,
"sparsemem": bool,
"ram": int,
"nvram": int,
"mac_addr": str,
"disk0": int,
"disk1": int,
"confreg": str,
"system_id": str,
"server": str
}
# supported platforms with the default RAM value
PLATFORMS_DEFAULT_RAM = {"c1700": 64,
"c2600": 64,

View File

@@ -30,7 +30,7 @@
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/jseutter/projects/gns3-gui/gns3/modules/dynamips/ui/ios_router_preferences_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_preferences_page.ui'
#
# Created: Tue Oct 14 11:35:46 2014
# Created: Wed Nov 19 18:57:20 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -37,7 +37,7 @@ class Ui_IOSRouterPreferencesPageWidget(object):
self.uiIOSRoutersTreeWidget.setSizePolicy(sizePolicy)
self.uiIOSRoutersTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(12)
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiIOSRoutersTreeWidget.setFont(font)

View File

@@ -2,9 +2,6 @@
<ui version="4.0">
<class>IOSRouterWizard</class>
<widget class="QWizard" name="IOSRouterWizard">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
@@ -16,6 +13,9 @@
<property name="windowTitle">
<string>New IOS router</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
#
# Created: Sat Oct 18 20:33:37 2014
# Created: Wed Oct 22 16:46:37 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,8 +26,8 @@ except AttributeError:
class Ui_IOSRouterWizard(object):
def setupUi(self, IOSRouterWizard):
IOSRouterWizard.setObjectName(_fromUtf8("IOSRouterWizard"))
IOSRouterWizard.setWindowModality(QtCore.Qt.WindowModal)
IOSRouterWizard.resize(517, 398)
IOSRouterWizard.setModal(True)
self.uiServerWizardPage = QtGui.QWizardPage()
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
self.verticalLayout = QtGui.QVBoxLayout(self.uiServerWizardPage)
@@ -320,7 +320,7 @@ class Ui_IOSRouterWizard(object):
self.uiWic1Label.setText(_translate("IOSRouterWizard", "wic 1:", None))
self.uiWic2Label.setText(_translate("IOSRouterWizard", "wic 2:", None))
self.uiIdlePCWizardPage.setTitle(_translate("IOSRouterWizard", "Idle-PC", None))
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An idle-pc value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An Idle-PC value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
self.uiIdlepcLabel.setText(_translate("IOSRouterWizard", "Idle-PC:", None))
self.uiIdlePCFinderPushButton.setText(_translate("IOSRouterWizard", "Idle-PC finder", None))

View File

@@ -29,6 +29,7 @@ from ..module import Module
from ..module_error import ModuleError
from .iou_device import IOUDevice
from .settings import IOU_SETTINGS, IOU_SETTING_TYPES
from .settings import IOU_DEVICE_SETTINGS, IOU_DEVICE_SETTING_TYPES
import logging
log = logging.getLogger(__name__)
@@ -91,33 +92,14 @@ class IOU(Module):
size = settings.beginReadArray("iou_device")
for index in range(0, size):
settings.setArrayIndex(index)
name = settings.value("name", "IOU{}".format(index + 1))
default_symbol = settings.value("default_symbol", ":/symbols/multilayer_switch.normal.svg")
hover_symbol = settings.value("hover_symbol", ":/symbols/multilayer_switch.selected.svg")
category = settings.value("category", Node.routers, type=int)
path = settings.value("path", "")
image = settings.value("image", "")
initial_config = settings.value("initial_config", "")
use_default_iou_values = settings.value("use_default_iou_values", True, type=bool)
ram = settings.value("ram", 256, type=int)
nvram = settings.value("nvram", 128, type=int)
ethernet_adapters = settings.value("ethernet_adapters", 2, type=int)
serial_adapters = settings.value("serial_adapters", 2, type=int)
server = settings.value("server", "local")
name = settings.value("name")
server = settings.value("server")
key = "{server}:{name}".format(server=server, name=name)
self._iou_devices[key] = {"name": name,
"path": path,
"default_symbol": default_symbol,
"hover_symbol": hover_symbol,
"category": category,
"image": image,
"initial_config": initial_config,
"use_default_iou_values": use_default_iou_values,
"ram": ram,
"nvram": nvram,
"ethernet_adapters": ethernet_adapters,
"serial_adapters": serial_adapters,
"server": server}
if key in self._iou_devices or not name or not server:
continue
self._iou_devices[key] = {}
for setting_name, default_value in IOU_DEVICE_SETTINGS.items():
self._iou_devices[key][setting_name] = settings.value(setting_name, default_value, IOU_DEVICE_SETTING_TYPES[setting_name])
settings.endArray()
settings.endGroup()
@@ -407,7 +389,12 @@ class IOU(Module):
settings["nvram"] = self._iou_devices[iouimage]["nvram"]
settings["ethernet_adapters"] = self._iou_devices[iouimage]["ethernet_adapters"]
settings["serial_adapters"] = self._iou_devices[iouimage]["serial_adapters"]
node.setup(iou_path, initial_settings=settings)
if node.server().isCloud():
settings["cloud_path"] = "images/IOU"
node.setup(self._iou_devices[iouimage]["image"], initial_settings=settings)
else:
node.setup(iou_path, initial_settings=settings)
def reset(self):
"""

View File

@@ -27,6 +27,7 @@ from gns3.qt import QtGui
from gns3.node import Node
from gns3.servers import Servers
from ....settings import ENABLE_CLOUD
from ..ui.iou_device_wizard_ui import Ui_IOUDeviceWizard
from .. import IOU
@@ -38,18 +39,25 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
:param parent: parent widget
"""
def __init__(self, parent):
def __init__(self, iou_devices, parent):
QtGui.QWizard.__init__(self, parent)
self.setupUi(self)
self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/multilayer_switch.normal.svg"))
self.setWizardStyle(QtGui.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtGui.QWizard.NoDefaultButton)
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
self.uiIOUImageToolButton.clicked.connect(self._iouImageBrowserSlot)
self.uiTypeComboBox.currentIndexChanged[str].connect(self._typeChangedSlot)
if sys.platform.startswith("win"):
# Cannot use IOU locally on Windows
self.uiLocalRadioButton.setEnabled(False)
# Available types
self.uiTypeComboBox.addItems(["L2 image", "L3 image"])
@@ -57,9 +65,17 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
self.uiNameImageWizardPage.registerField("name*", self.uiNameLineEdit)
self.uiNameImageWizardPage.registerField("image*", self.uiIOUImageLineEdit)
self._iou_devices = iou_devices
if IOU.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
else:
self.uiIOUImageToolButton.setEnabled(False)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
"""
@@ -70,8 +86,10 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
if checked:
self.uiRemoteServersGroupBox.setEnabled(True)
self.uiIOUImageToolButton.setEnabled(False)
else:
self.uiRemoteServersGroupBox.setEnabled(False)
self.uiIOUImageToolButton.setEnabled(True)
def _loadBalanceToggledSlot(self, checked):
"""
@@ -121,18 +139,21 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
self.uiRemoteServersComboBox.clear()
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem("{}:{}".format(server.host, server.port), server)
if self.page(page_id) == self.uiNameImageWizardPage:
if not self.uiIOUImageToolButton.isEnabled():
QtGui.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!")
def validateCurrentPage(self):
"""
Validates the server.
"""
if self.currentPage() == self.uiServerWizardPage:
#FIXME: prevent users to use "cloud"
if self.uiCloudRadioButton.isChecked():
QtGui.QMessageBox.critical(self, "Cloud", "Sorry not implemented yet!")
return False
if self.currentPage() == self.uiNameImageWizardPage:
name = self.uiNameLineEdit.text()
for iou_device in self._iou_devices.values():
if iou_device["name"] == name:
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
return True
def getSettings(self):
@@ -169,14 +190,17 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
if IOU.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOU device", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
else:
server = self.uiRemoteServersComboBox.currentText()
elif self.uiRemoteRadioButton.isChecked():
if self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOU device", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
else:
server = self.uiRemoteServersComboBox.currentText()
else: # Cloud is selected
server = "cloud"
settings = {
"name": self.uiNameLineEdit.text(),

View File

@@ -26,6 +26,7 @@ from gns3.ports.port import Port
from gns3.ports.ethernet_port import EthernetPort
from gns3.ports.serial_port import SerialPort
from gns3.utils.normalize_filename import normalize_filename
from .settings import IOU_DEVICE_SETTINGS
import logging
log = logging.getLogger(__name__)
@@ -54,11 +55,11 @@ class IOUDevice(Node):
"path": "",
"initial_config": "",
"l1_keepalives": False,
"use_default_iou_values": True,
"ram": 256,
"nvram": 128,
"ethernet_adapters": 2,
"serial_adapters": 2,
"use_default_iou_values": IOU_DEVICE_SETTINGS["use_default_iou_values"],
"ram": IOU_DEVICE_SETTINGS["ram"],
"nvram": IOU_DEVICE_SETTINGS["nvram"],
"ethernet_adapters": IOU_DEVICE_SETTINGS["ethernet_adapters"],
"serial_adapters": IOU_DEVICE_SETTINGS["serial_adapters"],
"console": None}
#self._occupied_slots = []
@@ -133,6 +134,9 @@ class IOUDevice(Node):
if console:
params["console"] = self._settings["console"] = console
if "cloud_path" in initial_settings:
params["cloud_path"] = self._settings["cloud_path"] = initial_settings.pop("cloud_path")
# other initial settings will be applied when the router has been created
if initial_settings:
self._inital_settings = initial_settings
@@ -497,7 +501,7 @@ class IOUDevice(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
@@ -614,7 +618,7 @@ class IOUDevice(Node):
# save only the image name if it is stored the images directory
iou["properties"]["path"] = os.path.basename(image_path)
else:
iou["properties"]["path"] = os.path.basename(image_path)
iou["properties"]["path"] = image_path
return iou

View File

@@ -29,8 +29,10 @@ from gns3.qt import QtCore, QtGui
from gns3.main_window import MainWindow
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.cloud.utils import UploadFilesThread
from .. import IOU
from ..settings import IOU_DEVICE_SETTINGS
from ..ui.iou_device_preferences_page_ui import Ui_IOUDevicePreferencesPageWidget
from ..pages.iou_device_configuration_page import iouDeviceConfigurationPage
from ..dialogs.iou_device_wizard import IOUDeviceWizard
@@ -81,28 +83,15 @@ class IOUDevicePreferencesPage(QtGui.QWidget, Ui_IOUDevicePreferencesPageWidget)
Creates a new IOU device.
"""
wizard = IOUDeviceWizard(parent=self)
wizard = IOUDeviceWizard(self._iou_devices, parent=self)
wizard.show()
if wizard.exec_():
new_device_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_device_settings["server"], name=new_device_settings["name"])
self._iou_devices[key] = {"name": new_device_settings["name"],
"path": new_device_settings["path"],
"default_symbol": new_device_settings["default_symbol"],
"hover_symbol": new_device_settings["hover_symbol"],
"category": new_device_settings["category"],
"image": os.path.basename(new_device_settings["path"]),
"initial_config": new_device_settings["initial_config"],
"use_default_iou_values": True,
"ram": 256,
"nvram": 128,
"ethernet_adapters": 2,
"serial_adapters": 2,
"server": new_device_settings["server"]}
self._iou_devices[key] = IOU_DEVICE_SETTINGS.copy()
self._iou_devices[key].update(new_device_settings)
item = QtGui.QTreeWidgetItem(self.uiIOUDevicesTreeWidget)
item.setText(0, self._iou_devices[key]["name"])
item.setIcon(0, QtGui.QIcon(self._iou_devices[key]["default_symbol"]))
@@ -110,6 +99,33 @@ class IOUDevicePreferencesPage(QtGui.QWidget, Ui_IOUDevicePreferencesPageWidget)
self._items.append(item)
self.uiIOUDevicesTreeWidget.setCurrentItem(item)
if new_device_settings["server"] == 'cloud':
import logging
log = logging.getLogger(__name__)
# Start uploading the image to cloud files
self._upload_image_progress_dialog = QtGui.QProgressDialog(
"Uploading image file {}".format(new_device_settings['image']), "Cancel", 0, 0, parent=self)
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._upload_image_progress_dialog.setWindowTitle("IOU image upload")
self._upload_image_progress_dialog.show()
try:
src = self._iou_devices[key]['path']
# Eg: images/IOU/i86.bin
dst = 'images/IOU/{}'.format(self._iou_devices[key]['image'])
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), [(src, dst)])
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:
self._upload_image_progress_dialog.reject()
log.error(e)
QtGui.QMessageBox.critical(self, "IOU image upload", "Error uploading IOU image: {}".format(e))
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():
return
self._upload_image_progress_dialog.accept()
def _iouDeviceEditSlot(self):
"""
Edits an IOU device.
@@ -123,11 +139,16 @@ class IOUDevicePreferencesPage(QtGui.QWidget, Ui_IOUDevicePreferencesPageWidget)
dialog.show()
if dialog.exec_():
if iou_device["name"] != item.text(0):
if "{}:{}".format(iou_device["server"], iou_device["name"]) in self._iou_devices:
# FIXME: bug when changing name
QtGui.QMessageBox.critical(self, "New IOU device", "IOU device name {} already exists".format(iou_device["name"]))
new_key = "{server}:{name}".format(server=iou_device["server"], name=iou_device["name"])
if new_key in self._iou_devices:
QtGui.QMessageBox.critical(self, "IOU device", "IOU device name {} already exists for server {}".format(iou_device["name"],
iou_device["server"]))
iou_device["name"] = item.text(0)
return
self._iou_devices[new_key] = self._iou_devices[key]
del self._iou_devices[key]
item.setText(0, iou_device["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(iou_device)
def _iouDeviceDeleteSlot(self):

View File

@@ -15,13 +15,16 @@
# 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 sys
import os
"""
Default IOU settings.
"""
from gns3.node import Node
import sys
import os
if sys.platform.startswith("linux"):
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
# look for iouyap in the current working directory and $PATH
@@ -59,3 +62,35 @@ IOU_SETTING_TYPES = {
"udp_end_port_range": int,
"use_local_server": bool,
}
IOU_DEVICE_SETTINGS = {
"name": "",
"path": "",
"default_symbol": ":/symbols/multilayer_switch.normal.svg",
"hover_symbol": ":/symbols/multilayer_switch.selected.svg",
"category": Node.routers,
"image": "",
"initial_config": "",
"use_default_iou_values": True,
"ram": 256,
"nvram": 128,
"ethernet_adapters": 2,
"serial_adapters": 2,
"server": "local"
}
IOU_DEVICE_SETTING_TYPES = {
"name": str,
"path": str,
"default_symbol": str,
"hover_symbol": str,
"category": int,
"image": str,
"initial_config": str,
"use_default_iou_values": bool,
"ram": int,
"nvram": int,
"ethernet_adapters": int,
"serial_adapters": int,
"server": str
}

View File

@@ -30,7 +30,7 @@
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_preferences_page.ui'
#
# Created: Tue Oct 7 15:39:24 2014
# Created: Wed Nov 19 18:57:19 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -37,7 +37,7 @@ class Ui_IOUDevicePreferencesPageWidget(object):
self.uiIOUDevicesTreeWidget.setSizePolicy(sizePolicy)
self.uiIOUDevicesTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(12)
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiIOUDevicesTreeWidget.setFont(font)

View File

@@ -2,9 +2,6 @@
<ui version="4.0">
<class>IOUDeviceWizard</class>
<widget class="QWizard" name="IOUDeviceWizard">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
@@ -16,6 +13,9 @@
<property name="windowTitle">
<string>New IOU device</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_wizard.ui'
#
# Created: Sat Oct 18 20:33:37 2014
# Created: Wed Oct 22 16:46:37 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,8 +26,8 @@ except AttributeError:
class Ui_IOUDeviceWizard(object):
def setupUi(self, IOUDeviceWizard):
IOUDeviceWizard.setObjectName(_fromUtf8("IOUDeviceWizard"))
IOUDeviceWizard.setWindowModality(QtCore.Qt.WindowModal)
IOUDeviceWizard.resize(514, 366)
IOUDeviceWizard.setModal(True)
self.uiServerWizardPage = QtGui.QWizardPage()
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
self.gridLayout_2 = QtGui.QGridLayout(self.uiServerWizardPage)

View File

@@ -28,6 +28,7 @@ from ..module import Module
from ..module_error import ModuleError
from .qemu_vm import QemuVM
from .settings import QEMU_SETTINGS, QEMU_SETTING_TYPES
from .settings import QEMU_VM_SETTINGS, QEMU_VM_SETTING_TYPES
import logging
log = logging.getLogger(__name__)
@@ -46,7 +47,6 @@ class Qemu(Module):
self._nodes = []
self._servers = []
self._working_dir = ""
self._qemu_binaries = {}
# load the settings
self._loadSettings()
@@ -89,40 +89,14 @@ class Qemu(Module):
size = settings.beginReadArray("VM")
for index in range(0, size):
settings.setArrayIndex(index)
name = settings.value("name", "")
default_symbol = settings.value("default_symbol", ":/symbols/qemu_guest.normal.svg")
hover_symbol = settings.value("hover_symbol", ":/symbols/qemu_guest.selected.svg")
category = settings.value("category", Node.end_devices, type=int)
qemu_path = settings.value("qemu_path", "")
hda_disk_image = settings.value("hda_disk_image", "")
hdb_disk_image = settings.value("hdb_disk_image", "")
ram = settings.value("ram", 256, type=int)
adapters = settings.value("adapters", 1, type=int)
adapter_type = settings.value("adapter_type", "e1000")
initrd = settings.value("initrd", "")
kernel_image = settings.value("kernel_image", "")
kernel_command_line = settings.value("kernel_command_line", "")
options = settings.value("options", "")
server = settings.value("server", "local")
name = settings.value("name")
server = settings.value("server")
key = "{server}:{name}".format(server=server, name=name)
self._qemu_vms[key] = {"name": name,
"default_symbol": default_symbol,
"hover_symbol": hover_symbol,
"category": category,
"qemu_path": qemu_path,
"hda_disk_image": hda_disk_image,
"hdb_disk_image": hdb_disk_image,
"ram": ram,
"adapters": adapters,
"adapter_type": adapter_type,
"options": options,
"initrd": initrd,
"kernel_image": kernel_image,
"kernel_command_line": kernel_command_line,
"server": server}
if key in self._qemu_vms or not name or not server:
continue
self._qemu_vms[key] = {}
for setting_name, default_value in QEMU_VM_SETTINGS.items():
self._qemu_vms[key][setting_name] = settings.value(setting_name, default_value, QEMU_VM_SETTING_TYPES[setting_name])
settings.endArray()
settings.endGroup()
@@ -381,15 +355,11 @@ class Qemu(Module):
qemu_path = self._qemu_vms[vm]["qemu_path"]
name = self._qemu_vms[vm]["name"]
node.setup(qemu_path, initial_settings=settings, base_name=name)
# refresh the Qemu binaries list
if node.server().isLocal():
server = "local"
else:
server = node.server().host
if not self._qemu_binaries or server not in self._qemu_binaries:
self.refreshQemuBinaries(node.server())
if node.server().isCloud():
settings["cloud_path"] = "images/qemu"
node.setup(qemu_path, initial_settings=settings, base_name=name)
def reset(self):
"""
@@ -437,50 +407,6 @@ class Qemu(Module):
server.send_message("qemu.qemu_list", None, callback)
def refreshQemuBinaries(self, server):
"""
Gets the QEMU binaries list from a server.
:param server: server to send the request to
"""
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
server.reconnect()
except OSError as e:
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
server.send_message("qemu.qemu_list", None, self._refreshQemuBinariesCallback)
def _refreshQemuBinariesCallback(self, result, error=False):
"""
Callback to get the QEMU binaries list.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("could not get the QEMU binaries list: {}".format(result["message"]))
else:
if self.settings()["use_local_server"]:
server = "local"
else:
server = result["server"]
self._qemu_binaries[server] = result
def getQemuBinariesList(self):
"""
Returns the list of Qemu binaries
:return: dict
"""
return self._qemu_binaries
@staticmethod
def getNodeClass(name):
"""

View File

@@ -30,8 +30,10 @@ from gns3.main_window import MainWindow
from gns3.modules.module_error import ModuleError
from gns3.utils.connect_to_server import ConnectToServer
from ....settings import ENABLE_CLOUD
from ..ui.qemu_vm_wizard_ui import Ui_QemuVMWizard
from .. import Qemu
from ..settings import QEMU_BINARIES_FOR_CLOUD
class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
@@ -41,12 +43,15 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
:param parent: parent widget
"""
def __init__(self, parent):
def __init__(self, qemu_vms, parent):
QtGui.QWizard.__init__(self, parent)
self.setupUi(self)
self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/qemu.svg"))
self.setWizardStyle(QtGui.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtGui.QWizard.NoDefaultButton)
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
self.uiHdaDiskImageToolButton.clicked.connect(self._hdaDiskImageBrowserSlot)
@@ -65,6 +70,8 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
self.uiASAWizardPage.registerField("initrd*", self.uiInitrdLineEdit)
self.uiASAWizardPage.registerField("kernel_image*", self.uiKernelImageLineEdit)
self._qemu_vms = qemu_vms
if Qemu.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
@@ -72,6 +79,9 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
# By default we use the local server
self._server = Servers.instance().localServer()
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
"""
Slot for when the remote server radio button is toggled.
@@ -184,19 +194,27 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
"""
if self.currentPage() == self.uiServerWizardPage:
if not self.uiCloudRadioButton.isChecked():
if Qemu.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = Servers.instance().localServer()
elif self.uiRemoteRadioButton.isChecked():
server = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex())
if not server.connected() and ConnectToServer(self, server) is False:
return False
self._server = server
#FIXME: prevent users to use "cloud"
if self.uiCloudRadioButton.isChecked():
QtGui.QMessageBox.critical(self, "Cloud", "Sorry not implemented yet!")
if self.currentPage() == self.uiNameTypeWizardPage:
name = self.uiNameLineEdit.text()
for qemu_vm in self._qemu_vms.values():
if qemu_vm["name"] == name:
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
if self.currentPage() == self.uiBinaryMemoryWizardPage:
if not self.uiQemuListComboBox.count():
QtGui.QMessageBox.critical(self, "QEMU binaries", "Sorry, no QEMU binary has been found. Please make sure QEMU is installed before continuing")
return False
if Qemu.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = Servers.instance().localServer()
else:
server = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex())
if not server.connected() and ConnectToServer(self, server) is False:
return False
self._server = server
return True
def initializePage(self, page_id):
@@ -206,15 +224,23 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem("{}:{}".format(server.host, server.port), server)
if self.page(page_id) == self.uiBinaryMemoryWizardPage:
self._qemu_binaries_progress_dialog = QtGui.QProgressDialog("Loading QEMU binaries", "Cancel", 0, 0, parent=self)
self._qemu_binaries_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._qemu_binaries_progress_dialog.setWindowTitle("QEMU binaries")
self._qemu_binaries_progress_dialog.show()
try:
Qemu.instance().getQemuBinariesFromServer(self._server, self._getQemuBinariesFromServerCallback)
except ModuleError as e:
self._qemu_binaries_progress_dialog.reject()
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
if self.uiCloudRadioButton.isChecked():
for binary in QEMU_BINARIES_FOR_CLOUD:
self.uiQemuListComboBox.addItem("{path}".format(path=binary), binary)
# Default to x86_64 for the user
index = self.uiQemuListComboBox.findData("x86_64", flags=QtCore.Qt.MatchEndsWith)
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
else:
self._qemu_binaries_progress_dialog = QtGui.QProgressDialog("Loading QEMU binaries", "Cancel", 0, 0, parent=self)
self._qemu_binaries_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._qemu_binaries_progress_dialog.setWindowTitle("QEMU binaries")
self._qemu_binaries_progress_dialog.show()
try:
Qemu.instance().getQemuBinariesFromServer(self._server, self._getQemuBinariesFromServerCallback)
except ModuleError as e:
self._qemu_binaries_progress_dialog.reject()
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
def _getQemuBinariesFromServerCallback(self, result, error=False):
"""
@@ -229,7 +255,7 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
self._qemu_binaries_progress_dialog.accept()
if error:
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error: ".format(result["message"]))
QtGui.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result["qemus"]:
@@ -238,13 +264,22 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
#FIXME: use findData instead
is_64bit = sys.maxsize > 2**32
if sys.platform.startswith("win"):
# default is qemu-system-x86_64w on Windows
index = self.uiQemuListComboBox.findText("x86_64w", QtCore.Qt.MatchContains)
if is_64bit:
# default is qemu-system-x86_64w.exe on Windows 64-bit
search_string = "x86_64w.exe"
else:
# default is qemu-system-i386w.exe on Windows 32-bit
search_string = "i386w.exe"
elif is_64bit:
# default is qemu-system-x86_64 on other 64-bit platforms
search_string = "x86_64"
else:
# default is qemu-system-x86_64 on other platforms
index = self.uiQemuListComboBox.findText("x86_64 ", QtCore.Qt.MatchContains) # the space after x86_64 must be present!
# default is qemu-system-i386 on other platforms
search_string = "i386"
index = self.uiQemuListComboBox.findData(search_string, flags=QtCore.Qt.MatchEndsWith)
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
@@ -257,8 +292,11 @@ class QemuVMWizard(QtGui.QWizard, Ui_QemuVMWizard):
if Qemu.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = "local"
else:
elif self.uiRemoteRadioButton.isChecked():
server = self.uiRemoteServersComboBox.currentText()
else: # Cloud is selected
server = "cloud"
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
settings = {

View File

@@ -21,13 +21,17 @@ Configuration page for QEMU VMs.
import os
import shutil
from functools import partial
from gns3.qt import QtGui
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.modules.module_error import ModuleError
from gns3.main_window import MainWindow
from gns3.dialogs.node_configurator_dialog import ConfigurationError
from ..ui.qemu_vm_configuration_page_ui import Ui_QemuVMConfigPageWidget
from .. import Qemu
from ..settings import QEMU_BINARIES_FOR_CLOUD
class QemuVMConfigurationPage(QtGui.QWidget, Ui_QemuVMConfigPageWidget):
@@ -135,6 +139,35 @@ class QemuVMConfigurationPage(QtGui.QWidget, Ui_QemuVMConfigPageWidget):
self.uiKernelImageLineEdit.clear()
self.uiKernelImageLineEdit.setText(path)
def _getQemuBinariesFromServerCallback(self, result, error=False, qemu_path=None):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if self._qemu_binaries_progress_dialog.wasCanceled():
return
self._qemu_binaries_progress_dialog.accept()
if error:
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error: ".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result["qemus"]:
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"])
index = self.uiQemuListComboBox.findData("{path}".format(path=qemu_path))
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
else:
QtGui.QMessageBox.critical(self, "Qemu", "Could not find {} in the Qemu binaries list".format(qemu_path))
self.uiQemuListComboBox.clear()
def loadSettings(self, settings, node=None, group=False):
"""
Loads the QEMU VM settings.
@@ -145,29 +178,31 @@ class QemuVMConfigurationPage(QtGui.QWidget, Ui_QemuVMConfigPageWidget):
"""
if node:
if node.server().isLocal():
server = "local"
else:
server = node.server().host
server = node.server()
else:
server = settings["server"]
if server == "local":
server = Servers.instance().localServer()
elif ":" in server:
host, port = server.rsplit(":")
server = Servers.instance().getRemoteServer(host, port)
qemu_binaries = Qemu.instance().getQemuBinariesList()
if server in qemu_binaries:
qemus = qemu_binaries[server]["qemus"]
for qemu in qemus:
key = "{server}:{qemu}".format(server=server, qemu=qemu["path"])
if qemu["version"]:
self.uiQemuListComboBox.addItem("{key} (v{version})".format(key=key, version=qemu["version"]), key)
else:
self.uiQemuListComboBox.addItem("{key}".format(key=key), key)
index = self.uiQemuListComboBox.findData("{server}:{qemu_path}".format(server=server, qemu_path=settings["qemu_path"]))
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
if server == "cloud":
for binary in QEMU_BINARIES_FOR_CLOUD:
self.uiQemuListComboBox.addItem("{path}".format(path=binary), binary)
else:
self.uiQemuListComboBox.clear()
self._qemu_binaries_progress_dialog = QtGui.QProgressDialog("Loading QEMU binaries", "Cancel", 0, 0, parent=self)
self._qemu_binaries_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._qemu_binaries_progress_dialog.setWindowTitle("QEMU binaries")
self._qemu_binaries_progress_dialog.show()
callback = partial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
try:
Qemu.instance().getQemuBinariesFromServer(server, callback)
except ModuleError as e:
self._qemu_binaries_progress_dialog.reject()
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
self.uiQemuListComboBox.clear()
if not group:
# set the device name
@@ -244,8 +279,9 @@ class QemuVMConfigurationPage(QtGui.QWidget, Ui_QemuVMConfigPageWidget):
del settings["kernel_image"]
if self.uiQemuListComboBox.count():
server, qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex()).split(":", 1)
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
settings["qemu_path"] = qemu_path
settings["adapter_type"] = self.uiAdapterTypesComboBox.currentText()
settings["kernel_command_line"] = self.uiKernelCommandLineEdit.text()

View File

@@ -19,6 +19,7 @@
Configuration page for QEMU VM preferences.
"""
import ntpath
import os
import copy
@@ -27,13 +28,16 @@ from gns3.node import Node
from gns3.main_window import MainWindow
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.cloud.utils import UploadFilesThread
from .. import Qemu
from ..settings import QEMU_VM_SETTINGS
from ..ui.qemu_vm_preferences_page_ui import Ui_QemuVMPreferencesPageWidget
from ..pages.qemu_vm_configuration_page import QemuVMConfigurationPage
from ..dialogs.qemu_vm_wizard import QemuVMWizard
class QemuVMPreferencesPage(QtGui.QWidget, Ui_QemuVMPreferencesPageWidget):
"""
QWidget preference page for QEMU VM preferences.
@@ -71,7 +75,8 @@ class QemuVMPreferencesPage(QtGui.QWidget, Ui_QemuVMPreferencesPageWidget):
QtGui.QTreeWidgetItem(section_item, ["VM name:", qemu_vm["name"]])
QtGui.QTreeWidgetItem(section_item, ["Server:", qemu_vm["server"]])
QtGui.QTreeWidgetItem(section_item, ["Memory:", "{} MB".format(qemu_vm["ram"])])
QtGui.QTreeWidgetItem(section_item, ["QEMU binary:", os.path.basename(qemu_vm["qemu_path"])])
if qemu_vm["qemu_path"]:
QtGui.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"]:
@@ -128,34 +133,18 @@ class QemuVMPreferencesPage(QtGui.QWidget, Ui_QemuVMPreferencesPageWidget):
Creates a new VM.
"""
wizard = QemuVMWizard(parent=self)
wizard = QemuVMWizard(self._qemu_vms, parent=self)
wizard.show()
if wizard.exec_():
new_vm_settings = wizard.getSettings()
key = "{server}:{name}".format(server=new_vm_settings["server"], name=new_vm_settings["name"])
if key in self._qemu_vms:
QtGui.QMessageBox.critical(self, "New QEMU VM", "VM name {} already exists".format(new_vm_settings["name"]))
return
self._qemu_vms[key] = {"name": "",
"default_symbol": ":/symbols/qemu_guest.normal.svg",
"hover_symbol": ":/symbols/qemu_guest.selected.svg",
"category": Node.end_devices,
"qemu_path": "default",
"hda_disk_image": "",
"hdb_disk_image": "",
"ram": 256,
"adapters": 1,
"adapter_type": "e1000",
"options": "",
"initrd": "",
"kernel_image": "",
"kernel_command_line": "",
"server": "local"}
self._qemu_vms[key] = QEMU_VM_SETTINGS.copy()
self._qemu_vms[key].update(new_vm_settings)
item = QtGui.QTreeWidgetItem(self.uiQemuVMsTreeWidget)
item.setText(0, self._qemu_vms[key]["name"])
item.setIcon(0, QtGui.QIcon(self._qemu_vms[key]["default_symbol"]))
@@ -163,6 +152,51 @@ class QemuVMPreferencesPage(QtGui.QWidget, Ui_QemuVMPreferencesPageWidget):
self._items.append(item)
self.uiQemuVMsTreeWidget.setCurrentItem(item)
if self._qemu_vms[key]["server"] == 'cloud':
self._qemu_vms[key]["options"] = "-nographic"
self._uploadImages(new_vm_settings)
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():
return
self._upload_image_progress_dialog.accept()
def _uploadImages(self, qemu_vm):
"""
Upload hard drive images to Cloud Files.
"""
# Start uploading the image to cloud files
self._upload_image_progress_dialog = QtGui.QProgressDialog(
"Uploading image file(s)", "Cancel", 0, 0, parent=self)
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._upload_image_progress_dialog.setWindowTitle("Qemu image upload")
self._upload_image_progress_dialog.show()
try:
uploads = []
src = qemu_vm.get("hda_disk_image", None)
if src:
_, filename = ntpath.split(src)
dst = "images/qemu/{}".format(filename)
uploads.append((src, dst))
src = qemu_vm.get("hdb_disk_image", None)
if src:
_, filename = ntpath.split(src)
dst = "images/qemu/{}".format(filename)
uploads.append((src, dst))
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), uploads)
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:
self._upload_image_progress_dialog.reject()
import logging
log = logging.getLogger(__name__)
log.error(e)
QtGui.QMessageBox.critical(self, "Qemu image upload", "Error uploading Qemu image: {}".format(e))
def _qemuVMEditSlot(self):
"""
Edits a QEMU VM.
@@ -176,11 +210,20 @@ class QemuVMPreferencesPage(QtGui.QWidget, Ui_QemuVMPreferencesPageWidget):
dialog.show()
if dialog.exec_():
if qemu_vm["name"] != item.text(0):
if "{}:{}".format(qemu_vm["server"], qemu_vm["name"]) in self._qemu_vms:
# FIXME: bug when changing name
QtGui.QMessageBox.critical(self, "New QEMU VM", "VM name {} already exists".format(qemu_vm["name"]))
new_key = "{server}:{name}".format(server=qemu_vm["server"], name=qemu_vm["name"])
if new_key in self._qemu_vms:
QtGui.QMessageBox.critical(self, "QEMU VM", "QEMU VM name {} already exists for server {}".format(qemu_vm["name"],
qemu_vm["server"]))
qemu_vm["name"] = item.text(0)
return
self._qemu_vms[new_key] = self._qemu_vms[key]
del self._qemu_vms[key]
item.setText(0, qemu_vm["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
if qemu_vm["server"] == 'cloud':
self._uploadImages(qemu_vm)
self._refreshInfo(qemu_vm)
def _qemuVMDeleteSlot(self):

View File

@@ -22,6 +22,7 @@ QEMU VM implementation.
from gns3.node import Node
from gns3.ports.port import Port
from gns3.ports.ethernet_port import EthernetPort
from .settings import QEMU_VM_SETTINGS
import logging
log = logging.getLogger(__name__)
@@ -46,19 +47,19 @@ class QemuVM(Node):
self._loading = False
self._module = module
self._ports = []
self._settings = {"name": "",
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
"options": "",
"ram": 256,
"ram": QEMU_VM_SETTINGS["ram"],
"console": None,
"adapters": 1,
"adapter_type": "e1000",
"adapters": QEMU_VM_SETTINGS["adapters"],
"adapter_type": QEMU_VM_SETTINGS["adapter_type"],
"initrd": "",
"kernel_image": "",
"kernel_command_line": "",
}
"kernel_command_line": ""}
self._addAdapters(1)
@@ -189,6 +190,9 @@ class QemuVM(Node):
if name in self._settings and self._settings[name] != value:
params[name] = value
if "cloud_path" in new_settings:
params["cloud_path"] = self._settings["cloud_path"] = new_settings.pop("cloud_path")
log.debug("{} is updating settings: {}".format(self.name(), params))
self._server.send_message("qemu.update", params, self._updateCallback)

View File

@@ -19,6 +19,8 @@
Default QEMU settings.
"""
from gns3.node import Node
QEMU_SETTINGS = {
"console_start_port_range": 5001,
"console_end_port_range": 5500,
@@ -34,3 +36,71 @@ QEMU_SETTING_TYPES = {
"udp_end_port_range": int,
"use_local_server": bool,
}
QEMU_VM_SETTINGS = {
"name": "",
"default_symbol": ":/symbols/qemu_guest.normal.svg",
"hover_symbol": ":/symbols/qemu_guest.selected.svg",
"category": Node.end_devices,
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
"ram": 256,
"adapters": 1,
"adapter_type": "e1000",
"options": "",
"initrd": "",
"kernel_image": "",
"kernel_command_line": "",
"server": "local"
}
QEMU_VM_SETTING_TYPES = {
"name": str,
"default_symbol": str,
"hover_symbol": str,
"category": int,
"qemu_path": str,
"hda_disk_image": str,
"hdb_disk_image": str,
"ram": int,
"adapters": int,
"adapter_type": str,
"options": str,
"initrd": str,
"kernel_image": str,
"kernel_command_line": str,
"server": str
}
# Use a hardcoded list of binaries rather than a dynamic one so the user
# doesn't require a running cloud instance to upload qemu images.
QEMU_BINARIES_FOR_CLOUD = [
"qemu-system-arm",
"qemu-system-microblaze",
"qemu-system-mipsel",
"qemu-system-ppcemb",
"qemu-system-sparc64",
"qemu-system-cris",
"qemu-system-microblazeel",
"qemu-system-moxie",
"qemu-system-s390x",
"qemu-system-unicore32",
"qemu-system-i386",
"qemu-system-mips",
"qemu-system-or32",
"qemu-system-sh4",
"qemu-system-x86_64",
"qemu-system-lm32",
"qemu-system-mips64",
"qemu-system-ppc",
"qemu-system-sh4eb",
"qemu-system-xtensa",
"qemu-system-alpha",
"qemu-system-m68k",
"qemu-system-mips64el",
"qemu-system-ppc64",
"qemu-system-sparc",
"qemu-system-xtensaeb",
]
QEMU_BINARIES_FOR_CLOUD.sort()

View File

@@ -192,7 +192,7 @@
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
<number>0</number>
</property>
<property name="maximum">
<number>8</number>

View File

@@ -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: Fri Oct 10 10:43:48 2014
# Created: Sun Nov 9 16:27:45 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -117,7 +117,7 @@ class Ui_QemuVMConfigPageWidget(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiAdaptersSpinBox.sizePolicy().hasHeightForWidth())
self.uiAdaptersSpinBox.setSizePolicy(sizePolicy)
self.uiAdaptersSpinBox.setMinimum(1)
self.uiAdaptersSpinBox.setMinimum(0)
self.uiAdaptersSpinBox.setMaximum(8)
self.uiAdaptersSpinBox.setObjectName(_fromUtf8("uiAdaptersSpinBox"))
self.gridLayout_8.addWidget(self.uiAdaptersSpinBox, 0, 1, 1, 1)

View File

@@ -30,7 +30,7 @@
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_preferences_page.ui'
#
# Created: Fri Oct 3 14:05:58 2014
# Created: Wed Nov 19 18:57:19 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -37,7 +37,7 @@ class Ui_QemuVMPreferencesPageWidget(object):
self.uiQemuVMsTreeWidget.setSizePolicy(sizePolicy)
self.uiQemuVMsTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(12)
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiQemuVMsTreeWidget.setFont(font)

View File

@@ -2,9 +2,6 @@
<ui version="4.0">
<class>QemuVMWizard</class>
<widget class="QWizard" name="QemuVMWizard">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
@@ -16,6 +13,9 @@
<property name="windowTitle">
<string>New QEMU VM</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
#
# Created: Sun Oct 19 16:55:07 2014
# Created: Wed Oct 22 16:46:37 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,8 +26,8 @@ except AttributeError:
class Ui_QemuVMWizard(object):
def setupUi(self, QemuVMWizard):
QemuVMWizard.setObjectName(_fromUtf8("QemuVMWizard"))
QemuVMWizard.setWindowModality(QtCore.Qt.WindowModal)
QemuVMWizard.resize(514, 366)
QemuVMWizard.setModal(True)
self.uiServerWizardPage = QtGui.QWizardPage()
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
self.gridLayout_4 = QtGui.QGridLayout(self.uiServerWizardPage)

View File

@@ -26,6 +26,7 @@ from ..module import Module
from ..module_error import ModuleError
from .virtualbox_vm import VirtualBoxVM
from .settings import VBOX_SETTINGS, VBOX_SETTING_TYPES
from .settings import VBOX_VM_SETTINGS, VBOX_VM_SETTING_TYPES
import logging
log = logging.getLogger(__name__)
@@ -87,29 +88,14 @@ class VirtualBox(Module):
size = settings.beginReadArray("VM")
for index in range(0, size):
settings.setArrayIndex(index)
vmname = settings.value("vmname", "")
adapters = settings.value("adapters", 1, type=int)
default_symbol = settings.value("default_symbol", ":/symbols/vbox_guest.normal.svg")
hover_symbol = settings.value("hover_symbol", ":/symbols/vbox_guest.selected.svg")
category = settings.value("category", Node.end_devices, type=int)
adapter_start_index = settings.value("adapter_start_index", 0, type=int)
adapter_type = settings.value("adapter_type", "Automatic")
headless = settings.value("headless", False, type=bool)
enable_console = settings.value("enable_console", True, type=bool)
server = settings.value("server", "local")
vmname = settings.value("vmname")
server = settings.value("server")
key = "{server}:{vmname}".format(server=server, vmname=vmname)
self._virtualbox_vms[key] = {"vmname": vmname,
"default_symbol": default_symbol,
"hover_symbol": hover_symbol,
"category": category,
"adapters": adapters,
"adapter_start_index": adapter_start_index,
"adapter_type": adapter_type,
"headless": headless,
"enable_console": enable_console,
"server": server}
if key in self._virtualbox_vms or not vmname or not server:
continue
self._virtualbox_vms[key] = {}
for setting_name, default_value in VBOX_VM_SETTINGS.items():
self._virtualbox_vms[key][setting_name] = settings.value(setting_name, default_value, VBOX_VM_SETTING_TYPES[setting_name])
settings.endArray()
settings.endGroup()
@@ -254,6 +240,8 @@ class VirtualBox(Module):
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "vboxmanage_path" in params:
del params["vboxmanage_path"] # do not send VBoxManage path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
@@ -277,8 +265,8 @@ class VirtualBox(Module):
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "vboxwrapper_path" in params:
del params["vboxwrapper_path"] # do not send Vboxwrapper path to remote servers
if "vboxmanage_path" in params:
del params["vboxmanage_path"] # do not send VBoxManage path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
@@ -347,19 +335,22 @@ class VirtualBox(Module):
else:
vm = selected_vms[0]
for other_node in self._nodes:
if other_node.settings()["vmname"] == self._virtualbox_vms[vm]["vmname"] and \
(self._virtualbox_vms[vm]["server"] == "local" and other_node.server().isLocal() or self._virtualbox_vms[vm]["server"] == other_node.server().host):
raise ModuleError("Sorry a VirtualBox VM can only be used once in your topology (this will change in future versions)")
linked_base = self._virtualbox_vms[vm]["linked_base"]
if not linked_base:
for other_node in self._nodes:
if other_node.settings()["vmname"] == self._virtualbox_vms[vm]["vmname"] and \
(self._virtualbox_vms[vm]["server"] == "local" and other_node.server().isLocal() or self._virtualbox_vms[vm]["server"] == other_node.server().host):
raise ModuleError("Sorry a VirtualBox VM can only be used once in your topology (this will change in future versions)")
settings = {"adapters": self._virtualbox_vms[vm]["adapters"],
"adapter_start_index": self._virtualbox_vms[vm]["adapter_start_index"],
"adapter_type": self._virtualbox_vms[vm]["adapter_type"],
"headless": self._virtualbox_vms[vm]["headless"],
"enable_console": self._virtualbox_vms[vm]["enable_console"]}
"enable_remote_console": self._virtualbox_vms[vm]["enable_remote_console"]}
vmname = self._virtualbox_vms[vm]["vmname"]
node.setup(vmname, initial_settings=settings)
node.setup(vmname, linked_clone=linked_base, initial_settings=settings)
def reset(self):
"""
@@ -404,8 +395,10 @@ class VirtualBox(Module):
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
server.send_message("virtualbox.vm_list", None, callback)
params = {}
if server.isLocal():
params["vboxmanage_path"] = self._settings["vboxmanage_path"]
server.send_message("virtualbox.vm_list", params, callback)
def getVirtualBoxVMList(self):
"""

View File

@@ -19,6 +19,8 @@
Wizard for VirtualBox VMs.
"""
import sys
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.utils.connect_to_server import ConnectToServer
@@ -42,6 +44,9 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
self.setupUi(self)
self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/icons/virtualbox.png"))
self.setWizardStyle(QtGui.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtGui.QWizard.NoDefaultButton)
if VirtualBox.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
@@ -67,7 +72,7 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
VirtualBox.instance().getVirtualBoxVMsFromServer(self._server, self._getVirtualBoxVMsFromServerCallback)
except ModuleError as e:
self._vbox_vms_progress_dialog.reject()
QtGui.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
QtGui.QMessageBox.critical(self, "VirtualBox VMs", "Error while getting the VirtualBox VMs: {}".format(e))
def _getVirtualBoxVMsFromServerCallback(self, result, error=False):
"""
@@ -82,7 +87,7 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
self._vbox_vms_progress_dialog.accept()
if error:
QtGui.QMessageBox.critical(self, "VirtualBox VMs", "Error: ".format(result["message"]))
QtGui.QMessageBox.critical(self, "VirtualBox VMs", "{}".format(result["message"]))
else:
self.uiVMListComboBox.clear()
existing_vms = []
@@ -112,6 +117,10 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
if not server.connected() and ConnectToServer(self, server) is False:
return False
self._server = server
if self.currentPage() == self.uiVirtualBoxWizardPage:
if not self.uiVMListComboBox.count():
QtGui.QMessageBox.critical(self, "VirtualBox VMs", "There is no VirtualBox VM available!")
return False
return True
def getSettings(self):
@@ -121,9 +130,6 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
:return: settings dict
"""
if not self.uiVMListComboBox.count():
return {}
if VirtualBox.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = "local"
else:
@@ -134,6 +140,7 @@ class VirtualBoxVMWizard(QtGui.QWizard, Ui_VirtualBoxVMWizard):
settings = {
"vmname": vmname,
"server": server,
"linked_base": self.uiBaseVMCheckBox.isChecked()
}
return settings

View File

@@ -40,25 +40,25 @@ class VirtualBoxPreferencesPage(QtGui.QWidget, Ui_VirtualBoxPreferencesPageWidge
# connect signals
self.uiUseLocalServercheckBox.stateChanged.connect(self._useLocalServerSlot)
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
self.uiVboxWrapperPathToolButton.clicked.connect(self._vboxPathBrowserSlot)
self.uiVboxManagePathToolButton.clicked.connect(self._vboxPathBrowserSlot)
#FIXME: temporally hide test button
self.uiTestSettingsPushButton.hide()
def _vboxPathBrowserSlot(self):
"""
Slot to open a file browser and select VirtualBox wrapper.
Slot to open a file browser and select VBoxManage.
"""
path = QtGui.QFileDialog.getOpenFileName(self, "Select VirtualBox wrapper", ".")
path = QtGui.QFileDialog.getOpenFileName(self, "Select VBoxManage", ".")
if not path:
return
if not os.access(path, os.X_OK):
QtGui.QMessageBox.critical(self, "VirtualBox wrapper", "{} is not an executable".format(os.path.basename(path)))
QtGui.QMessageBox.critical(self, "VBoxManage", "{} is not an executable".format(os.path.basename(path)))
return
self.uiVboxWrapperPathLineEdit.setText(os.path.normpath(path))
self.uiVboxManagePathLineEdit.setText(os.path.normpath(path))
def _restoreDefaultsSlot(self):
"""
@@ -84,7 +84,7 @@ class VirtualBoxPreferencesPage(QtGui.QWidget, Ui_VirtualBoxPreferencesPageWidge
:param settings: VirtualBox settings
"""
self.uiVboxWrapperPathLineEdit.setText(settings["vboxwrapper_path"])
self.uiVboxManagePathLineEdit.setText(settings["vboxmanage_path"])
self.uiUseLocalServercheckBox.setChecked(settings["use_local_server"])
self.uiConsoleStartPortSpinBox.setValue(settings["console_start_port_range"])
self.uiConsoleEndPortSpinBox.setValue(settings["console_end_port_range"])
@@ -125,7 +125,7 @@ class VirtualBoxPreferencesPage(QtGui.QWidget, Ui_VirtualBoxPreferencesPageWidge
"""
new_settings = {}
new_settings["vboxwrapper_path"] = self.uiVboxWrapperPathLineEdit.text()
new_settings["vboxmanage_path"] = self.uiVboxManagePathLineEdit.text()
new_settings["use_local_server"] = self.uiUseLocalServercheckBox.isChecked()
new_settings["console_start_port_range"] = self.uiConsoleStartPortSpinBox.value()
new_settings["console_end_port_range"] = self.uiConsoleEndPortSpinBox.value()

View File

@@ -36,8 +36,7 @@ class virtualBoxVMConfigurationPage(QtGui.QWidget, Ui_virtualBoxVMConfigPageWidg
self.setupUi(self)
self.uiAdapterTypesComboBox.clear()
self.uiAdapterTypesComboBox.addItems(["Automatic",
"PCnet-PCI II (Am79C970A)",
self.uiAdapterTypesComboBox.addItems(["PCnet-PCI II (Am79C970A)",
"PCNet-FAST III (Am79C973)",
"Intel PRO/1000 MT Desktop (82540EM)",
"Intel PRO/1000 T Server (82543GC)",
@@ -72,6 +71,11 @@ class virtualBoxVMConfigurationPage(QtGui.QWidget, Ui_virtualBoxVMConfigPageWidg
self.uiConsolePortLabel.hide()
self.uiConsolePortSpinBox.hide()
if "linked_base" in settings:
self.uiBaseVMCheckBox.setChecked(settings["linked_base"])
else:
self.uiBaseVMCheckBox.hide()
else:
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
@@ -86,7 +90,7 @@ class virtualBoxVMConfigurationPage(QtGui.QWidget, Ui_virtualBoxVMConfigPageWidg
if index != -1:
self.uiAdapterTypesComboBox.setCurrentIndex(index)
self.uiHeadlessModeCheckBox.setChecked(settings["headless"])
self.uiEnableConsoleCheckBox.setChecked(settings["enable_console"])
self.uiEnableConsoleCheckBox.setChecked(settings["enable_remote_console"])
def saveSettings(self, settings, node=None, group=False):
"""
@@ -108,13 +112,18 @@ class virtualBoxVMConfigurationPage(QtGui.QWidget, Ui_virtualBoxVMConfigPageWidg
else:
settings["name"] = name
settings["console"] = self.uiConsolePortSpinBox.value()
settings["enable_console"] = self.uiEnableConsoleCheckBox.isChecked()
if "console" in settings:
settings["console"] = self.uiConsolePortSpinBox.value()
if "linked_base" in settings:
settings["linked_base"] = self.uiBaseVMCheckBox.isChecked()
settings["enable_remote_console"] = self.uiEnableConsoleCheckBox.isChecked()
else:
del settings["name"]
del settings["console"]
del settings["enable_console"]
del settings["enable_remote_console"]
settings["adapter_type"] = self.uiAdapterTypesComboBox.currentText()

View File

@@ -28,6 +28,7 @@ from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from .. import VirtualBox
from ..settings import VBOX_VM_SETTINGS
from ..ui.virtualbox_vm_preferences_page_ui import Ui_VirtualBoxVMPreferencesPageWidget
from ..pages.virtualbox_vm_configuration_page import virtualBoxVMConfigurationPage
from ..dialogs.virtualbox_vm_wizard import VirtualBoxVMWizard
@@ -87,12 +88,14 @@ class VirtualBoxVMPreferencesPage(QtGui.QWidget, Ui_VirtualBoxVMPreferencesPageW
section_item = self._createSectionItem("General")
QtGui.QTreeWidgetItem(section_item, ["VM name:", vbox_vm["vmname"]])
QtGui.QTreeWidgetItem(section_item, ["Server:", vbox_vm["server"]])
QtGui.QTreeWidgetItem(section_item, ["Console enabled:", "{}".format(vbox_vm["enable_console"])])
QtGui.QTreeWidgetItem(section_item, ["Remote console enabled:", "{}".format(vbox_vm["enable_remote_console"])])
QtGui.QTreeWidgetItem(section_item, ["Headless mode enabled:", "{}".format(vbox_vm["headless"])])
QtGui.QTreeWidgetItem(section_item, ["Linked base VM:", "{}".format(vbox_vm["linked_base"])])
# fill out the Network section
section_item = self._createSectionItem("Network")
QtGui.QTreeWidgetItem(section_item, ["Adapters:", str(vbox_vm["adapters"])])
QtGui.QTreeWidgetItem(section_item, ["Start at:", str(vbox_vm["adapter_start_index"])])
QtGui.QTreeWidgetItem(section_item, ["Type:", vbox_vm["adapter_type"]])
self.uiVirtualBoxVMInfoTreeWidget.expandAll()
@@ -109,22 +112,10 @@ class VirtualBoxVMPreferencesPage(QtGui.QWidget, Ui_VirtualBoxVMPreferencesPageW
if wizard.exec_():
new_vm_settings = wizard.getSettings()
if not new_vm_settings:
return
key = "{server}:{vmname}".format(server=new_vm_settings["server"], vmname=new_vm_settings["vmname"])
self._virtualbox_vms[key] = {"vmname": "",
"default_symbol": ":/symbols/vbox_guest.normal.svg",
"hover_symbol": ":/symbols/vbox_guest.selected.svg",
"category": Node.end_devices,
"adapters": 1,
"adapter_start_index": 0,
"adapter_type": "Automatic",
"headless": False,
"enable_console": True,
"server": "local"}
self._virtualbox_vms[key] = VBOX_VM_SETTINGS.copy()
self._virtualbox_vms[key].update(new_vm_settings)
item = QtGui.QTreeWidgetItem(self.uiVirtualBoxVMsTreeWidget)
item.setText(0, self._virtualbox_vms[key]["vmname"])
item.setIcon(0, QtGui.QIcon(self._virtualbox_vms[key]["default_symbol"]))
@@ -145,11 +136,16 @@ class VirtualBoxVMPreferencesPage(QtGui.QWidget, Ui_VirtualBoxVMPreferencesPageW
dialog.show()
if dialog.exec_():
if vbox_vm["vmname"] != item.text(0):
if "{}:{}".format(vbox_vm["server"], vbox_vm["vmname"]) in self._virtualbox_vms:
# FIXME: bug when changing name
QtGui.QMessageBox.critical(self, "New VirtualBox VM", "VM name {} already exists".format(vbox_vm["vmname"]))
new_key = "{server}:{vmname}".format(server=vbox_vm["server"], name=vbox_vm["vmname"])
if new_key in self._virtualbox_vms:
QtGui.QMessageBox.critical(self, "VirtualBox VM", "VirtualBox VM name {} already exists for server {}".format(vbox_vm["vmname"],
vbox_vm["server"]))
vbox_vm["vmname"] = item.text(0)
return
self._virtualbox_vms[new_key] = self._virtualbox_vms[key]
del self._virtualbox_vms[key]
item.setText(0, vbox_vm["vmname"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(vbox_vm)
def _vboxVMDeleteSlot(self):

View File

@@ -19,26 +19,28 @@
Default VirtualBox settings.
"""
from gns3.node import Node
import sys
import os
# default path to VirtualBox wrapper executable
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
DEFAULT_VBOXWRAPPER_PATH = os.path.join(os.getcwd(), "vboxwrapper")
# default path to VirtualBox vboxmanage executable
if sys.platform.startswith("win") and "VBOX_INSTALL_PATH" in os.environ:
DEFAULT_VBOXMANAGE_PATH = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
else:
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
# look for VirtualBox wrapper in the current working directory and $PATH
DEFAULT_VBOXWRAPPER_PATH = "vboxwrapper"
# look for vboxmanage in the current working directory and $PATH
DEFAULT_VBOXMANAGE_PATH = "vboxmanage"
for path in paths:
try:
if "vboxwrapper" in os.listdir(path) and os.access(os.path.join(path, "vboxwrapper"), os.X_OK):
DEFAULT_VBOXWRAPPER_PATH = os.path.join(path, "vboxwrapper")
if "vboxmanage" in os.listdir(path) and os.access(os.path.join(path, "vboxmanage"), os.X_OK):
DEFAULT_VBOXMANAGE_PATH = os.path.join(path, "vboxmanage")
break
except OSError:
continue
VBOX_SETTINGS = {
"vboxwrapper_path": DEFAULT_VBOXWRAPPER_PATH,
"vboxmanage_path": DEFAULT_VBOXMANAGE_PATH,
"console_start_port_range": 3501,
"console_end_port_range": 4000,
"udp_start_port_range": 35001,
@@ -47,10 +49,38 @@ VBOX_SETTINGS = {
}
VBOX_SETTING_TYPES = {
"vboxwrapper_path": str,
"vboxmanage_path": str,
"console_start_port_range": int,
"console_end_port_range": int,
"udp_start_port_range": int,
"udp_end_port_range": int,
"use_local_server": bool,
}
VBOX_VM_SETTINGS = {
"vmname": "",
"default_symbol": ":/symbols/vbox_guest.normal.svg",
"hover_symbol": ":/symbols/vbox_guest.selected.svg",
"category": Node.end_devices,
"adapters": 1,
"adapter_start_index": 0,
"adapter_type": "Intel PRO/1000 MT Desktop (82540EM)",
"headless": False,
"enable_remote_console": False,
"linked_base": False,
"server": "local"
}
VBOX_VM_SETTING_TYPES = {
"vmname": str,
"default_symbol": str,
"hover_symbol": str,
"category": int,
"adapters": int,
"adapter_start_index": int,
"adapter_type": str,
"headless": bool,
"enable_remote_console": bool,
"linked_base": bool,
"server": str
}

View File

@@ -28,19 +28,19 @@
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QLabel" name="uiVboxWrapperPathLabel">
<widget class="QLabel" name="uiVboxManagePathLabel">
<property name="text">
<string>Path to the VirtualBox wrapper (Linux/UNIX only):</string>
<string>Path to VBoxManage:</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="uiVboxWrapperPathLineEdit"/>
<widget class="QLineEdit" name="uiVboxManagePathLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiVboxWrapperPathToolButton">
<widget class="QToolButton" name="uiVboxManagePathToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_preferences_page.ui'
#
# Created: Sun Oct 19 11:35:54 2014
# Created: Thu Oct 30 17:14:33 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -36,18 +36,18 @@ class Ui_VirtualBoxPreferencesPageWidget(object):
self.uiGeneralSettingsTabWidget.setObjectName(_fromUtf8("uiGeneralSettingsTabWidget"))
self.gridLayout = QtGui.QGridLayout(self.uiGeneralSettingsTabWidget)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiVboxWrapperPathLabel = QtGui.QLabel(self.uiGeneralSettingsTabWidget)
self.uiVboxWrapperPathLabel.setObjectName(_fromUtf8("uiVboxWrapperPathLabel"))
self.gridLayout.addWidget(self.uiVboxWrapperPathLabel, 2, 0, 1, 1)
self.uiVboxManagePathLabel = QtGui.QLabel(self.uiGeneralSettingsTabWidget)
self.uiVboxManagePathLabel.setObjectName(_fromUtf8("uiVboxManagePathLabel"))
self.gridLayout.addWidget(self.uiVboxManagePathLabel, 2, 0, 1, 1)
self.horizontalLayout_5 = QtGui.QHBoxLayout()
self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5"))
self.uiVboxWrapperPathLineEdit = QtGui.QLineEdit(self.uiGeneralSettingsTabWidget)
self.uiVboxWrapperPathLineEdit.setObjectName(_fromUtf8("uiVboxWrapperPathLineEdit"))
self.horizontalLayout_5.addWidget(self.uiVboxWrapperPathLineEdit)
self.uiVboxWrapperPathToolButton = QtGui.QToolButton(self.uiGeneralSettingsTabWidget)
self.uiVboxWrapperPathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiVboxWrapperPathToolButton.setObjectName(_fromUtf8("uiVboxWrapperPathToolButton"))
self.horizontalLayout_5.addWidget(self.uiVboxWrapperPathToolButton)
self.uiVboxManagePathLineEdit = QtGui.QLineEdit(self.uiGeneralSettingsTabWidget)
self.uiVboxManagePathLineEdit.setObjectName(_fromUtf8("uiVboxManagePathLineEdit"))
self.horizontalLayout_5.addWidget(self.uiVboxManagePathLineEdit)
self.uiVboxManagePathToolButton = QtGui.QToolButton(self.uiGeneralSettingsTabWidget)
self.uiVboxManagePathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiVboxManagePathToolButton.setObjectName(_fromUtf8("uiVboxManagePathToolButton"))
self.horizontalLayout_5.addWidget(self.uiVboxManagePathToolButton)
self.gridLayout.addLayout(self.horizontalLayout_5, 3, 0, 1, 2)
spacerItem = QtGui.QSpacerItem(390, 193, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 5, 0, 1, 2)
@@ -141,8 +141,8 @@ class Ui_VirtualBoxPreferencesPageWidget(object):
def retranslateUi(self, VirtualBoxPreferencesPageWidget):
VirtualBoxPreferencesPageWidget.setWindowTitle(_translate("VirtualBoxPreferencesPageWidget", "VirtualBox", None))
self.uiVboxWrapperPathLabel.setText(_translate("VirtualBoxPreferencesPageWidget", "Path to the VirtualBox wrapper (Linux/UNIX only):", None))
self.uiVboxWrapperPathToolButton.setText(_translate("VirtualBoxPreferencesPageWidget", "&Browse...", None))
self.uiVboxManagePathLabel.setText(_translate("VirtualBoxPreferencesPageWidget", "Path to VBoxManage:", None))
self.uiVboxManagePathToolButton.setText(_translate("VirtualBoxPreferencesPageWidget", "&Browse...", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("VirtualBoxPreferencesPageWidget", "General settings", None))
self.uiUseLocalServercheckBox.setText(_translate("VirtualBoxPreferencesPageWidget", "Always use the local server", None))
self.uiRemoteServersGroupBox.setTitle(_translate("VirtualBoxPreferencesPageWidget", "Remote servers", None))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>514</width>
<height>365</height>
<width>509</width>
<height>346</height>
</rect>
</property>
<property name="windowTitle">
@@ -68,7 +68,7 @@
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="uiEnableConsoleCheckBox">
<property name="text">
<string>Enable console</string>
<string>Enable remote console</string>
</property>
</widget>
</item>
@@ -82,7 +82,17 @@
</property>
</widget>
</item>
<item row="5" column="1">
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Use as a linked base VM</string>
</property>
</widget>
</item>
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_configuration_page.ui'
#
# Created: Fri Oct 10 10:43:48 2014
# Created: Sat Nov 15 15:33:17 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,7 +26,7 @@ except AttributeError:
class Ui_virtualBoxVMConfigPageWidget(object):
def setupUi(self, virtualBoxVMConfigPageWidget):
virtualBoxVMConfigPageWidget.setObjectName(_fromUtf8("virtualBoxVMConfigPageWidget"))
virtualBoxVMConfigPageWidget.resize(514, 365)
virtualBoxVMConfigPageWidget.resize(509, 346)
self.verticalLayout = QtGui.QVBoxLayout(virtualBoxVMConfigPageWidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.uiTabWidget = QtGui.QTabWidget(virtualBoxVMConfigPageWidget)
@@ -66,8 +66,12 @@ class Ui_virtualBoxVMConfigPageWidget(object):
self.uiHeadlessModeCheckBox.setChecked(False)
self.uiHeadlessModeCheckBox.setObjectName(_fromUtf8("uiHeadlessModeCheckBox"))
self.gridLayout.addWidget(self.uiHeadlessModeCheckBox, 4, 0, 1, 2)
self.uiBaseVMCheckBox = QtGui.QCheckBox(self.tab)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName(_fromUtf8("uiBaseVMCheckBox"))
self.gridLayout.addWidget(self.uiBaseVMCheckBox, 5, 0, 1, 2)
spacerItem = QtGui.QSpacerItem(20, 138, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 5, 1, 1, 1)
self.gridLayout.addItem(spacerItem, 6, 1, 1, 1)
self.uiTabWidget.addTab(self.tab, _fromUtf8(""))
self.tab_2 = QtGui.QWidget()
self.tab_2.setObjectName(_fromUtf8("tab_2"))
@@ -115,8 +119,9 @@ class Ui_virtualBoxVMConfigPageWidget(object):
self.uiNameLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Name:", None))
self.uiVMListLabel.setText(_translate("virtualBoxVMConfigPageWidget", "VM name:", None))
self.uiConsolePortLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Console port:", None))
self.uiEnableConsoleCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Enable console", None))
self.uiEnableConsoleCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Enable remote console", None))
self.uiHeadlessModeCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Start VM in headless mode", None))
self.uiBaseVMCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Use as a linked base VM", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("virtualBoxVMConfigPageWidget", "General settings", None))
self.uiAdaptersLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Adapters:", None))
self.uiAdapterStartIndexLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Start at:", None))

View File

@@ -84,7 +84,7 @@
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_preferences_page.ui'
#
# Created: Tue Oct 7 12:02:22 2014
# Created: Wed Nov 19 18:57:20 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -57,7 +57,7 @@ class Ui_VirtualBoxVMPreferencesPageWidget(object):
self.uiVirtualBoxVMsTreeWidget.setSizePolicy(sizePolicy)
self.uiVirtualBoxVMsTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(12)
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiVirtualBoxVMsTreeWidget.setFont(font)

View File

@@ -2,9 +2,6 @@
<ui version="4.0">
<class>VirtualBoxVMWizard</class>
<widget class="QWizard" name="VirtualBoxVMWizard">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
@@ -16,6 +13,9 @@
<property name="windowTitle">
<string>New VirtualBox VM</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>
@@ -126,6 +126,16 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Use as a linked base VM</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_wizard.ui'
#
# Created: Mon Oct 20 14:23:13 2014
# Created: Sat Nov 15 15:29:07 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,8 +26,8 @@ except AttributeError:
class Ui_VirtualBoxVMWizard(object):
def setupUi(self, VirtualBoxVMWizard):
VirtualBoxVMWizard.setObjectName(_fromUtf8("VirtualBoxVMWizard"))
VirtualBoxVMWizard.setWindowModality(QtCore.Qt.WindowModal)
VirtualBoxVMWizard.resize(514, 367)
VirtualBoxVMWizard.setModal(True)
self.uiServerWizardPage = QtGui.QWizardPage()
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
self.gridLayout_2 = QtGui.QGridLayout(self.uiServerWizardPage)
@@ -82,6 +82,10 @@ class Ui_VirtualBoxVMWizard(object):
self.uiVMListComboBox.setSizePolicy(sizePolicy)
self.uiVMListComboBox.setObjectName(_fromUtf8("uiVMListComboBox"))
self.gridLayout.addWidget(self.uiVMListComboBox, 0, 1, 1, 1)
self.uiBaseVMCheckBox = QtGui.QCheckBox(self.uiVirtualBoxWizardPage)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName(_fromUtf8("uiBaseVMCheckBox"))
self.gridLayout.addWidget(self.uiBaseVMCheckBox, 1, 0, 1, 2)
VirtualBoxVMWizard.addPage(self.uiVirtualBoxWizardPage)
self.retranslateUi(VirtualBoxVMWizard)
@@ -100,4 +104,5 @@ class Ui_VirtualBoxVMWizard(object):
self.uiVirtualBoxWizardPage.setTitle(_translate("VirtualBoxVMWizard", "VirtualBox Virtual Machine", None))
self.uiVirtualBoxWizardPage.setSubTitle(_translate("VirtualBoxVMWizard", "Please choose a VirtualBox virtual machine from the list.", None))
self.uiVMListLabel.setText(_translate("VirtualBoxVMWizard", "VM list:", None))
self.uiBaseVMCheckBox.setText(_translate("VirtualBoxVMWizard", "Use as a linked base VM", None))

View File

@@ -22,6 +22,7 @@ VirtualBox VM implementation.
from gns3.node import Node
from gns3.ports.port import Port
from gns3.ports.ethernet_port import EthernetPort
from .settings import VBOX_VM_SETTINGS
import logging
log = logging.getLogger(__name__)
@@ -40,6 +41,7 @@ class VirtualBoxVM(Node):
log.info("VirtualBox VM instance is being created")
self._vbox_id = None
self._linked_clone = False
self._defaults = {}
self._inital_settings = None
self._export_directory = None
@@ -49,11 +51,11 @@ class VirtualBoxVM(Node):
self._settings = {"name": "",
"vmname": "",
"console": None,
"adapters": 2,
"adapter_start_index": 0,
"adapter_type": "Automatic",
"headless": False,
"enable_console": True}
"adapters": VBOX_VM_SETTINGS["adapters"],
"adapter_start_index": VBOX_VM_SETTINGS["adapter_start_index"],
"adapter_type": VBOX_VM_SETTINGS["adapter_type"],
"headless": VBOX_VM_SETTINGS["headless"],
"enable_remote_console": VBOX_VM_SETTINGS["enable_remote_console"]}
self._addAdapters(2)
@@ -77,25 +79,31 @@ class VirtualBoxVM(Node):
self._ports.append(new_port)
log.debug("port {} has been added".format(port_name))
def setup(self, vmname, name=None, console=None, vbox_id=None, initial_settings={}):
def setup(self, vmname, name=None, console=None, vbox_id=None, linked_clone=False, initial_settings={}):
"""
Setups this VirtualBox VM.
:param name: optional name
"""
self._linked_clone = linked_clone
# let's create a unique name if none has been chosen
if not name:
name = vmname
self.setName(name)
#name = self.allocateName("VBOX")
if linked_clone:
name = self.allocateName(vmname + " ")
else:
name = vmname
self.setName(name)
if not name:
self.error_signal.emit(self.id(), "could not allocate a name for this VirtualBox VM")
return
params = {"name": name,
"vmname": vmname}
"vmname": vmname,
"linked_clone": linked_clone}
if console:
params["console"] = self._settings["console"] = console
@@ -469,7 +477,7 @@ class VirtualBoxVM(Node):
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}".format(e))
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
@@ -547,6 +555,7 @@ class VirtualBoxVM(Node):
vbox_vm = {"id": self.id(),
"vbox_id": self._vbox_id,
"linked_clone": self._linked_clone,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
@@ -575,6 +584,7 @@ class VirtualBoxVM(Node):
self.node_info = node_info
vbox_id = node_info.get("vbox_id")
linked_clone = node_info.get("linked_clone", False)
settings = node_info["properties"]
name = settings.pop("name")
vmname = settings.pop("vmname")
@@ -584,7 +594,7 @@ class VirtualBoxVM(Node):
self._loading = True
log.info("VirtualBox VM {} is loading".format(name))
self.setName(name)
self.setup(vmname, name, console, vbox_id, settings)
self.setup(vmname, name, console, vbox_id, linked_clone, settings)
def _updatePortSettings(self):
"""
@@ -636,6 +646,17 @@ class VirtualBoxVM(Node):
return self._ports
def serialConsole(self):
"""
Returns either the serial console must be used or not.
:return: boolean
"""
if self._settings["enable_remote_console"]:
return False
return True
def console(self):
"""
Returns the console port for this VirtualBox VM instance.

View File

@@ -171,6 +171,8 @@ class VPCS(Module):
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send VPCS path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]

View File

@@ -36,6 +36,7 @@ class NewsDockWidget(QtGui.QDockWidget, Ui_NewsDockWidget):
QtGui.QDockWidget.__init__(self, parent)
self.setupUi(self)
self.visibilityChanged.connect(self._visibilityChangedSlot)
self.uiWebView.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self.uiWebView.loadFinished.connect(self._loadFinishedSlot)
@@ -46,7 +47,32 @@ class NewsDockWidget(QtGui.QDockWidget, Ui_NewsDockWidget):
self._timer.timeout.connect(self._loadFinishedSlot)
self._timer.setSingleShot(True)
self._timer.start(5000)
self.uiWebView.load(QtCore.QUrl("http://as.gns3.com/software/docked.html"))
if parent.settings()["default_local_news"]:
self._loadFinishedSlot()
else:
self.uiWebView.load(QtCore.QUrl("http://as.gns3.com/software/docked_200x200.html"))
def _visibilityChangedSlot(self, visible):
"""
Slot for visibility changed signal.
:param visible: either the dock is visible or not
"""
self._visible = visible
def isVisible(self):
return self._visible
def closeEvent(self, event):
"""
You really cannot close that dock (using ATL+F4...)
:param event: closeEvent instance.
"""
event.ignore()
def _refreshSlot(self):
"""
@@ -76,12 +102,17 @@ class NewsDockWidget(QtGui.QDockWidget, Ui_NewsDockWidget):
self._timer.stop()
self._timer.timeout.disconnect()
if result is False:
self._refresh_timer.stop()
# load a local resource if the page is not available
resource_name = os.path.join("static", "gns3_jungle.html")
gns3_jungle = None
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
gns3_jungle = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
gns3_jungle_page = pkg_resources.resource_filename("gns3", resource_name)
gns3_jungle = os.path.normpath(gns3_jungle_page)
self.uiWebView.load(QtCore.QUrl("file://{}".format(gns3_jungle)))
self._refresh_timer.stop()
if gns3_jungle and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl("file://{}".format(gns3_jungle)))
else:
self.hide()

View File

@@ -23,7 +23,7 @@ import os
import shutil
from gns3.qt import QtGui, QtCore
from ..ui.general_preferences_page_ui import Ui_GeneralPreferencesPageWidget
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, PRECONFIGURED_TELNET_CONSOLE_COMMANDS, PRECONFIGURED_SERIAL_CONSOLE_COMMANDS
from ..settings import GRAPHICS_VIEW_SETTINGS, GENERAL_SETTINGS, PRECONFIGURED_TELNET_CONSOLE_COMMANDS, PRECONFIGURED_SERIAL_CONSOLE_COMMANDS, STYLES
class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
@@ -58,6 +58,7 @@ class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
self.uiDefaultLabelFontPushButton.clicked.connect(self._setDefaultLabelFontSlot)
self.uiDefaultLabelColorPushButton.clicked.connect(self._setDefaultLabelColorSlot)
self._default_label_color = QtGui.QColor(QtCore.Qt.black)
self.uiStyleComboBox.addItems(STYLES)
def _projectsPathSlot(self):
"""
@@ -195,11 +196,15 @@ class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
self.uiProjectsPathLineEdit.setText(settings["projects_path"])
self.uiImagesPathLineEdit.setText(settings["images_path"])
self.uiTemporaryFilesPathLineEdit.setText(settings["temporary_files_path"])
self.uiLaunchNewProjectDialogCheckBox.setChecked(settings["auto_launch_project_dialog"])
self.uiCheckForUpdateCheckBox.setChecked(settings["check_for_update"])
self.uiLinkManualModeCheckBox.setChecked(settings["link_manual_mode"])
self.uiSlowStartAllSpinBox.setValue(settings["slow_device_start_all"])
self.uiTelnetConsoleCommandLineEdit.setText(settings["telnet_console_command"])
self.uiTelnetConsoleCommandLineEdit.setCursorPosition(0)
index = self.uiStyleComboBox.findText(settings["style"])
if index != -1:
self.uiStyleComboBox.setCurrentIndex(index)
index = self.uiTelnetConsolePreconfiguredCommandComboBox.findData(settings["telnet_console_command"])
if index != -1:
self.uiTelnetConsolePreconfiguredCommandComboBox.setCurrentIndex(index)
@@ -210,7 +215,7 @@ class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
self.uiSerialConsolePreconfiguredCommandComboBox.setCurrentIndex(index)
self.uiCloseConsoleWindowsOnDeleteCheckBox.setChecked(settings["auto_close_console"])
self.uiBringConsoleWindowToFrontCheckBox.setChecked(settings["bring_console_to_front"])
self.uiSlowConsoleAllDoubleSpinBox.setValue(settings["slow_console_all"])
self.uiDelayConsoleAllSpinBox.setValue(settings["delay_console_all"])
def _populateGraphicsViewSettingWidgets(self, settings):
"""
@@ -253,6 +258,8 @@ class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
new_settings["projects_path"] = self.uiProjectsPathLineEdit.text()
new_settings["images_path"] = self.uiImagesPathLineEdit.text()
new_settings["temporary_files_path"] = self.uiTemporaryFilesPathLineEdit.text()
new_settings["auto_launch_project_dialog"] = self.uiLaunchNewProjectDialogCheckBox.isChecked()
new_settings["style"] = self.uiStyleComboBox.currentText()
new_settings["check_for_update"] = self.uiCheckForUpdateCheckBox.isChecked()
new_settings["link_manual_mode"] = self.uiLinkManualModeCheckBox.isChecked()
new_settings["slow_device_start_all"] = self.uiSlowStartAllSpinBox.value()
@@ -260,7 +267,7 @@ class GeneralPreferencesPage(QtGui.QWidget, Ui_GeneralPreferencesPageWidget):
new_settings["serial_console_command"] = self.uiSerialConsoleCommandLineEdit.text()
new_settings["auto_close_console"] = self.uiCloseConsoleWindowsOnDeleteCheckBox.isChecked()
new_settings["bring_console_to_front"] = self.uiBringConsoleWindowToFrontCheckBox.isChecked()
new_settings["slow_console_all"] = self.uiSlowConsoleAllDoubleSpinBox.value()
new_settings["delay_console_all"] = self.uiDelayConsoleAllSpinBox.value()
from ..main_window import MainWindow
MainWindow.instance().setSettings(new_settings)

View File

@@ -179,6 +179,7 @@ class ServerPreferencesPage(QtGui.QWidget, Ui_ServerPreferencesPageWidget):
self.uiLocalServerPortSpinBox.setValue(local_server.port)
self.uiLocalServerPathLineEdit.setText(servers.localServerPath())
self.uiLocalServerAutoStartCheckBox.setChecked(servers.localServerAutoStart())
self.uiConsoleConnectionsToAnyIPCheckBox.setChecked(servers.localServerAllowConsoleFromAnywhere())
# load remote server preferences
self._remote_servers.clear()
@@ -206,6 +207,7 @@ class ServerPreferencesPage(QtGui.QWidget, Ui_ServerPreferencesPageWidget):
local_server_port = self.uiLocalServerPortSpinBox.value()
local_server_path = self.uiLocalServerPathLineEdit.text()
local_server_auto_start = self.uiLocalServerAutoStartCheckBox.isChecked()
local_server_allow_console_from_anywhere = self.uiConsoleConnectionsToAnyIPCheckBox.isChecked()
if local_server_path:
if not os.path.isfile(local_server_path):
@@ -214,7 +216,10 @@ class ServerPreferencesPage(QtGui.QWidget, Ui_ServerPreferencesPageWidget):
QtGui.QMessageBox.critical(self, "Local server", "{} is not an executable".format(local_server_path))
else:
server = servers.localServer()
if servers.localServerPath() != local_server_path or server.host != local_server_host or server.port != local_server_port:
if servers.localServerPath() != local_server_path or \
server.host != local_server_host or \
server.port != local_server_port or \
servers.localServerAllowConsoleFromAnywhere() != local_server_allow_console_from_anywhere:
# first check if we have nodes on the local server
local_nodes = []
@@ -228,6 +233,12 @@ class ServerPreferencesPage(QtGui.QWidget, Ui_ServerPreferencesPageWidget):
MessageBox(self, "Local server", "Please close your project or delete all the nodes running on the local server before changing settings", nodes)
return
servers.setLocalServer(local_server_path,
local_server_host,
local_server_port,
local_server_auto_start,
local_server_allow_console_from_anywhere)
# local server settings have changed, let's stop the current local server.
if server.connected() and not sys.platform.startswith('win'):
server.close_connection()
@@ -240,8 +251,12 @@ class ServerPreferencesPage(QtGui.QWidget, Ui_ServerPreferencesPageWidget):
dialog.exec_()
else:
QtGui.QMessageBox.critical(self, "Local server", "Could not start the local server process: {}".format(local_server_path))
servers.setLocalServer(local_server_path, local_server_host, local_server_port, local_server_auto_start)
else:
servers.setLocalServer(local_server_path,
local_server_host,
local_server_port,
local_server_auto_start,
local_server_allow_console_from_anywhere)
# save the remote server preferences
servers.updateRemoteServers(self._remote_servers)

View File

@@ -31,8 +31,11 @@ import logging
log = logging.getLogger(__name__)
def serialConsole(name, vmname):
#TODO: support more than just Vbox (Qemu maybe?)
def serialConsole(vmname):
"""
:param vmname: Virtual machine name.
Start a Serial console program.
"""
@@ -49,7 +52,7 @@ def serialConsole(name, vmname):
# replace the place-holders by the actual values
command = command.replace("%s", pipe_name)
command = command.replace("%d", name)
command = command.replace("%d", vmname)
log.info('starting serial console "{}"'.format(command))
try:

View File

@@ -53,6 +53,7 @@ class Servers(QtCore.QObject):
self._cloud_servers = {}
self._local_server_path = ""
self._local_server_auto_start = True
self._local_server_allow_console_from_anywhere = False
self._local_server_proccess = None
self._settings = self._loadSettings()
self._remote_server_iter_pos = 0
@@ -78,8 +79,15 @@ class Servers(QtCore.QObject):
local_server_port = settings.value("local_server_port", DEFAULT_LOCAL_SERVER_PORT, type=int)
local_server_path = settings.value("local_server_path", DEFAULT_LOCAL_SERVER_PATH)
local_server_auto_start = settings.value("local_server_auto_start", True, type=bool)
local_server_allow_console_from_anywhere = settings.value("local_server_allow_console_from_anywhere", False, type=bool)
heartbeat_freq = settings.value("heartbeat_freq", DEFAULT_HEARTBEAT_FREQ, type=int)
self.setLocalServer(local_server_path, local_server_host, local_server_port, local_server_auto_start, heartbeat_freq)
self.setLocalServer(local_server_path,
local_server_host,
local_server_port,
local_server_auto_start,
local_server_allow_console_from_anywhere,
heartbeat_freq)
# load the remote servers
size = settings.beginReadArray("remote")
@@ -109,6 +117,7 @@ class Servers(QtCore.QObject):
settings.setValue("local_server_port", self._local_server.port)
settings.setValue("local_server_path", self._local_server_path)
settings.setValue("local_server_auto_start", self._local_server_auto_start)
settings.setValue("local_server_allow_console_from_anywhere", self._local_server_allow_console_from_anywhere)
# save the remote servers
settings.beginWriteArray("remote", len(self._remote_servers))
@@ -131,6 +140,16 @@ class Servers(QtCore.QObject):
return self._local_server_auto_start
def localServerAllowConsoleFromAnywhere(self):
"""
Returns either the local server
is allows console connections to any local IP address.
:returns: boolean
"""
return self._local_server_allow_console_from_anywhere
def localServerPath(self):
"""
Returns the local server path.
@@ -145,7 +164,10 @@ class Servers(QtCore.QObject):
Starts the local server process.
"""
command = '"{executable}" --host={host} --port={port}'.format(executable=path, host=host, port=port)
command = '"{executable}" --host={host} --port={port} --console_bind_to_any={bind}'.format(executable=path,
host=host,
port=port,
bind=self._local_server_allow_console_from_anywhere)
# settings_dir = os.path.dirname(QtCore.QSettings().fileName())
# if os.path.isdir(settings_dir):
@@ -180,18 +202,18 @@ class Servers(QtCore.QObject):
def stopLocalServer(self, wait=False):
if self._local_server and self._local_server.connected() and not sys.platform.startswith('win'):
# only gracefully disconnect if we are not on Windows
self._local_server.close_connection()
#if self._local_server and self._local_server.connected() and not sys.platform.startswith('win'):
# # only gracefully disconnect if we are not on Windows
# self._local_server.close_connection()
if self._local_server_proccess and self._local_server_proccess.poll() is None:
if sys.platform.startswith("win"):
self._local_server_proccess.send_signal(signal.CTRL_BREAK_EVENT)
else:
self._local_server_proccess.send_signal(signal.SIGINT)
if wait:
self._local_server_proccess.wait()
self._local_server_proccess.wait(timeout=3000)
def setLocalServer(self, path, host, port, auto_start, heartbeat_freq=DEFAULT_HEARTBEAT_FREQ):
def setLocalServer(self, path, host, port, auto_start, allow_console_from_anywhere, heartbeat_freq=DEFAULT_HEARTBEAT_FREQ):
"""
Sets the local server.
@@ -200,11 +222,13 @@ class Servers(QtCore.QObject):
:param port: port of the server (integer)
:param auto_start: either the local server should be
automatically started on startup (boolean)
:param allow_console_from_anywhere: allow console connections to any local IP address
:param heartbeat_freq: The interval to send heartbeats to the server
"""
self._local_server_path = path
self._local_server_auto_start = auto_start
self._local_server_allow_console_from_anywhere = allow_console_from_anywhere
if self._local_server:
if self._local_server.host == host and self._local_server.port == port:
return
@@ -215,7 +239,6 @@ class Servers(QtCore.QObject):
url = "ws://{host}:{port}".format(host=host, port=port)
self._local_server = WebSocketClient(url)
self._local_server.setLocal(True)
self._local_server.enableHeartbeatsAt(heartbeat_freq)
log.info("new local server connection {} registered".format(url))
def localServer(self):
@@ -242,15 +265,22 @@ class Servers(QtCore.QObject):
server_socket = "{host}:{port}".format(host=host, port=port)
url = "ws://{server_socket}".format(server_socket=server_socket)
server = WebSocketClient(url)
server.enableHeartbeatsAt(heartbeat_freq)
self._remote_servers[server_socket] = server
log.info("new remote server connection {} registered".format(url))
return server
def getRemoteServer(self, host, port):
"""
Gets a remote server.
:param host: host address
:param port: port
:returns: remote server (WebSocketClient instance)
"""
for server in self._remote_servers.values():
if server.host == host and server.port == port:
if server.host == host and int(server.port) == int(port):
return server
return self._addRemoteServer(host, port)
@@ -291,7 +321,7 @@ class Servers(QtCore.QObject):
return self._remote_servers
def getCloudServer(self, host, port, ca_file):
def getCloudServer(self, host, port, ca_file, auth_user, auth_password, ssh_pkey, instance_id):
"""
Return a websocket connection to the cloud server, creating one if none exists.
@@ -302,13 +332,15 @@ class Servers(QtCore.QObject):
"""
for server in self._remote_servers.values():
if server.host == host and server.port == port:
if server.host == host and int(server.port) == int(port):
return server
heartbeat_freq = self._settings.value("heartbeat_freq", DEFAULT_HEARTBEAT_FREQ)
return self._addCloudServer(host, port, ca_file, heartbeat_freq)
def _addCloudServer(self, host, port, ca_file, heartbeat_freq):
return self._addCloudServer(host, port, ca_file, auth_user, auth_password, ssh_pkey,
heartbeat_freq, instance_id)
def _addCloudServer(self, host, port, ca_file, auth_user, auth_password, ssh_pkey,
heartbeat_freq, instance_id):
"""
Create a websocket connection to the specified cloud server
@@ -323,12 +355,21 @@ class Servers(QtCore.QObject):
url = "wss://{host}:{port}".format(host=host, port=port)
log.debug('Starting SecureWebSocketClient url={}'.format(url))
log.debug('Starting SecureWebSocketClient ca_file={}'.format(ca_file))
server = SecureWebSocketClient(url, ca_file)
log.debug('Starting SecureWebSocketClient ssh_pkey={}'.format(ssh_pkey))
server = SecureWebSocketClient(url, instance_id=instance_id)
server.setSecureOptions(ca_file, auth_user, auth_password, ssh_pkey)
server.setCloud(True)
server.enableHeartbeatsAt(heartbeat_freq)
self._cloud_servers[host] = server
log.info("new remote server connection {} registered".format(url))
return server
def anyCloudServer(self):
# Return the first server for now
for key, value in self._cloud_servers.items():
return value
return None
def __iter__(self):
"""
Creates a round-robin system to pick up a remote server.

View File

@@ -87,22 +87,48 @@ elif sys.platform.startswith("win"):
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Terminal': "/usr/bin/osascript -e 'tell application \"terminal\" to do script with command \"telnet %h %p ; exit\"'",
'iTerm': "/usr/bin/osascript -e 'tell app \"iTerm\"' -e 'activate' -e 'set myterm to the first terminal' -e 'tell myterm' -e 'set mysession to (make new session at the end of sessions)' -e 'tell mysession' -e 'exec command \"telnet %h %p\"' -e 'set name to \"%d\"' -e 'end tell' -e 'end tell' -e 'end tell'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /ARG %d /T /TELNET %h %p'}
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {
'Terminal': "osascript -e 'tell application \"Terminal\"'"
" -e 'activate'"
" -e 'set _tab to do script \"telnet %h %p ; exit\"'"
" -e 'delay 1'"
" -e 'repeat while _tab exists'"
" -e 'delay 1'"
" -e 'end repeat'"
" -e 'end tell'",
'iTerm': "osascript -e 'tell application \"iTerm\"'"
" -e 'activate'"
" -e 'if (count of terminals) = 0 then'"
" -e ' set t to (make new terminal)'"
" -e 'else'"
" -e ' set t to current terminal'"
" -e 'end if'"
" -e 'tell t'"
" -e ' set s to (make new session at the end of sessions)'"
" -e ' tell s'"
" -e ' exec command (\"telnet %h %p\")'"
" -e ' delay 1'"
" -e ' repeat while s exists'"
" -e ' delay 1'"
" -e ' end repeat'"
" -e ' end tell'"
" -e 'end tell'"
" -e 'end tell'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /ARG %d /T /TELNET %h %p'
}
# default Mac OS X Telnet console command
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Terminal"]
else:
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T %d -e \'telnet %h %p\'',
'Putty': 'putty -telnet %h %p -title %d -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal -t %d -e \'telnet %h %p\'',
'Xfce4 Terminal': 'xfce4-terminal -T %d -e \'telnet %h %p\'',
'ROXTerm': 'roxterm -n %d --tab -e telnet %h %p',
'KDE Konsole': 'konsole --new-tab -p tabtitle=%d -e telnet %h %p',
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "%d" -e "telnet %h %p"',
'Putty': 'putty -telnet %h %p -title "%d" -sl 2500 -fg SALMON1 -bg BLACK',
'Gnome Terminal': 'gnome-terminal -t "%d" -e "telnet %h %p"',
'Xfce4 Terminal': 'xfce4-terminal -T "%d" -e "telnet %h %p"',
'ROXTerm': 'roxterm -n "%d" --tab -e "telnet %h %p"',
'KDE Konsole': 'konsole --new-tab -p tabtitle="%d" -e "telnet %h %p"',
'SecureCRT': 'SecureCRT /T /N "%d" /TELNET %h %p',
'Mate Terminal': 'mate-terminal --tab -e \'telnet %h %p\' -t %d'}
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"'}
# default Telnet console command on other systems
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Xterm"]
@@ -132,9 +158,9 @@ elif sys.platform.startswith("darwin"):
DEFAULT_SERIAL_CONSOLE_COMMAND = PRECONFIGURED_SERIAL_CONSOLE_COMMANDS["Terminal + socat"]
else:
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS = {'Xterm + socat': 'xterm -T %d -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\'',
'Gnome Terminal + socat': 'gnome-terminal -t %d -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\'',
'Konsole + socat': 'konsole --new-tab -p tabtitle=%d -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\''}
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS = {'Xterm + socat': 'xterm -T "%d" -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\'',
'Gnome Terminal + socat': 'gnome-terminal -t "%d" -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\'',
'Konsole + socat': 'konsole --new-tab -p tabtitle="%d" -e \'socat UNIX-CONNECT:"%s" stdio,raw,echo=0\''}
# default serial console command on other systems
DEFAULT_SERIAL_CONSOLE_COMMAND = PRECONFIGURED_SERIAL_CONSOLE_COMMANDS["Xterm + socat"]
@@ -172,10 +198,14 @@ if sys.platform.startswith("win") and "PROGRAMFILES(X86)" in os.environ and os.p
# Windows 64-bit
DEFAULT_PACKET_CAPTURE_ANALYZER_COMMAND = r'"C:\Program Files (x86)\SolarWinds\ResponseTimeViewer\ResponseTimeViewer.exe" %c'
STYLES = ["Charcoal (default)", "Classic", "Legacy"]
GENERAL_SETTINGS = {
"projects_path": DEFAULT_PROJECTS_PATH,
"images_path": DEFAULT_IMAGES_PATH,
"temporary_files_path": DEFAULT_TEMPORARY_FILES_PATH,
"style": STYLES[0],
"auto_launch_project_dialog": True,
"check_for_update": True,
"last_check_for_update": 0,
"slow_device_start_all": 0,
@@ -184,13 +214,16 @@ GENERAL_SETTINGS = {
"serial_console_command": DEFAULT_SERIAL_CONSOLE_COMMAND,
"auto_close_console": True,
"bring_console_to_front": True,
"slow_console_all": 0.5,
"delay_console_all": 500,
"default_local_news": False,
}
GENERAL_SETTING_TYPES = {
"projects_path": str,
"images_path": str,
"temporary_files_path": str,
"style": str,
"auto_launch_project_dialog": bool,
"check_for_update": bool,
"last_check_for_update": int,
"slow_device_start_all": int,
@@ -199,7 +232,8 @@ GENERAL_SETTING_TYPES = {
"serial_console_command": str,
"auto_close_console": bool,
"bring_console_to_front": bool,
"slow_console_all": float,
"delay_console_all": int,
"default_local_news": bool,
}
GRAPHICS_VIEW_SETTINGS = {
@@ -232,6 +266,8 @@ PACKET_CAPTURE_SETTING_TYPES = {
"packet_capture_analyzer_command": str,
}
ENABLE_CLOUD = False
CLOUD_SETTINGS = {
"cloud_user_name": "",
"cloud_api_key": "",

View File

@@ -3,7 +3,7 @@
<title>GNS3 Jungle</title>
</head>
<body leftmargin=0 marginwidth=0 topmargin=0 marginheight=0>
<a href="https://community.gns3.com"><img width="300" height="250" src="images/gns3_jungle.jpg"></a>
<a href="https://community.gns3.com"><img width="200" height="200" src="images/gns3_jungle.jpg"></a>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -18,6 +18,7 @@
"""
Functions to start external console terminals.
"""
from PyQt4.QtCore import QThread, pyqtSignal
import sys
import shlex
@@ -28,7 +29,38 @@ import logging
log = logging.getLogger(__name__)
def telnetConsole(name, host, port):
class ConsoleThread(QThread):
"""
"""
consoleDone = pyqtSignal(str, str, int)
def __init__(self, parent, command, name, host, port):
super(QThread, self).__init__(parent)
self._command = command
self._name = name
self._host = host
self._port = port
def run(self):
try:
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.call(self._command)
else:
# use arguments on other platforms
args = shlex.split(self._command)
subprocess.call(args)
# emit signal upon completion
self.consoleDone.emit(self._name, self._host, self._port)
except (OSError, ValueError) as e:
log.warning('could not start Telnet console "{}": {}'.format(self._command, e))
raise
def telnetConsole(name, host, port, callback=None):
"""
Start a Telnet console program.
@@ -46,14 +78,8 @@ def telnetConsole(name, host, port):
command = command.replace("%d", name)
log.info('starting telnet console "{}"'.format(command))
try:
if sys.platform.startswith("win"):
# use the string on Windows
subprocess.Popen(command)
else:
# use arguments on other platforms
args = shlex.split(command)
subprocess.Popen(args)
except (OSError, ValueError) as e:
log.warning('could not start Telnet console "{}": {}'.format(command, e))
raise
console_thread = ConsoleThread(MainWindow.instance(), command, name, host, port)
if callback is not None:
console_thread.consoleDone.connect(callback)
console_thread.start()

View File

@@ -21,7 +21,6 @@ Handles the saving and loading of a topology.
"""
import os
from collections import namedtuple
from .qt import QtCore, QtGui, QtSvg
from .items.node_item import NodeItem
@@ -35,15 +34,40 @@ from .modules import MODULES
from .modules.module_error import ModuleError
from .utils.message_box import MessageBox
from .version import __version__
from pkg_resources import parse_version
import logging
log = logging.getLogger(__name__)
TopologyInstance = namedtuple("TopologyInstance",
["name", "id", "size_id", "image_id", "private_key", "public_key"],
verbose=False)
class TopologyInstance:
def __init__(self, name, id, size_id, image_id, private_key, public_key,
host=None, port=None, ssl_ca=None, ssl_ca_file=None):
# host, port, ssl_ca and ssl_ca_file are not known when the instance is created.
# They will typically be set at a later point in time.
self.name = name
self.id = id
self.size_id = size_id
self.image_id = image_id
self.public_key = public_key
self.private_key = private_key
self.host = host
self.port = port
self.ssl_ca = ssl_ca
self.ssl_ca_file = ssl_ca_file
@classmethod
def fields(cls):
return ["name", "id", "size_id", "image_id", "private_key", "public_key",
"host", "port", "ssl_ca", "ssl_ca_file"]
def set_later_attributes(self, host, port, ssl_ca, ssl_ca_file):
"""
Set attributes that are not known at the time of cloud instance creation.
"""
self.host = host
self.port = port
self.ssl_ca = ssl_ca
self.ssl_ca_file = ssl_ca_file
class Topology(object):
"""
@@ -203,15 +227,21 @@ class Topology(object):
if image in self._images:
self._images.remove(image)
def addInstance(self, name, id, size_id, image_id, private_key, public_key):
def addInstance(self, name, id, size_id, image_id, private_key, public_key,
host=None, port=None, ssl_ca=None, ssl_ca_file=None):
"""
Add an instance to this cloud topology
"""
i = TopologyInstance(name=name, id=id, size_id=size_id, image_id=image_id,
private_key=private_key, public_key=public_key)
private_key=private_key, public_key=public_key, host=host,
port=port, ssl_ca=ssl_ca, ssl_ca_file=ssl_ca_file)
self._instances.append(i)
def addInstance2(self, topology_instance):
self._instances.append(topology_instance)
def removeInstance(self, id):
"""
Removes an instance from this cloud topology
@@ -236,6 +266,10 @@ class Topology(object):
if instance.id == id:
return instance
def anyInstance(self):
# For now, just return the first instance
return self._instances[0]
def nodes(self):
"""
Returns all the nodes in this topology.

View File

@@ -4,7 +4,7 @@ import socket
import paramiko
import logging
from io import StringIO
from endpoint import Endpoint
from .endpoint import Endpoint
log = logging.getLogger(__name__)
@@ -72,7 +72,7 @@ class Tunnel(object):
if hasattr(data, 'readlines'):
key_file = data
else:
key_file = StringIO.StringIO()
key_file = StringIO()
key_file.write(data)
key_file.flush()
key_file.seek(0)
@@ -121,11 +121,11 @@ class Tunnel(object):
new_endpoint.enable()
self.end_points[new_endpoint.getId()] = new_endpoint
return new_endpoint.getId()
return new_endpoint
def remove_endpoint(self, name):
if name in self.end_points:
self.end_points[name].disable()
def remove_endpoint(self, endpoint):
if endpoint.getId() in self.end_points:
self.end_points[endpoint.getId()].disable()
def list_endpoints(self):
remotes = {}

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>621</height>
<width>467</width>
<height>639</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,8 +23,8 @@
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0" colspan="2">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="uiLocalPathsGroupBox">
<property name="title">
<string>Local paths</string>
@@ -117,7 +117,19 @@
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item>
<widget class="QGroupBox" name="uiStyleGroupBox">
<property name="title">
<string>Style</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QComboBox" name="uiStyleComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiConfigurationFileGroupBox">
<property name="title">
<string>Configuration file</string>
@@ -164,13 +176,13 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item>
<widget class="QGroupBox" name="uiGeneralMiscGroupBox">
<property name="title">
<string>Miscellaneous</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" rowspan="2" colspan="2">
<item row="1" column="0" rowspan="2" colspan="2">
<widget class="QCheckBox" name="uiCheckForUpdateCheckBox">
<property name="text">
<string>Automatically check for update</string>
@@ -180,24 +192,30 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="uiSlowStartAllLabel">
<property name="text">
<string>Delay between each device start when starting all devices:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="QSpinBox" name="uiSlowStartAllSpinBox">
<property name="suffix">
<string> seconds</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QCheckBox" name="uiLinkManualModeCheckBox">
<property name="text">
<string>Always use manual mode when adding links</string>
@@ -207,10 +225,20 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="uiLaunchNewProjectDialogCheckBox">
<property name="text">
<string>Launch the new project dialog on startup</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -353,21 +381,15 @@
</widget>
</item>
<item row="3" column="0">
<widget class="QDoubleSpinBox" name="uiSlowConsoleAllDoubleSpinBox">
<widget class="QSpinBox" name="uiDelayConsoleAllSpinBox">
<property name="suffix">
<string> seconds</string>
<string> ms</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<double>1.000000000000000</double>
<number>500</number>
</property>
</widget>
</item>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
#
# Created: Fri Oct 10 10:43:47 2014
# Created: Mon Nov 17 18:55:08 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,15 +26,15 @@ except AttributeError:
class Ui_GeneralPreferencesPageWidget(object):
def setupUi(self, GeneralPreferencesPageWidget):
GeneralPreferencesPageWidget.setObjectName(_fromUtf8("GeneralPreferencesPageWidget"))
GeneralPreferencesPageWidget.resize(502, 621)
GeneralPreferencesPageWidget.resize(467, 639)
self.verticalLayout = QtGui.QVBoxLayout(GeneralPreferencesPageWidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.uiTabWidget = QtGui.QTabWidget(GeneralPreferencesPageWidget)
self.uiTabWidget.setObjectName(_fromUtf8("uiTabWidget"))
self.uiGeneralTab = QtGui.QWidget()
self.uiGeneralTab.setObjectName(_fromUtf8("uiGeneralTab"))
self.gridLayout_8 = QtGui.QGridLayout(self.uiGeneralTab)
self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8"))
self.verticalLayout_5 = QtGui.QVBoxLayout(self.uiGeneralTab)
self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5"))
self.uiLocalPathsGroupBox = QtGui.QGroupBox(self.uiGeneralTab)
self.uiLocalPathsGroupBox.setObjectName(_fromUtf8("uiLocalPathsGroupBox"))
self.gridLayout_3 = QtGui.QGridLayout(self.uiLocalPathsGroupBox)
@@ -78,7 +78,15 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiTemporaryFilesPathToolButton.setObjectName(_fromUtf8("uiTemporaryFilesPathToolButton"))
self.horizontalLayout_3.addWidget(self.uiTemporaryFilesPathToolButton)
self.gridLayout_3.addLayout(self.horizontalLayout_3, 5, 0, 1, 1)
self.gridLayout_8.addWidget(self.uiLocalPathsGroupBox, 0, 0, 1, 2)
self.verticalLayout_5.addWidget(self.uiLocalPathsGroupBox)
self.uiStyleGroupBox = QtGui.QGroupBox(self.uiGeneralTab)
self.uiStyleGroupBox.setObjectName(_fromUtf8("uiStyleGroupBox"))
self.verticalLayout_4 = QtGui.QVBoxLayout(self.uiStyleGroupBox)
self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4"))
self.uiStyleComboBox = QtGui.QComboBox(self.uiStyleGroupBox)
self.uiStyleComboBox.setObjectName(_fromUtf8("uiStyleComboBox"))
self.verticalLayout_4.addWidget(self.uiStyleComboBox)
self.verticalLayout_5.addWidget(self.uiStyleGroupBox)
self.uiConfigurationFileGroupBox = QtGui.QGroupBox(self.uiGeneralTab)
self.uiConfigurationFileGroupBox.setObjectName(_fromUtf8("uiConfigurationFileGroupBox"))
self.gridLayout = QtGui.QGridLayout(self.uiConfigurationFileGroupBox)
@@ -97,7 +105,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.gridLayout.addWidget(self.uiConfigurationFileLabel, 0, 0, 1, 1)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 1, 1, 1, 1)
self.gridLayout_8.addWidget(self.uiConfigurationFileGroupBox, 1, 0, 1, 2)
self.verticalLayout_5.addWidget(self.uiConfigurationFileGroupBox)
self.uiGeneralMiscGroupBox = QtGui.QGroupBox(self.uiGeneralTab)
self.uiGeneralMiscGroupBox.setObjectName(_fromUtf8("uiGeneralMiscGroupBox"))
self.gridLayout_2 = QtGui.QGridLayout(self.uiGeneralMiscGroupBox)
@@ -105,21 +113,27 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiCheckForUpdateCheckBox = QtGui.QCheckBox(self.uiGeneralMiscGroupBox)
self.uiCheckForUpdateCheckBox.setChecked(True)
self.uiCheckForUpdateCheckBox.setObjectName(_fromUtf8("uiCheckForUpdateCheckBox"))
self.gridLayout_2.addWidget(self.uiCheckForUpdateCheckBox, 0, 0, 2, 2)
self.gridLayout_2.addWidget(self.uiCheckForUpdateCheckBox, 1, 0, 2, 2)
self.uiSlowStartAllLabel = QtGui.QLabel(self.uiGeneralMiscGroupBox)
self.uiSlowStartAllLabel.setObjectName(_fromUtf8("uiSlowStartAllLabel"))
self.gridLayout_2.addWidget(self.uiSlowStartAllLabel, 3, 0, 1, 2)
self.gridLayout_2.addWidget(self.uiSlowStartAllLabel, 4, 0, 1, 2)
self.uiSlowStartAllSpinBox = QtGui.QSpinBox(self.uiGeneralMiscGroupBox)
self.uiSlowStartAllSpinBox.setMinimum(0)
self.uiSlowStartAllSpinBox.setMaximum(10000)
self.uiSlowStartAllSpinBox.setProperty("value", 0)
self.uiSlowStartAllSpinBox.setObjectName(_fromUtf8("uiSlowStartAllSpinBox"))
self.gridLayout_2.addWidget(self.uiSlowStartAllSpinBox, 4, 0, 1, 2)
self.gridLayout_2.addWidget(self.uiSlowStartAllSpinBox, 5, 0, 1, 2)
self.uiLinkManualModeCheckBox = QtGui.QCheckBox(self.uiGeneralMiscGroupBox)
self.uiLinkManualModeCheckBox.setChecked(True)
self.uiLinkManualModeCheckBox.setObjectName(_fromUtf8("uiLinkManualModeCheckBox"))
self.gridLayout_2.addWidget(self.uiLinkManualModeCheckBox, 2, 0, 1, 1)
self.gridLayout_8.addWidget(self.uiGeneralMiscGroupBox, 2, 0, 1, 2)
self.gridLayout_2.addWidget(self.uiLinkManualModeCheckBox, 3, 0, 1, 1)
self.uiLaunchNewProjectDialogCheckBox = QtGui.QCheckBox(self.uiGeneralMiscGroupBox)
self.uiLaunchNewProjectDialogCheckBox.setChecked(True)
self.uiLaunchNewProjectDialogCheckBox.setObjectName(_fromUtf8("uiLaunchNewProjectDialogCheckBox"))
self.gridLayout_2.addWidget(self.uiLaunchNewProjectDialogCheckBox, 0, 0, 1, 1)
self.verticalLayout_5.addWidget(self.uiGeneralMiscGroupBox)
spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout_8.addItem(spacerItem1, 4, 0, 1, 2)
self.verticalLayout_5.addItem(spacerItem1)
self.uiTabWidget.addTab(self.uiGeneralTab, _fromUtf8(""))
self.uiConsoleTab = QtGui.QWidget()
self.uiConsoleTab.setObjectName(_fromUtf8("uiConsoleTab"))
@@ -188,13 +202,11 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiSlowConsoleAllLabel = QtGui.QLabel(self.uiConsoleMiscGroupBox)
self.uiSlowConsoleAllLabel.setObjectName(_fromUtf8("uiSlowConsoleAllLabel"))
self.gridLayout_7.addWidget(self.uiSlowConsoleAllLabel, 2, 0, 1, 1)
self.uiSlowConsoleAllDoubleSpinBox = QtGui.QDoubleSpinBox(self.uiConsoleMiscGroupBox)
self.uiSlowConsoleAllDoubleSpinBox.setDecimals(1)
self.uiSlowConsoleAllDoubleSpinBox.setMinimum(0.0)
self.uiSlowConsoleAllDoubleSpinBox.setSingleStep(0.5)
self.uiSlowConsoleAllDoubleSpinBox.setProperty("value", 1.0)
self.uiSlowConsoleAllDoubleSpinBox.setObjectName(_fromUtf8("uiSlowConsoleAllDoubleSpinBox"))
self.gridLayout_7.addWidget(self.uiSlowConsoleAllDoubleSpinBox, 3, 0, 1, 1)
self.uiDelayConsoleAllSpinBox = QtGui.QSpinBox(self.uiConsoleMiscGroupBox)
self.uiDelayConsoleAllSpinBox.setMaximum(10000)
self.uiDelayConsoleAllSpinBox.setProperty("value", 500)
self.uiDelayConsoleAllSpinBox.setObjectName(_fromUtf8("uiDelayConsoleAllSpinBox"))
self.gridLayout_7.addWidget(self.uiDelayConsoleAllSpinBox, 3, 0, 1, 1)
self.verticalLayout_3.addWidget(self.uiConsoleMiscGroupBox)
spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem2)
@@ -279,6 +291,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiTemporaryFilesPathLabel.setText(_translate("GeneralPreferencesPageWidget", "Temporary files:", None))
self.uiTemporaryFilesPathLineEdit.setToolTip(_translate("GeneralPreferencesPageWidget", "Directory where temporary files are stored", None))
self.uiTemporaryFilesPathToolButton.setText(_translate("GeneralPreferencesPageWidget", "&Browse...", None))
self.uiStyleGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Style", None))
self.uiConfigurationFileGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Configuration file", None))
self.uiImportConfigurationFilePushButton.setText(_translate("GeneralPreferencesPageWidget", "&Import", None))
self.uiExportConfigurationFilePushButton.setText(_translate("GeneralPreferencesPageWidget", "&Export", None))
@@ -288,6 +301,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiSlowStartAllLabel.setText(_translate("GeneralPreferencesPageWidget", "Delay between each device start when starting all devices:", None))
self.uiSlowStartAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " seconds", None))
self.uiLinkManualModeCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Always use manual mode when adding links", None))
self.uiLaunchNewProjectDialogCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Launch the new project dialog on startup", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralTab), _translate("GeneralPreferencesPageWidget", "General", None))
self.uiTelnetConsoleSettingsGroupBox.setTitle(_translate("GeneralPreferencesPageWidget", "Console settings for Telnet connections", None))
self.uiTelnetConsolePreconfiguredCommandLabel.setText(_translate("GeneralPreferencesPageWidget", "Preconfigured commands:", None))
@@ -304,7 +318,7 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiBringConsoleWindowToFrontCheckBox.setToolTip(_translate("GeneralPreferencesPageWidget", "<html>This option will attempt to bring existing opened console window to front, instead of opening a new window.<br>If no existing opened console window exists, it will start a new console window.</html>", None))
self.uiBringConsoleWindowToFrontCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Bring console window to front (experimental feature)", None))
self.uiSlowConsoleAllLabel.setText(_translate("GeneralPreferencesPageWidget", "Delay between each console launch when consoling to all devices:", None))
self.uiSlowConsoleAllDoubleSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " seconds", None))
self.uiDelayConsoleAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " ms", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiConsoleTab), _translate("GeneralPreferencesPageWidget", "Console applications", None))
self.uiSceneWidthLabel.setText(_translate("GeneralPreferencesPageWidget", "Default width:", None))
self.uiSceneWidthSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " pixels", None))

72
gns3/ui/idlepc_dialog.ui Normal file
View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IdlePCDialog</class>
<widget class="QDialog" name="IdlePCDialog">
<property name="windowTitle">
<string>Idle-PC values</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="uiLabel">
<property name="text">
<string>Potentially better Idle-PC values are marked with '*'</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="uiComboBox"/>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>uiButtonBox</sender>
<signal>accepted()</signal>
<receiver>IdlePCDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>uiButtonBox</sender>
<signal>rejected()</signal>
<receiver>IdlePCDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/idlepc_dialog.ui'
#
# Created: Wed Oct 22 18:01:47 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_IdlePCDialog(object):
def setupUi(self, IdlePCDialog):
IdlePCDialog.setObjectName(_fromUtf8("IdlePCDialog"))
IdlePCDialog.setModal(True)
self.gridLayout = QtGui.QGridLayout(IdlePCDialog)
self.gridLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiLabel = QtGui.QLabel(IdlePCDialog)
self.uiLabel.setObjectName(_fromUtf8("uiLabel"))
self.gridLayout.addWidget(self.uiLabel, 0, 0, 1, 1)
self.uiComboBox = QtGui.QComboBox(IdlePCDialog)
self.uiComboBox.setObjectName(_fromUtf8("uiComboBox"))
self.gridLayout.addWidget(self.uiComboBox, 1, 0, 1, 1)
self.uiButtonBox = QtGui.QDialogButtonBox(IdlePCDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Help|QtGui.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName(_fromUtf8("uiButtonBox"))
self.gridLayout.addWidget(self.uiButtonBox, 2, 0, 1, 1)
self.retranslateUi(IdlePCDialog)
QtCore.QObject.connect(self.uiButtonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), IdlePCDialog.accept)
QtCore.QObject.connect(self.uiButtonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), IdlePCDialog.reject)
QtCore.QMetaObject.connectSlotsByName(IdlePCDialog)
def retranslateUi(self, IdlePCDialog):
IdlePCDialog.setWindowTitle(_translate("IdlePCDialog", "Idle-PC values", None))
self.uiLabel.setText(_translate("IdlePCDialog", "Potentially better Idle-PC values are marked with \'*\'", None))

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImportCloudProjectDialog</class>
<widget class="QDialog" name="ImportCloudProjectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>471</width>
<height>402</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QListWidget" name="listWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>30</y>
<width>431</width>
<height>271</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="uiImportProjectAction">
<property name="geometry">
<rect>
<x>110</x>
<y>320</y>
<width>99</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Import</string>
</property>
</widget>
<widget class="QPushButton" name="uiDeleteProjectAction">
<property name="geometry">
<rect>
<x>260</x>
<y>320</y>
<width>99</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Delete</string>
</property>
</widget>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>300</x>
<y>360</y>
<width>160</width>
<height>25</height>
</rect>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ImportCloudProjectDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>403</x>
<y>366</y>
</hint>
<hint type="destinationlabel">
<x>440</x>
<y>318</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'gns3/ui/import_cloud_project_dialog.ui'
#
# Created: Wed Oct 22 12:45:41 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_ImportCloudProjectDialog(object):
def setupUi(self, ImportCloudProjectDialog):
ImportCloudProjectDialog.setObjectName(_fromUtf8("ImportCloudProjectDialog"))
ImportCloudProjectDialog.resize(471, 402)
self.listWidget = QtGui.QListWidget(ImportCloudProjectDialog)
self.listWidget.setGeometry(QtCore.QRect(20, 30, 431, 271))
self.listWidget.setObjectName(_fromUtf8("listWidget"))
self.uiImportProjectAction = QtGui.QPushButton(ImportCloudProjectDialog)
self.uiImportProjectAction.setGeometry(QtCore.QRect(110, 320, 99, 24))
self.uiImportProjectAction.setObjectName(_fromUtf8("uiImportProjectAction"))
self.uiDeleteProjectAction = QtGui.QPushButton(ImportCloudProjectDialog)
self.uiDeleteProjectAction.setGeometry(QtCore.QRect(260, 320, 99, 24))
self.uiDeleteProjectAction.setObjectName(_fromUtf8("uiDeleteProjectAction"))
self.buttonBox = QtGui.QDialogButtonBox(ImportCloudProjectDialog)
self.buttonBox.setGeometry(QtCore.QRect(300, 360, 160, 25))
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.retranslateUi(ImportCloudProjectDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), ImportCloudProjectDialog.reject)
QtCore.QMetaObject.connectSlotsByName(ImportCloudProjectDialog)
def retranslateUi(self, ImportCloudProjectDialog):
ImportCloudProjectDialog.setWindowTitle(_translate("ImportCloudProjectDialog", "Dialog", None))
self.uiImportProjectAction.setText(_translate("ImportCloudProjectDialog", "Import", None))
self.uiDeleteProjectAction.setText(_translate("ImportCloudProjectDialog", "Delete", None))

View File

@@ -9,7 +9,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>980</width>
<width>984</width>
<height>715</height>
</rect>
</property>
@@ -33,6 +33,9 @@ background-none;
</string>
</property>
<property name="dockOptions">
<set>QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks</set>
</property>
<property name="unifiedTitleAndToolBarOnMac">
<bool>false</bool>
</property>
@@ -61,7 +64,7 @@ background-none;
<rect>
<x>0</x>
<y>0</y>
<width>980</width>
<width>984</width>
<height>25</height>
</rect>
</property>
@@ -82,6 +85,7 @@ background-none;
<addaction name="uiOpenProjectAction"/>
<addaction name="uiSaveProjectAction"/>
<addaction name="uiSaveProjectAsAction"/>
<addaction name="uiImportProjectAction"/>
<addaction name="uiExportProjectAction"/>
<addaction name="separator"/>
<addaction name="uiImportExportConfigsAction"/>
@@ -105,13 +109,6 @@ background-none;
<property name="title">
<string>&amp;View</string>
</property>
<widget class="QMenu" name="uiStyleMenu">
<property name="title">
<string>Window Style</string>
</property>
<addaction name="uiDefaultStyleAction"/>
<addaction name="uiEnergySavingStyleAction"/>
</widget>
<widget class="QMenu" name="uiDocksMenu">
<property name="title">
<string>Docks</string>
@@ -124,9 +121,7 @@ background-none;
<addaction name="separator"/>
<addaction name="uiShowLayersAction"/>
<addaction name="uiResetPortLabelsAction"/>
<addaction name="uiShowNamesAction"/>
<addaction name="uiShowPortNamesAction"/>
<addaction name="uiStyleMenu"/>
<addaction name="separator"/>
<addaction name="uiDocksMenu"/>
</widget>
@@ -279,7 +274,7 @@ background-none;
<enum>LeftToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
<bool>true</bool>
</attribute>
<addaction name="uiBrowseRoutersAction"/>
<addaction name="separator"/>
@@ -555,10 +550,8 @@ background-none;
</property>
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/play7-test.svg</normaloff>
<normalon>:/icons/play7-test.svg</normalon>
<activeoff>:/icons/play2-test.svg</activeoff>
<activeon>:/icons/play2-test.svg</activeon>:/icons/play7-test.svg</iconset>
<normaloff>:/icons/start.svg</normaloff>
<activeoff>:/icons/start-hover.svg</activeoff>:/icons/start.svg</iconset>
</property>
<property name="text">
<string>Start/Resume all devices</string>
@@ -576,9 +569,8 @@ background-none;
</property>
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/stop3-test.svg</normaloff>
<activeoff>:/icons/stop2-test.svg</activeoff>
<activeon>:/icons/stop2-test.svg</activeon>:/icons/stop3-test.svg</iconset>
<normaloff>:/icons/stop.svg</normaloff>
<activeoff>:/icons/stop-hover.svg</activeoff>:/icons/stop.svg</iconset>
</property>
<property name="text">
<string>Stop all devices</string>
@@ -726,9 +718,8 @@ background-none;
<action name="uiSuspendAllAction">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/pause3-test.svg</normaloff>
<activeoff>:/icons/pause2-test.svg</activeoff>
<activeon>:/icons/pause2-test.svg</activeon>:/icons/pause3-test.svg</iconset>
<normaloff>:/icons/pause.svg</normaloff>
<activeoff>:/icons/pause-hover.svg</activeoff>:/icons/pause.svg</iconset>
</property>
<property name="text">
<string>Suspend all devices</string>
@@ -818,9 +809,8 @@ background-none;
</property>
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/rectangle3-test.svg</normaloff>
<activeoff>:/icons/rectangle2-test.svg</activeoff>
<activeon>:/icons/rectangle2-test.svg</activeon>:/icons/rectangle3-test.svg</iconset>
<normaloff>:/icons/rectangle.svg</normaloff>
<activeoff>:/icons/rectangle-hover.svg</activeoff>:/icons/rectangle.svg</iconset>
</property>
<property name="text">
<string>Draw rectangle</string>
@@ -838,9 +828,8 @@ background-none;
</property>
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/ellipse3-test.svg</normaloff>
<activeoff>:/icons/ellipse2-test.svg</activeoff>
<activeon>:/icons/ellipse2-test.svg</activeon>:/icons/ellipse3-test.svg</iconset>
<normaloff>:/icons/ellipse.svg</normaloff>
<activeoff>:/icons/ellipse-hover.svg</activeoff>:/icons/ellipse.svg</iconset>
</property>
<property name="text">
<string>Draw ellipse</string>
@@ -1084,7 +1073,9 @@ background-none;
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/connection-new.svg</normaloff>
<activeoff>:/icons/connection-new-hover.svg</activeoff>:/icons/connection-new.svg</iconset>
<normalon>:/icons/cancel-connection.svg</normalon>
<activeoff>:/icons/connection-new-hover.svg</activeoff>
<activeon>:/icons/cancel-connection.svg</activeon>:/icons/connection-new.svg</iconset>
</property>
<property name="text">
<string>Add a link</string>
@@ -1119,6 +1110,16 @@ background-none;
<string>Export project</string>
</property>
</action>
<action name="uiImportProjectAction">
<property name="text">
<string>Import project</string>
</property>
</action>
<action name="uiDarkStyleAction">
<property name="text">
<string>Dark Style</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/main_window.ui'
#
# Created: Thu Oct 16 17:45:13 2014
# Created: Wed Nov 19 14:45:32 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -27,7 +27,7 @@ class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.setWindowModality(QtCore.Qt.NonModal)
MainWindow.resize(980, 715)
MainWindow.resize(984, 715)
MainWindow.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
MainWindow.setStyleSheet(_fromUtf8("#toolBar_Devices QToolButton {\n"
"width: 50px;\n"
@@ -44,6 +44,7 @@ class Ui_MainWindow(object):
"}\n"
"\n"
""))
MainWindow.setDockOptions(QtGui.QMainWindow.AllowTabbedDocks|QtGui.QMainWindow.AnimatedDocks)
MainWindow.setUnifiedTitleAndToolBarOnMac(False)
self.uiCentralWidget = QtGui.QWidget(MainWindow)
self.uiCentralWidget.setObjectName(_fromUtf8("uiCentralWidget"))
@@ -61,7 +62,7 @@ class Ui_MainWindow(object):
self.gridlayout.addWidget(self.uiGraphicsView, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.uiCentralWidget)
self.uiMenuBar = QtGui.QMenuBar(MainWindow)
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 980, 25))
self.uiMenuBar.setGeometry(QtCore.QRect(0, 0, 984, 25))
self.uiMenuBar.setObjectName(_fromUtf8("uiMenuBar"))
self.uiEditMenu = QtGui.QMenu(self.uiMenuBar)
self.uiEditMenu.setObjectName(_fromUtf8("uiEditMenu"))
@@ -71,8 +72,6 @@ class Ui_MainWindow(object):
self.uiHelpMenu.setObjectName(_fromUtf8("uiHelpMenu"))
self.uiViewMenu = QtGui.QMenu(self.uiMenuBar)
self.uiViewMenu.setObjectName(_fromUtf8("uiViewMenu"))
self.uiStyleMenu = QtGui.QMenu(self.uiViewMenu)
self.uiStyleMenu.setObjectName(_fromUtf8("uiStyleMenu"))
self.uiDocksMenu = QtGui.QMenu(self.uiViewMenu)
self.uiDocksMenu.setObjectName(_fromUtf8("uiDocksMenu"))
self.uiControlMenu = QtGui.QMenu(self.uiMenuBar)
@@ -125,6 +124,7 @@ class Ui_MainWindow(object):
self.uiBrowsersToolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
self.uiBrowsersToolBar.setObjectName(_fromUtf8("uiBrowsersToolBar"))
MainWindow.addToolBar(QtCore.Qt.LeftToolBarArea, self.uiBrowsersToolBar)
MainWindow.insertToolBarBreak(self.uiBrowsersToolBar)
self.uiControlToolBar = QtGui.QToolBar(MainWindow)
self.uiControlToolBar.setIconSize(QtCore.QSize(32, 32))
self.uiControlToolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
@@ -225,18 +225,15 @@ class Ui_MainWindow(object):
self.uiStartAllAction = QtGui.QAction(MainWindow)
self.uiStartAllAction.setEnabled(True)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/play7-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/play7-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/play2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/play2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/start.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/start-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
self.uiStartAllAction.setIcon(icon5)
self.uiStartAllAction.setObjectName(_fromUtf8("uiStartAllAction"))
self.uiStopAllAction = QtGui.QAction(MainWindow)
self.uiStopAllAction.setEnabled(True)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop3-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/stop-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
self.uiStopAllAction.setIcon(icon6)
self.uiStopAllAction.setObjectName(_fromUtf8("uiStopAllAction"))
self.uiShowNamesAction = QtGui.QAction(MainWindow)
@@ -279,9 +276,8 @@ class Ui_MainWindow(object):
self.uiPreferencesAction.setObjectName(_fromUtf8("uiPreferencesAction"))
self.uiSuspendAllAction = QtGui.QAction(MainWindow)
icon12 = QtGui.QIcon()
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause3-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon12.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/pause-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
self.uiSuspendAllAction.setIcon(icon12)
self.uiSuspendAllAction.setObjectName(_fromUtf8("uiSuspendAllAction"))
self.uiAddNoteAction = QtGui.QAction(MainWindow)
@@ -309,17 +305,15 @@ class Ui_MainWindow(object):
self.uiDrawRectangleAction = QtGui.QAction(MainWindow)
self.uiDrawRectangleAction.setCheckable(True)
icon17 = QtGui.QIcon()
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle3-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon17.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/rectangle-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
self.uiDrawRectangleAction.setIcon(icon17)
self.uiDrawRectangleAction.setObjectName(_fromUtf8("uiDrawRectangleAction"))
self.uiDrawEllipseAction = QtGui.QAction(MainWindow)
self.uiDrawEllipseAction.setCheckable(True)
icon18 = QtGui.QIcon()
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse3-test.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse2-test.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon18.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/ellipse-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
self.uiDrawEllipseAction.setIcon(icon18)
self.uiDrawEllipseAction.setObjectName(_fromUtf8("uiDrawEllipseAction"))
self.uiShowPortNamesAction = QtGui.QAction(MainWindow)
@@ -397,7 +391,9 @@ class Ui_MainWindow(object):
self.uiAddLinkAction.setCheckable(True)
icon29 = QtGui.QIcon()
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new.svg")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/cancel-connection.svg")), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/connection-new-hover.svg")), QtGui.QIcon.Active, QtGui.QIcon.Off)
icon29.addPixmap(QtGui.QPixmap(_fromUtf8(":/icons/cancel-connection.svg")), QtGui.QIcon.Active, QtGui.QIcon.On)
self.uiAddLinkAction.setIcon(icon29)
self.uiAddLinkAction.setObjectName(_fromUtf8("uiAddLinkAction"))
self.uiGettingStartedAction = QtGui.QAction(MainWindow)
@@ -408,6 +404,10 @@ class Ui_MainWindow(object):
self.uiFitInViewAction.setObjectName(_fromUtf8("uiFitInViewAction"))
self.uiExportProjectAction = QtGui.QAction(MainWindow)
self.uiExportProjectAction.setObjectName(_fromUtf8("uiExportProjectAction"))
self.uiImportProjectAction = QtGui.QAction(MainWindow)
self.uiImportProjectAction.setObjectName(_fromUtf8("uiImportProjectAction"))
self.uiDarkStyleAction = QtGui.QAction(MainWindow)
self.uiDarkStyleAction.setObjectName(_fromUtf8("uiDarkStyleAction"))
self.uiEditMenu.addAction(self.uiSelectAllAction)
self.uiEditMenu.addAction(self.uiSelectNoneAction)
self.uiEditMenu.addSeparator()
@@ -416,6 +416,7 @@ class Ui_MainWindow(object):
self.uiFileMenu.addAction(self.uiOpenProjectAction)
self.uiFileMenu.addAction(self.uiSaveProjectAction)
self.uiFileMenu.addAction(self.uiSaveProjectAsAction)
self.uiFileMenu.addAction(self.uiImportProjectAction)
self.uiFileMenu.addAction(self.uiExportProjectAction)
self.uiFileMenu.addSeparator()
self.uiFileMenu.addAction(self.uiImportExportConfigsAction)
@@ -429,8 +430,6 @@ class Ui_MainWindow(object):
self.uiHelpMenu.addAction(self.uiLabInstructionsAction)
self.uiHelpMenu.addAction(self.uiAboutQtAction)
self.uiHelpMenu.addAction(self.uiAboutAction)
self.uiStyleMenu.addAction(self.uiDefaultStyleAction)
self.uiStyleMenu.addAction(self.uiEnergySavingStyleAction)
self.uiViewMenu.addAction(self.uiZoomInAction)
self.uiViewMenu.addAction(self.uiZoomOutAction)
self.uiViewMenu.addAction(self.uiZoomResetAction)
@@ -438,9 +437,7 @@ class Ui_MainWindow(object):
self.uiViewMenu.addSeparator()
self.uiViewMenu.addAction(self.uiShowLayersAction)
self.uiViewMenu.addAction(self.uiResetPortLabelsAction)
self.uiViewMenu.addAction(self.uiShowNamesAction)
self.uiViewMenu.addAction(self.uiShowPortNamesAction)
self.uiViewMenu.addAction(self.uiStyleMenu.menuAction())
self.uiViewMenu.addSeparator()
self.uiViewMenu.addAction(self.uiDocksMenu.menuAction())
self.uiControlMenu.addAction(self.uiStartAllAction)
@@ -504,7 +501,6 @@ class Ui_MainWindow(object):
self.uiFileMenu.setTitle(_translate("MainWindow", "&File", None))
self.uiHelpMenu.setTitle(_translate("MainWindow", "&Help", None))
self.uiViewMenu.setTitle(_translate("MainWindow", "&View", None))
self.uiStyleMenu.setTitle(_translate("MainWindow", "Window Style", None))
self.uiDocksMenu.setTitle(_translate("MainWindow", "Docks", None))
self.uiControlMenu.setTitle(_translate("MainWindow", "Control", None))
self.uiAnnotateMenu.setTitle(_translate("MainWindow", "Annotate", None))
@@ -649,6 +645,8 @@ class Ui_MainWindow(object):
self.uiLabInstructionsAction.setText(_translate("MainWindow", "Lab instructions", None))
self.uiFitInViewAction.setText(_translate("MainWindow", "Fit in view", None))
self.uiExportProjectAction.setText(_translate("MainWindow", "Export project", None))
self.uiImportProjectAction.setText(_translate("MainWindow", "Import project", None))
self.uiDarkStyleAction.setText(_translate("MainWindow", "Dark Style", None))
from ..cloud_inspector_view import CloudInspectorView
from ..console_view import ConsoleView

View File

@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>472</width>
<height>175</height>
<width>491</width>
<height>169</height>
</rect>
</property>
<property name="windowTitle">
@@ -23,17 +23,7 @@
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<item row="0" column="0" colspan="3">
<widget class="QGroupBox" name="uiProjectGroupBox">
<property name="title">
<string>Project</string>
@@ -130,6 +120,30 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="uiOpenProjectPushButton">
<property name="text">
<string>&amp;Open a project</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiRecentProjectsPushButton">
<property name="text">
<string>&amp;Recent projects...</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/new_project_dialog.ui'
#
# Created: Mon Oct 20 16:31:02 2014
# Created: Sun Nov 9 18:17:35 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -27,16 +27,11 @@ class Ui_NewProjectDialog(object):
def setupUi(self, NewProjectDialog):
NewProjectDialog.setObjectName(_fromUtf8("NewProjectDialog"))
NewProjectDialog.setWindowModality(QtCore.Qt.ApplicationModal)
NewProjectDialog.resize(472, 175)
NewProjectDialog.resize(491, 169)
NewProjectDialog.setModal(True)
self.gridLayout_2 = QtGui.QGridLayout(NewProjectDialog)
self.gridLayout_2.setSizeConstraint(QtGui.QLayout.SetFixedSize)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiButtonBox = QtGui.QDialogButtonBox(NewProjectDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName(_fromUtf8("uiButtonBox"))
self.gridLayout_2.addWidget(self.uiButtonBox, 1, 0, 1, 1)
self.uiProjectGroupBox = QtGui.QGroupBox(NewProjectDialog)
self.uiProjectGroupBox.setObjectName(_fromUtf8("uiProjectGroupBox"))
self.gridLayout = QtGui.QGridLayout(self.uiProjectGroupBox)
@@ -88,7 +83,18 @@ class Ui_NewProjectDialog(object):
self.gridLayout.addWidget(self.uiCloudRadioButton, 2, 2, 1, 1)
spacerItem = QtGui.QSpacerItem(201, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 2, 3, 1, 2)
self.gridLayout_2.addWidget(self.uiProjectGroupBox, 0, 0, 1, 1)
self.gridLayout_2.addWidget(self.uiProjectGroupBox, 0, 0, 1, 3)
self.uiOpenProjectPushButton = QtGui.QPushButton(NewProjectDialog)
self.uiOpenProjectPushButton.setObjectName(_fromUtf8("uiOpenProjectPushButton"))
self.gridLayout_2.addWidget(self.uiOpenProjectPushButton, 1, 0, 1, 1)
self.uiRecentProjectsPushButton = QtGui.QPushButton(NewProjectDialog)
self.uiRecentProjectsPushButton.setObjectName(_fromUtf8("uiRecentProjectsPushButton"))
self.gridLayout_2.addWidget(self.uiRecentProjectsPushButton, 1, 1, 1, 1)
self.uiButtonBox = QtGui.QDialogButtonBox(NewProjectDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.uiButtonBox.setObjectName(_fromUtf8("uiButtonBox"))
self.gridLayout_2.addWidget(self.uiButtonBox, 1, 2, 1, 1)
self.retranslateUi(NewProjectDialog)
QtCore.QObject.connect(self.uiButtonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewProjectDialog.accept)
@@ -104,4 +110,6 @@ class Ui_NewProjectDialog(object):
self.uiTypeLabel.setText(_translate("NewProjectDialog", "Type:", None))
self.uiLocalRadioButton.setText(_translate("NewProjectDialog", "Local", None))
self.uiCloudRadioButton.setText(_translate("NewProjectDialog", "Cloud", None))
self.uiOpenProjectPushButton.setText(_translate("NewProjectDialog", "&Open a project", None))
self.uiRecentProjectsPushButton.setText(_translate("NewProjectDialog", "&Recent projects...", None))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>275</height>
<width>203</width>
<height>225</height>
</rect>
</property>
<property name="floating">
@@ -16,8 +16,11 @@
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="allowedAreas">
<set>Qt::AllDockWidgetAreas</set>
</property>
<property name="windowTitle">
<string>GNS3 Jungle</string>
<string>Jungle Newsfeed</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QHBoxLayout" name="horizontalLayout">
@@ -27,21 +30,30 @@
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="margin">
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWebView" name="uiWebView" native="true">
<property name="minimumSize">
<size>
<width>300</width>
<height>250</height>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>250</height>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="url" stdset="0">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/news_dock_widget.ui'
#
# Created: Sat Oct 18 15:38:49 2014
# Created: Tue Nov 11 15:47:57 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,19 +26,20 @@ except AttributeError:
class Ui_NewsDockWidget(object):
def setupUi(self, NewsDockWidget):
NewsDockWidget.setObjectName(_fromUtf8("NewsDockWidget"))
NewsDockWidget.resize(300, 275)
NewsDockWidget.resize(203, 225)
NewsDockWidget.setFloating(False)
NewsDockWidget.setFeatures(QtGui.QDockWidget.DockWidgetFloatable|QtGui.QDockWidget.DockWidgetMovable)
NewsDockWidget.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
self.dockWidgetContents = QtGui.QWidget()
self.dockWidgetContents.setObjectName(_fromUtf8("dockWidgetContents"))
self.horizontalLayout = QtGui.QHBoxLayout(self.dockWidgetContents)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
self.horizontalLayout.setMargin(0)
self.horizontalLayout.setContentsMargins(1, 0, 2, 0)
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.uiWebView = QtWebKit.QWebView(self.dockWidgetContents)
self.uiWebView.setMinimumSize(QtCore.QSize(300, 250))
self.uiWebView.setMaximumSize(QtCore.QSize(300, 250))
self.uiWebView.setMinimumSize(QtCore.QSize(200, 200))
self.uiWebView.setMaximumSize(QtCore.QSize(200, 200))
self.uiWebView.setProperty("url", QtCore.QUrl(None))
self.uiWebView.setObjectName(_fromUtf8("uiWebView"))
self.horizontalLayout.addWidget(self.uiWebView)
@@ -48,6 +49,6 @@ class Ui_NewsDockWidget(object):
QtCore.QMetaObject.connectSlotsByName(NewsDockWidget)
def retranslateUi(self, NewsDockWidget):
NewsDockWidget.setWindowTitle(_translate("NewsDockWidget", "GNS3 Jungle", None))
NewsDockWidget.setWindowTitle(_translate("NewsDockWidget", "Jungle Newsfeed", None))
from PyQt4 import QtWebKit

View File

@@ -46,13 +46,13 @@
</property>
<property name="maximumSize">
<size>
<width>140</width>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/preferences_dialog.ui'
#
# Created: Tue Sep 30 18:52:19 2014
# Created: Wed Nov 19 16:42:07 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -47,9 +47,9 @@ class Ui_PreferencesDialog(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiTreeWidget.sizePolicy().hasHeightForWidth())
self.uiTreeWidget.setSizePolicy(sizePolicy)
self.uiTreeWidget.setMaximumSize(QtCore.QSize(140, 16777215))
self.uiTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(10)
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiTreeWidget.setFont(font)

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,19 @@
</item>
</layout>
</item>
<item row="9" column="0" colspan="2">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>390</width>
<height>193</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiLocalServerHostLabel">
<property name="text">
@@ -65,19 +78,6 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>390</width>
<height>193</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="2">
<widget class="QSpinBox" name="uiLocalServerPortSpinBox">
<property name="suffix">
@@ -101,6 +101,13 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="uiConsoleConnectionsToAnyIPCheckBox">
<property name="text">
<string>Allow console connections to any local IP address</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiRemoteTabWidget">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/server_preferences_page.ui'
#
# Created: Fri Oct 10 10:46:44 2014
# Created: Sun Nov 9 18:49:51 2014
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -48,6 +48,8 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiLocalServerToolButton.setObjectName(_fromUtf8("uiLocalServerToolButton"))
self.horizontalLayout.addWidget(self.uiLocalServerToolButton)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2)
spacerItem = QtGui.QSpacerItem(390, 193, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 9, 0, 1, 2)
self.uiLocalServerHostLabel = QtGui.QLabel(self.uiLocalTabWidget)
self.uiLocalServerHostLabel.setObjectName(_fromUtf8("uiLocalServerHostLabel"))
self.gridLayout.addWidget(self.uiLocalServerHostLabel, 2, 0, 1, 1)
@@ -57,8 +59,6 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiLocalServerPortLabel = QtGui.QLabel(self.uiLocalTabWidget)
self.uiLocalServerPortLabel.setObjectName(_fromUtf8("uiLocalServerPortLabel"))
self.gridLayout.addWidget(self.uiLocalServerPortLabel, 4, 0, 1, 1)
spacerItem = QtGui.QSpacerItem(390, 193, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 8, 0, 1, 2)
self.uiLocalServerPortSpinBox = QtGui.QSpinBox(self.uiLocalTabWidget)
self.uiLocalServerPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiLocalServerPortSpinBox.setMaximum(65535)
@@ -69,6 +69,9 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiLocalServerAutoStartCheckBox.setChecked(True)
self.uiLocalServerAutoStartCheckBox.setObjectName(_fromUtf8("uiLocalServerAutoStartCheckBox"))
self.gridLayout.addWidget(self.uiLocalServerAutoStartCheckBox, 6, 0, 1, 2)
self.uiConsoleConnectionsToAnyIPCheckBox = QtGui.QCheckBox(self.uiLocalTabWidget)
self.uiConsoleConnectionsToAnyIPCheckBox.setObjectName(_fromUtf8("uiConsoleConnectionsToAnyIPCheckBox"))
self.gridLayout.addWidget(self.uiConsoleConnectionsToAnyIPCheckBox, 7, 0, 1, 1)
self.uiTabWidget.addTab(self.uiLocalTabWidget, _fromUtf8(""))
self.uiRemoteTabWidget = QtGui.QWidget()
self.uiRemoteTabWidget.setObjectName(_fromUtf8("uiRemoteTabWidget"))
@@ -134,6 +137,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiLocalServerHostLabel.setText(_translate("ServerPreferencesPageWidget", "Host binding:", None))
self.uiLocalServerPortLabel.setText(_translate("ServerPreferencesPageWidget", "Port:", None))
self.uiLocalServerAutoStartCheckBox.setText(_translate("ServerPreferencesPageWidget", "Automatically start the server on startup", None))
self.uiConsoleConnectionsToAnyIPCheckBox.setText(_translate("ServerPreferencesPageWidget", "Allow console connections to any local IP address", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiLocalTabWidget), _translate("ServerPreferencesPageWidget", "Local server", None))
self.uiRemoteServersTreeWidget.headerItem().setText(0, _translate("ServerPreferencesPageWidget", "Host", None))
self.uiRemoteServersTreeWidget.headerItem().setText(1, _translate("ServerPreferencesPageWidget", "Port", None))

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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 subprocess
import shlex
import tempfile
def RunInTerminal(command):
if sys.platform.startswith("win"):
if not "ComSpec" in os.environ:
raise OSError("ComSpec environment variable is not set")
terminal_cmd = "{} /K cd %TEMP% && {}".format(os.environ['ComSpec'], command)
elif sys.platform.startswith('darwin'):
terminal_cmd = "/usr/bin/osascript -e 'tell application \"terminal\" to do script with command \"{}; exit\"'".format(command)
terminal_cmd = shlex.split(terminal_cmd)
else:
terminal_cmd = None
for path in os.environ["PATH"].split(os.pathsep):
if "xterm" in os.listdir(path) and os.access(os.path.join(path, "xterm"), os.X_OK):
terminal_cmd = "{} -e '{}'".format(os.path.join(path, "xterm"), command)
terminal_cmd = shlex.split(terminal_cmd)
break
if not terminal_cmd:
raise OSError("xterm must be installed first")
subprocess.Popen(terminal_cmd, stdout=subprocess.PIPE, cwd=tempfile.gettempdir())
if __name__ == '__main__':
cmd = '/usr/local/bin/dynamips -P 3600 -r 128 --idle-pc 0x0 "/path/to/IOS/c3725-advipservicesk9-mz.124-15.T14.image"'
RunInTerminal(cmd)

Some files were not shown because too many files have changed in this diff Show More