Compare commits

...

547 Commits

Author SHA1 Message Date
Julien Duponchelle
e4a336cd67 1.4.2 2016-02-17 19:31:28 +01:00
Jeremy Grossmann
1810956185 Merge pull request #1047 from GNS3/gif
Allow GIF images (not animated) since patent expired in 2004.
2016-02-17 11:25:12 -07:00
Julien Duponchelle
af7f0f49d1 Allow gif image (not animated) since pattent expire in 2004 2016-02-17 18:54:27 +01:00
Jeremy Grossmann
d6ebf8ea04 Merge pull request #1046 from GNS3/countdown
Countdown before starting the GNS3 VM
2016-02-17 10:25:58 -07:00
Julien Duponchelle
13721c9811 Countdown before starting the GNS3 VM
Fix #912
2016-02-17 18:24:34 +01:00
Julien Duponchelle
02810e84a9 Prevent IOU GNS3A install on Windows 2016-02-17 10:33:57 +01:00
grossmj
a12a686a68 Customizable name for VirtualBox VM templates. Fixes #748. 2016-02-16 22:22:13 -07:00
grossmj
16ec69bb8c Set timeout from 1 to 3 seconds when waiting for GNS3 VM server. Ref #1034. 2016-02-16 18:19:25 -07:00
grossmj
82cb95aff7 Redirect stderr to stdout when executing VBoxManage or vmrun. Ref #1027. 2016-02-16 17:24:41 -07:00
Jeremy Grossmann
1eb3693f0a Merge pull request #1031 from GNS3/cancel_vm_start
Allow to cancel the start of the GNS3 VM.
2016-02-16 16:58:18 -07:00
Julien Duponchelle
bfd9c654aa Apply the correct name to the appliance imported 2016-02-16 19:04:18 +01:00
Julien Duponchelle
b1477f5fb5 Update VMware banners
Fix #1032
2016-02-16 18:10:32 +01:00
Julien Duponchelle
cda6398010 Fix crash when loading a SVG image in topology
Fix #1035
2016-02-16 11:52:40 +01:00
Julien Duponchelle
13b699a183 Prevent a crash in progress dialog
Fix #1037
2016-02-16 11:46:12 +01:00
Julien Duponchelle
0cd1f314f6 Update for 4K monitor
Ref #1033
2016-02-16 09:56:33 +01:00
grossmj
0c894ee48a Do not use the appliance version for create its VM template name. 2016-02-12 21:17:02 -07:00
Julien Duponchelle
c85820e685 Allow to cancel the start of the GNS3 VM
Fix #912
2016-02-12 16:05:38 +01:00
Jeremy Grossmann
445f721ceb Merge pull request #1024 from GNS3/svg_only_node_item
All node are now SVG items
2016-02-11 18:42:32 -08:00
Jeremy Grossmann
5fa24247c6 Merge pull request #1011 from GNS3/allow_to_import_unknow
Allow to import unknow files via GNS3A
2016-02-11 17:20:38 -08:00
Julien Duponchelle
2acfe7f1bf Recompute file size during import and add a column with the MD5 size 2016-02-11 12:11:38 +01:00
Julien Duponchelle
b33f660f90 Fix setup.py is not installing gns3-net-converter
Fix #1025
2016-02-11 10:11:08 +01:00
grossmj
91509888af Launch custom console command only if a node is initialized and started. 2016-02-10 15:11:40 -07:00
Jeremy Grossmann
fa3657f736 Merge pull request #1022 from GNS3/custom_console_without_save
Allow to open a custom console on any node
2016-02-10 13:22:51 -08:00
Julien Duponchelle
09638633c1 gns3-net-converter 2016-02-10 19:30:15 +01:00
Jeremy Grossmann
98ba58f39b Merge pull request #1013 from GNS3/detect_corrupt_port_name
Detect corrupt port name
2016-02-10 10:06:27 -08:00
Julien Duponchelle
baa7436e43 Detect and fix duplicate port name in topology
Try to track #992.
2016-02-10 17:45:07 +01:00
Julien Duponchelle
4a9b649e9f Allow to open a custom console on any node
Without saving the console anywhere

Fix #675
2016-02-10 17:39:44 +01:00
Julien Duponchelle
42143f77c5 All node are now SVG items
This create a generic renderer outputing always an SVG item.

Fix #1017
2016-02-10 15:47:27 +01:00
Julien Duponchelle
e2bdd216bb Move Qt code to a module so we can add new code in differents files 2016-02-10 13:53:03 +01:00
Julien Duponchelle
856fde79ec Fix rare crash when updating node
Fix #1018
2016-02-10 11:33:23 +01:00
Julien Duponchelle
91987b2e06 Prevent duplicate server in server summary
Fix #1020
2016-02-10 11:29:18 +01:00
Julien Duponchelle
36c6eeabc9 Fix crash if you create a new version and click on next without file 2016-02-10 11:25:22 +01:00
Julien Duponchelle
549e364da3 Use _updateCallback instead of _setupCallback
Fix #992, #1015
2016-02-10 11:13:55 +01:00
grossmj
d0321ce0fa Change text description in create a new version dialog. 2016-02-09 18:28:34 -07:00
Jeremy Grossmann
0d4cca3aa7 Merge pull request #1010 from GNS3/doctor_double_gns3
Check if GNS3 is not installed twice in doctor
2016-02-09 17:08:08 -08:00
Jeremy Grossmann
d77177a98f Merge pull request #1008 from GNS3/custom_console
Allow to set a custom console per node & refactor common code for save and load topology
2016-02-09 16:55:52 -08:00
Jeremy Grossmann
13bcac7e22 Merge pull request #1000 from GNS3/edit_console_dialog
Create a dialog for choosing the console command.
2016-02-09 16:49:22 -08:00
Julien Duponchelle
4475a93fc5 Fix a regression in Host and Cloud
Fix #1014
2016-02-09 18:13:30 +01:00
Julien Duponchelle
041b609f99 Check if GNS3 is not installed twice in doctor
Fix #1007
2016-02-09 16:10:00 +01:00
Julien Duponchelle
d84ce07038 Allow to add custom command to the list
https://www.youtube.com/watch?v=sEXbQmHJ1uM&feature=em-upload_owner
2016-02-09 11:18:16 +01:00
Julien Duponchelle
c9fec9ad51 Update Readme Python 3.4 is require 2016-02-09 10:04:01 +01:00
Julien Duponchelle
e953ea7212 Allow to import unknow files via GNS3A
Fix #980
2016-02-08 20:27:09 +01:00
Julien Duponchelle
d1d7cd8186 Fix a problem with gns3 running in background after exit
Fix #971
2016-02-08 17:41:46 +01:00
Julien Duponchelle
d3e4f17dab Move common code for ports dump to vm.py 2016-02-08 16:24:14 +01:00
Julien Duponchelle
344fde0d6f Move common code _updatePortSettings to vm.py 2016-02-08 16:16:38 +01:00
Julien Duponchelle
e27c8955c5 Fix a crash when searching for alternative images 2016-02-08 15:24:04 +01:00
Julien Duponchelle
27fa234888 Fix tests
Fix #1009
2016-02-08 14:20:27 +01:00
Julien Duponchelle
59ae047359 Fix a crash with corrupted topology from 1.0
Fix #1005
2016-02-08 14:01:55 +01:00
Julien Duponchelle
8445637dd1 Remove all the docker code from 1.4 gui to avoid confusion 2016-02-08 13:33:27 +01:00
grossmj
e54faa3079 User configurable default name format for Docker. Fixes #748. 2016-02-05 12:15:30 -07:00
Julien Duponchelle
4d2a4f3433 Allow to set a custom console per node
This require some refactoring:
* all VM inherit of the same dump and load common code
* move some code for opening a console from graphic views to the VM

Fix #675
2016-02-05 19:00:53 +01:00
Julien Duponchelle
ddd6de24ce Create a dialog for choosing the console command.
This extract all the code for selecting a console command
to a Dialog. With the following improvements:
* detect if a command exists and select it in the combo box
* help about custom variables that can be used in the command

Demonstration here:
https://www.youtube.com/watch?v=fw9XhRtFJuY&feature=em-upload_owner

It's part 1 of the #675 for supporting console application by node.
2016-02-05 16:10:59 +01:00
Julien Duponchelle
19f1b969ea Catch error if dynamips is disabled for local and no remote available
Fix #999
2016-02-05 14:54:46 +01:00
Julien Duponchelle
48beb69103 Merge pull request #993 from GNS3/download_vm_link
Put a link for the GNS3 VM in the setup wizard
2016-02-05 10:18:09 +01:00
Julien Duponchelle
a0be08d62a Put a link for the GNS3 VM in the setup wizard
It's smart the link change with the version and emulator

Fix #978
2016-02-05 10:17:48 +01:00
Julien Duponchelle
a750c7df2a Merge pull request #994 from GNS3/gray_vm
When importing appliance explain why options is gray
2016-02-05 10:11:36 +01:00
Julien Duponchelle
950986c69e When importing appliance explain why options is gray
Fix #979
2016-02-05 10:11:15 +01:00
grossmj
cc7bddefa1 User configurable default name format for VMware and VirtualBox. Ref #748 2016-02-04 23:47:34 -07:00
grossmj
1c4b735880 Changes "base name prefix" to "default name format". Ref #748. 2016-02-04 21:20:42 -07:00
grossmj
0862c135d0 User configurable base name support for Dynamips, IOU and VPCS. Ref #748. 2016-02-04 19:57:53 -07:00
grossmj
01e3cf1b1c Refactor "Import config" router dialog. Fixes #752. 2016-02-04 18:17:20 -07:00
grossmj
519bd389f6 Fixes ValueError: cannot mmap an empty file. Fixes #723. 2016-02-04 17:45:09 -07:00
grossmj
ee6aac2614 Merge remote-tracking branch 'origin/master' 2016-02-04 17:31:17 -07:00
grossmj
fa319b6529 Saves the "show port names" state in topology files. Fixes #778. 2016-02-04 17:31:01 -07:00
Julien Duponchelle
905be4130e Fix KeyError: 'midplane' when loading 7200 in some cases
Perhaps something when importing old config.

Fix #996
2016-02-04 18:40:17 +01:00
Julien Duponchelle
814e973f9a Hide the server select box for builtin switch if dynamips local is off
Fix #872
2016-02-04 17:49:46 +01:00
Julien Duponchelle
016e279d43 IOU right click on devices 2016-02-04 17:12:14 +01:00
Julien Duponchelle
d432047ac1 Right click edit for VirtualBox 2016-02-04 16:53:14 +01:00
Julien Duponchelle
9938e60e05 Support right click on VMware vms templates 2016-02-04 16:27:36 +01:00
Julien Duponchelle
6e751bbfaf Support right click on dynamips templates 2016-02-04 16:24:17 +01:00
Julien Duponchelle
8bdc07185b Fix an issue where the Existing image button can disapear from wizard
If you select a server with no image and after a server with image.
The existing image button will not be visible.
2016-02-04 11:31:20 +01:00
Julien Duponchelle
8872aceb0b Fix a race condition when you ask for image list but close the windows
Fix #990
2016-02-04 11:13:43 +01:00
Julien Duponchelle
f7b14c7da7 Fix QWidget::setWindowModified: The window title does not contain a
'[*]' placeholder

Fix #991
2016-02-04 11:10:06 +01:00
Julien Duponchelle
86cd732453 Fix alignements of VMware and VirtualBox in VM choice type 2016-02-04 11:07:08 +01:00
Julien Duponchelle
3178fb1a46 Better explanation during server choice
Fix #977
2016-02-04 11:03:03 +01:00
Julien Duponchelle
a917e80774 Disabled remote button when we have no remote in server wizard 2016-02-04 11:03:02 +01:00
grossmj
9bde5fcad6 Improved lookup for VMware host type. Fixes #970. 2016-02-03 19:13:10 -07:00
Jeremy Grossmann
e33f423733 Merge pull request #986 from GNS3/righ_edit
Allow to edit a node via a right click in the node
2016-02-03 11:30:26 -08:00
grossmj
cc35408607 Change some text in nodes view. 2016-02-03 12:28:09 -07:00
Jeremy Grossmann
10d91b50ec Merge pull request #976 from GNS3/command_line
Allow to show the command line used to start a VM
2016-02-03 11:10:10 -08:00
Julien Duponchelle
7dd06b5659 Allow to edit a node via a right click in the node
At this time only Qemu is OK. If the change are OK
I can patch VMware, VirtualBox, Dynamips and IOU.

Fix #670
2016-02-03 18:09:20 +01:00
Julien Duponchelle
86087bf505 Show error if a problem occur when getting remote server KVM status
Fix #983
2016-02-03 16:27:21 +01:00
Julien Duponchelle
cb4923815a Display a clean error when an appliance has an invalid JSON
Fix #984
2016-02-03 16:11:42 +01:00
Julien Duponchelle
43258d64d2 MobaXterm integration
Fix #985
2016-02-03 16:08:26 +01:00
Julien Duponchelle
6d3109be67 Allow to show the command line used to start a VM 2016-02-02 18:24:51 +01:00
Julien Duponchelle
a25e0cc23f Add - GNS3 at the end of the windows name
Ref #578
2016-02-02 10:37:59 +01:00
Julien Duponchelle
026a78b1b6 Fix a crash with doctor on windows
Fix #974
2016-02-02 09:44:24 +01:00
Julien Duponchelle
380a4a0395 Fix crash in doctor if ubridge path is empty
Fix #975
2016-02-02 09:42:04 +01:00
Julien Duponchelle
f6c2a6387f 1.4.2dev1 2016-02-01 17:54:58 +01:00
Julien Duponchelle
27f65a92e9 1.4.1 2016-02-01 17:51:30 +01:00
grossmj
53dbac1d5c Improvement to detect VMware Player on Linux. Ref #970. 2016-01-31 11:32:40 -07:00
Julien Duponchelle
a780866cd8 You can move Dock widgets everywhere 2016-01-28 14:49:15 +01:00
Julien Duponchelle
bf6f1af217 Link to download VIX api 2016-01-28 14:12:40 +01:00
Julien Duponchelle
29c85f3c65 Fix SSH present in the server preferences
Fix also a scaling issue

Fix #968
2016-01-27 16:57:42 +01:00
Julien Duponchelle
ba51d3ebf5 Apply diff from @elhers 2016-01-27 15:46:03 +01:00
Julien Duponchelle
e4f3ea1da9 Pach for linux 2016-01-27 15:25:45 +01:00
Julien Duponchelle
f037452188 Fix doctor for linux 2016-01-27 14:35:47 +01:00
Julien Duponchelle
3d35601d47 Check dynamips and ubridge permission.
Not tested on linux for the moment

Ref #944
2016-01-27 11:49:43 +01:00
Julien Duponchelle
146e642097 Check if processor is 64 bit in Doctor 2016-01-27 11:03:51 +01:00
Julien Duponchelle
e2c53443e3 Merge pull request #929 from GNS3/doctor
Show a dialog for checking some common issues
2016-01-27 10:57:45 +01:00
grossmj
965a98d5a7 Adjustments for PR #929. 2016-01-27 10:52:32 +01:00
Julien Duponchelle
41a68e7b0e Warn if vmrun not accessible 2016-01-27 10:51:36 +01:00
Julien Duponchelle
20e62b824c Show a dialog for checking some common issues
Warn user about issues with AVG

Fix #928, #744
2016-01-27 10:51:36 +01:00
Julien Duponchelle
36e9fcbddf Add Servers Summary in "View / Docks"
Fix #965
2016-01-27 10:44:02 +01:00
Julien Duponchelle
bd3d33d67b Add an unknwon status icon when the server has never been connected
Fix #966
2016-01-27 10:40:03 +01:00
grossmj
caf77a24f1 Some cleaning in MainWindow. 2016-01-26 13:24:40 -07:00
Jeremy Grossmann
729f9e103d Merge pull request #964 from GNS3/server_usage
Show a summary with server usages
2016-01-26 10:36:48 -08:00
grossmj
7261debb79 Revert "Remove unused code."
This reverts commit b4d0deddfc.
2016-01-26 11:30:36 -07:00
grossmj
b4d0deddfc Remove unused code. 2016-01-26 10:58:15 -07:00
Julien Duponchelle
d62a32c7d7 Show a summary with server usages
Fix #963
2016-01-26 18:52:53 +01:00
Julien Duponchelle
cb94fc4d1e Fix a typo 2016-01-26 17:03:51 +01:00
Julien Duponchelle
554888b21d Close project on VM when closing the project on all servers 2016-01-26 17:02:13 +01:00
Julien Duponchelle
4ae01282d7 Avoid a crash during test 2016-01-26 17:01:08 +01:00
Julien Duponchelle
020e62ddf5 Allow to not rotate the text when changing all text colors
Fix #805
2016-01-26 14:10:32 +01:00
Julien Duponchelle
c1dce98595 Fix topology summary view 2016-01-26 11:34:28 +01:00
Julien Duponchelle
676187061c Raise an error when psutil is too old
Also in crash report we include the version of psutil

Fix #960
2016-01-26 11:03:25 +01:00
Julien Duponchelle
14f4f26791 Fix a race condition when closing the server
Fix #959
2016-01-26 10:47:55 +01:00
Julien Duponchelle
57cf10c251 Fix a very very rare crash in topology summary view
Fix #961
2016-01-26 10:44:07 +01:00
Julien Duponchelle
70b4fae62c Code cleanup from @elhers 2016-01-26 10:40:16 +01:00
Julien Duponchelle
5c3b9e7fae Merge pull request #955 from GNS3/fix_label_invert
Fix inversion of port label when loading a topology
2016-01-26 10:37:29 +01:00
Julien Duponchelle
9ea39a78db Code cleanup for label inversion
Following @grossmj and and @ehlers advices
2016-01-26 10:31:56 +01:00
grossmj
8f77697482 Reset port label positions. Fixes #811. 2016-01-25 20:41:38 -07:00
grossmj
1b5e53ddc6 Tentative to fix #951. 2016-01-25 14:42:00 -07:00
Jeremy Grossmann
86ee9595f4 Merge pull request #943 from GNS3/fix_terminal_command
Reset the telnet command on Mac
2016-01-25 12:02:15 -08:00
Jeremy Grossmann
dd7937dba4 Merge pull request #956 from GNS3/drop_ssh
Drop SSH support
2016-01-25 11:37:49 -08:00
Julien Duponchelle
a4c646152e Drop SSH support
Since we introduce the SSH support we got bug and bug
due to the complexity introduce by the tunnels. Even
in the 1.4.0 after long testing nobody notice that
the feature was broke.

And in his actual implementation we have limitation
preventing you to connect a node running on your PC
to a node running on the remote server.

After thinking about that it's better to drop it before
this feature is used by a lot of people. It's better to
use a VPN and less ninja.

Also we know that embeding paramiko require us to distribute
an encryption library creating legal trouble with US embargo
on various country.

Bonus point for the future: SSH support is probalby hard, or impossible for
a web interface and complicate the move to an architecture
with GNS3 server running as controller managing all the servers.

Fix #930
2016-01-25 19:21:36 +01:00
Julien Duponchelle
3d7db9a9c7 Fix inversion of port label when loading a topology
This PR rewrite the way multi link are created and avoid
to invert their directions.

Fix #954
2016-01-25 18:30:15 +01:00
Julien Duponchelle
e9ceadfc34 Fix error when importing Windows XP OVA
Fix #932
2016-01-20 17:06:46 +01:00
Julien Duponchelle
267cdc365d Warn if configuration file contain invalid unicode characters
Fix #933
2016-01-20 13:43:29 +01:00
Julien Duponchelle
6dfbaccee1 Fix a crash when importing some OVA
Fix #936
2016-01-20 13:41:03 +01:00
Julien Duponchelle
f97f48d428 Fix travis build 2016-01-20 13:09:02 +01:00
Julien Duponchelle
70b3bc680e Reset the telnet command on Mac
Following the issue #942

It's the easiest way to do it. But we can not interact
with the user at this step because the Qt is not yet started.
This mean during the upgrade the command is reset to the standard.

Even if some users could be disapointed it's positive for most user.
Because for most user the current settings is broke. And if someone
has change something he know how to configure it again (for example
switching to iTerm). If we don't do that I think the bug will remain
for years.
2016-01-20 11:50:33 +01:00
Jeremy Grossmann
c125579a38 Merge pull request #942 from GNS3/console_osx
Fix only one console work for OSX
2016-01-19 18:25:42 -08:00
Julien Duponchelle
9bc2a7e7bc Fix only one console work for OSX
In previous releases we got race conditions issues with
the OSX console due to the usage of Apple Script.

Problem. How to fix this situation for all users. Should
we reset terminal preferences for all OSX users coming from version
older than 1.4.1 ?

Fix #941
2016-01-19 20:17:32 +01:00
Julien Duponchelle
a5358dec14 Fix a rare crash when loading topology with missing image 2016-01-19 16:29:51 +01:00
Julien Duponchelle
c84f554f36 Fix a rare race condition when inserting an image
Fix #934
2016-01-19 16:23:33 +01:00
Julien Duponchelle
0dd9803f74 Fix a crash when pid file is empty
Fix #935
2016-01-19 16:12:36 +01:00
Julien Duponchelle
2baaa26a42 Fix a rare crash in progress dialogs
Fix #938
2016-01-19 16:10:45 +01:00
Julien Duponchelle
3fd62f906e Fix a crash if you don't have vms when importing a gns3a 2016-01-19 14:30:11 +01:00
Julien Duponchelle
41e8b44dde Warn user during appliance install if server is not avaible 2016-01-18 17:23:18 +01:00
Julien Duponchelle
09cbd9b606 Fix merge error after adding KVM requirements checks 2016-01-18 16:43:39 +01:00
Julien Duponchelle
3ab8d79ff7 Improve text when KVM is ok 2016-01-18 14:18:56 +01:00
Julien Duponchelle
97eaed6d2c Fix error when you stop the GNS3 VM but break the config before
Fix #924
2016-01-18 11:30:45 +01:00
Jeremy Grossmann
805aa23948 Merge pull request #920 from GNS3/fix_asa_wizard
Fix select of image broken if you need to select multiple images
2016-01-17 09:42:05 -08:00
Jeremy Grossmann
a25c45a502 Merge pull request #916 from GNS3/kvm_check
Add a step in the wizard checking KVM support
2016-01-17 09:41:04 -08:00
Julien Duponchelle
6ebda209ac startup_config is not mandatory inside .gns3 file 2016-01-15 20:57:13 +01:00
Julien Duponchelle
d226b31fab Fix wrong host on SSH connection
Fix #919
2016-01-15 19:33:10 +01:00
Julien Duponchelle
27a3009ce1 Fix select of image broken if you need to select multiple images
In the case of ASA you need to select multiple image
on multiple screen. The problem is we refresh the list
of the VM on each step.

This patch refresh only the VM list when the widget is visible.

Fix #886
2016-01-14 18:42:23 +01:00
Julien Duponchelle
654ad61105 Fix a crash when changing qemu cluster size to more than 512
Fix #915
2016-01-13 17:30:02 +01:00
Julien Duponchelle
31a461195f Fix a crash when closing SSH connections
Fix #917
2016-01-13 17:16:13 +01:00
Julien Duponchelle
331eef3164 Fix IOU support in gns3a
Fix #918
2016-01-13 17:05:47 +01:00
Julien Duponchelle
cb0d576ade 1.4.1dev1 2016-01-13 09:22:00 +01:00
Julien Duponchelle
32978381b2 Add urxvt support
https://gns3.com/qa/trying-to-get-gns3-to-work-with-
2016-01-13 09:21:07 +01:00
Julien Duponchelle
a7f7a688d3 1.4.0 release 2016-01-12 17:38:45 +01:00
Julien Duponchelle
5d87726397 Add a step in the wizard checking KVM support
It's mostly code from PR #904 but less error prone due to the usage
of a step in the wizard. Also it's check the capacities for all servers.
2016-01-11 17:53:36 +01:00
Julien Duponchelle
bb66555896 Merge branch 'master' into 1.4 2016-01-07 18:09:28 +01:00
Julien Duponchelle
37726630ce Fix rare crash when showing the progress dialog
Fix #909
2016-01-07 09:16:42 +01:00
Julien Duponchelle
e26762fce3 1.4.0dev14 2016-01-06 14:34:08 +01:00
Julien Duponchelle
32e59fcce4 Fix crash on Windows when a gui is already running
Fix #908
2016-01-06 14:24:49 +01:00
grossmj
9b43330e95 Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389. 2016-01-05 13:00:39 -07:00
Julien Duponchelle
b3b94243b4 1.4.0rc3 2016-01-05 18:30:48 +01:00
Julien Duponchelle
fba1032388 Fix config tests 2016-01-05 18:27:16 +01:00
Julien Duponchelle
ccc0803c5b Add information about antivirus and firewall in case of connection fail 2016-01-05 17:08:19 +01:00
Julien Duponchelle
f0340bcc98 Change link to doc for missing router image 2016-01-05 16:43:17 +01:00
Julien Duponchelle
8d19b8fcbf On windows and OSX experimental Qemu GNS3A support 2016-01-04 21:53:10 +01:00
Vasil Rangelov
dbf66d5a9a Allow appliances to be installed on a local Windows or OSX server, while also allowing them to instead require KVM be supported on the target server, or to disable it even if supported. Also resolve Qemu binary at appliance install time. 2016-01-04 21:53:10 +01:00
Julien Duponchelle
0fa8d61b19 Wait server in thread 2016-01-04 21:50:42 +01:00
Julien Duponchelle
02d326419b Fix local server non avaible in appliance wizard when local server
started by hand.

Ref #904
2016-01-04 14:12:08 +01:00
Julien Duponchelle
608e80a80b Improve pid checks
Fix #900
2016-01-03 20:45:58 +01:00
Julien Duponchelle
d90f11eb86 Add a comment about tests run in a container 2016-01-03 19:54:57 +01:00
Jeremy Grossmann
dc39187091 Merge pull request #894 from boenrobot/port1names
Port names with numbers starting from 1
2015-12-27 20:08:21 -08:00
Jeremy Grossmann
e4cd418533 Merge pull request #898 from boenrobot/diskInterfaceOnAppliance
Disk interfaces on Qemu appliances
2015-12-25 11:09:06 -08:00
Vasil Rangelov
8559469c73 Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide"). 2015-12-25 04:00:48 +02:00
Julien Duponchelle
e6ba7bdd98 Fix project non closing when you have only remote servers
Fix #895
2015-12-22 16:21:41 +01:00
Julien Duponchelle
784055689f Fix Windows layout not saved in some scenario
Fix #884
2015-12-22 16:11:50 +01:00
Julien Duponchelle
c937811f45 Force gns3 converter >= 1.2.4 2015-12-22 10:45:16 +01:00
Vasil Rangelov
79c8021faa Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables. 2015-12-22 00:41:12 +02:00
Julien Duponchelle
a428730f59 Merge pull request #893 from boenrobot/schemaFix1
Added missing Qemu adapters to the topology schema.
2015-12-21 21:12:44 +01:00
Vasil Rangelov
3cad8ea046 Added missing Qemu adapters to the topology schema. 2015-12-21 20:50:27 +02:00
Julien Duponchelle
ab1775e44b Fix Cannot save my topology getting an error message for temporary
topology

Fix #885
2015-12-18 10:10:26 +01:00
Julien Duponchelle
9c3facb07a Fix creation of ASA devices
Fix #890
2015-12-18 10:08:41 +01:00
Julien Duponchelle
e5ae7b2d25 Turn off Docker until 1.5 2015-12-18 09:49:50 +01:00
Julien Duponchelle
60462ff986 Fix display of server preferences on small screen
Fix #882
2015-12-14 16:37:55 +01:00
Julien Duponchelle
39443cd676 Fix If you turn off the local server and close the gui and reopen
preferences you have an issue

Fix #881
2015-12-14 16:28:50 +01:00
Julien Duponchelle
25ab8249ae Zoc 7 support
Fix #879
2015-12-14 16:19:38 +01:00
Julien Duponchelle
6e86c606cc Fix test on windows
Fix #877
2015-12-14 15:07:45 +01:00
Julien Duponchelle
8878b96e74 Merge branch 'master' into unstable 2015-12-11 14:32:32 +01:00
Julien Duponchelle
c3ef2edbab 1.4.0dev13 2015-12-11 14:27:20 +01:00
Julien Duponchelle
f7e398edc3 1.3.14dev1 2015-12-11 14:26:13 +01:00
Julien Duponchelle
8d4fc9585e 1.3.13 2015-12-11 09:22:46 +01:00
Julien Duponchelle
5fe65e26ce 1.3.12 2015-12-11 08:53:14 +01:00
Julien Duponchelle
daaebe7f96 Fix warning when closing GUI
Fix #875
2015-12-11 08:51:12 +01:00
grossmj
f9f6a52e8b Update links for new website. 2015-12-10 15:00:16 -07:00
grossmj
601c0217b9 Update VMware links. 2015-12-10 14:05:39 -07:00
Julien Duponchelle
b0971b4ba3 1.4.0rc2 2015-12-10 19:36:41 +01:00
Julien Duponchelle
5a4549c36c Merge pull request #874 from GNS3/iou_dynamips_gns3a
IOU and dynamips support for gns3a
2015-12-10 19:32:11 +01:00
Julien Duponchelle
fe2cc362f8 Dynamips GNS3A support 2015-12-10 18:17:51 +01:00
Julien Duponchelle
dd0220fd59 Prevent user turning off the Local server when using the GNS3 VM
Fix #873
2015-12-10 13:57:09 +01:00
Julien Duponchelle
26fdd9ef6f Force VM wizard to be modal
Fix #868
2015-12-10 10:31:03 +01:00
Julien Duponchelle
733ee259e5 Fix unicode error when exporting debug informations
Fix #869
2015-12-10 10:25:19 +01:00
Julien Duponchelle
762fecbcff Fix Debug can't be deactivated for current session
Fix #871
2015-12-10 10:19:13 +01:00
Julien Duponchelle
5e85dfe5fd Ignore missing file when reading config
Fix #870
2015-12-10 10:08:29 +01:00
Jeremy Grossmann
e01632d60a Merge pull request #864 from GNS3/drop_webkit
Drop Webkit from 1.3.X
2015-12-09 19:34:15 -08:00
Julien Duponchelle
60916e8b80 IOU gns3a support 2015-12-09 19:49:23 +01:00
Julien Duponchelle
4fe55634ae Support relative path for configuration file.
It's allow futur GNS3A support for IOU and Dynamips
2015-12-09 18:06:07 +01:00
Julien Duponchelle
b1b861c99d Don't kill the server if two gui are running
Fix #865
2015-12-09 15:26:37 +01:00
Julien Duponchelle
07c0474386 Ask user to send bug reports to GNS3.com 2015-12-09 12:17:32 +01:00
Julien Duponchelle
755fb5c8f3 Report bug to GNS3.com 2015-12-09 12:16:49 +01:00
Julien Duponchelle
fdb382874d Avoid crash when cancel connection to a server
Ref #865
2015-12-09 12:04:18 +01:00
Julien Duponchelle
471b3e1009 Fix travis dependencies installation 2015-12-09 09:11:19 +01:00
Julien Duponchelle
f64a226336 1.4.0dev12 2015-12-08 16:27:54 +01:00
Julien Duponchelle
dec257bb6b Fix version number display twice when installing appliance 2015-12-08 16:05:48 +01:00
Julien Duponchelle
e736fbbb87 Drop Webkit from 1.3.X 2015-12-08 11:44:21 +01:00
Julien Duponchelle
aeb42b3ffe Show a warning when you try to save as a remote topology
Ref #831
2015-12-08 11:34:37 +01:00
Julien Duponchelle
1694a57ed9 Fix application restart after self upgrade
Fix #863
2015-12-07 15:39:13 +01:00
Julien Duponchelle
26001463a0 Cleanup server autostart
Fix #862
2015-12-07 11:30:17 +01:00
grossmj
36c1197f1f Merge remote-tracking branch 'origin/unstable' into unstable 2015-12-05 17:26:52 -07:00
grossmj
1fa16936fe Have default console port start from 2000. 2015-12-05 17:26:33 -07:00
Jeremy Grossmann
fca65784ee Merge pull request #852 from GNS3/vnc
Allow to use the VNC port range for console
2015-12-05 16:03:44 -08:00
Julien Duponchelle
8983e6c5a9 Fix crash when opening a new topologies after gns3 converter failure
Fix #858
2015-12-04 15:18:38 +01:00
Julien Duponchelle
c6e06f3941 Fix crash when opening a new topologies after gns3 converter failure
Fix #858
2015-12-04 15:13:03 +01:00
Julien Duponchelle
b7e32a60ce Fix SSH support
Fix #857
2015-12-04 14:16:14 +01:00
Julien Duponchelle
da100e494b Fix bus error when writting on console
Fix #371
2015-12-04 11:37:42 +01:00
Julien Duponchelle
9d7b5bccb8 Fix Ok & Cancel button in preferences are broken
Fix #855
2015-12-03 16:57:15 +01:00
Julien Duponchelle
112b05c3dd Fix After a project load failure you can't open new project
Fix #851
2015-12-03 16:53:41 +01:00
Julien Duponchelle
987e85cce8 More fix around closing the GUI
Fix #854
2015-12-03 16:37:33 +01:00
Julien Duponchelle
8142d0baa7 Fix crash in progress dialog on OSX
Fix #828
2015-12-03 15:45:55 +01:00
Julien Duponchelle
cc32b2661c Kill already running zombie server
Fix #838
2015-12-03 11:14:16 +01:00
Julien Duponchelle
781857f598 Store the pid of the server when started
Refe #838
2015-12-03 10:33:03 +01:00
Jeremy Grossmann
69179dcb63 Merge pull request #849 from GNS3/resize_preferences
Preferences dialog resize
2015-12-02 11:11:32 -08:00
Julien Duponchelle
8017838d60 Fix a crash in rare case after a PyQT garbage collect
Fix #842
2015-12-02 16:13:59 +01:00
Julien Duponchelle
81946493ec Allow to use the VNC port range for console
This PR allow user to have VNC port in the
console port range. It's not a problem because server
can share the range (we have a common list for all tcp ports).

And we add an informations in the settings in order to allow user
to know that 5900 => 6000 will be used.

Fix #846, #839
2015-12-02 16:08:34 +01:00
Julien Duponchelle
a7f40c3d50 Preferences dialog resize
* add a scrollbar if preferences height is too big
* set a dynamics max size depending of the screen

Fix #844
2015-12-02 12:22:28 +01:00
Julien Duponchelle
c28723287f Fix a race condition when opening telnet from apple script
Prevent closing ssh tunnel on OSX when using apple script
this leak port...

Fix #841, #848
2015-12-02 11:21:16 +01:00
Julien Duponchelle
c7a8588647 Experimental support for tabbed terminal on OSX
Fix #841
2015-12-02 11:05:24 +01:00
Julien Duponchelle
4a64261c5d Fix validation error when saving topology with an usage
Fix #829
2015-12-01 18:50:17 +01:00
Julien Duponchelle
5f4365542c Fix another case of not closing windows
Fix #833
2015-12-01 18:47:42 +01:00
Julien Duponchelle
2e2c951ffc Fix GUI doesn't close after connection error to remote server
Fix #833
2015-12-01 16:12:41 +01:00
Julien Duponchelle
2ba7dde326 Add usage text to device template and on hover
Fix #829
2015-12-01 11:04:05 +01:00
Julien Duponchelle
a1579ca86b Fix a rare crash in progress dialog
Fix #835
2015-12-01 09:49:18 +01:00
Julien Duponchelle
81c4ddb85f Fix crash when displaying an error from the update
Fix #836
2015-11-30 21:35:36 +01:00
Julien Duponchelle
285a8d413a Url encode royal tx url 2015-11-30 17:09:43 +01:00
Julien Duponchelle
672c86b38d Use Royal TX URI scheme thanks to @lemonmojo
Fix #832
2015-11-30 16:49:02 +01:00
Julien Duponchelle
905611d2a8 OSX support for Royal TSX
Can't test because I don't have a Royal TSX licence

Ref #832
2015-11-30 15:13:45 +01:00
Julien Duponchelle
339fb22217 Merge branch 'master' into unstable 2015-11-30 14:46:10 +01:00
Julien Duponchelle
a8f5fa5dd5 Set Wireshark 2.0 as default OSX version
Fix #830
2015-11-30 14:37:39 +01:00
Julien Duponchelle
bd365dd6eb iTerm 2.9 support
Fix #817
2015-11-30 14:27:54 +01:00
Jeremy Grossmann
8e4a6169e0 Merge pull request #809 from GNS3/contributing
Contributing guidelines
2015-11-14 20:20:13 -08:00
Jeremy Grossmann
b1790844f3 Update CONTRIBUTING.md 2015-11-14 19:59:15 -07:00
grossmj
4b0050d26c Update debug information dialog. 2015-11-14 18:33:03 -07:00
grossmj
19bf40dc89 Change text for export debug information. 2015-11-14 18:31:46 -07:00
Julien Duponchelle
23cda61d17 1.4.0rc1 2015-11-12 17:48:51 +01:00
Julien Duponchelle
c74ffde65a Rename an appliance if the default name is already taken 2015-11-12 17:34:50 +01:00
Julien Duponchelle
0a9c10e748 Existing image option should be hidden when none is available
Fix #819
2015-11-12 17:02:05 +01:00
Julien Duponchelle
6973eaaa02 Warn users about the need to uncompress the image
Fix #826
2015-11-12 16:18:49 +01:00
Julien Duponchelle
9ce483398b Fix crash when you have no qemu vms and use gns3a
Fix #824
2015-11-12 15:40:23 +01:00
Julien Duponchelle
55744ab129 Change sentry key
Fix #825
2015-11-12 11:20:17 +01:00
Julien Duponchelle
0e1cb47aa1 Merge branch 'master' into unstable 2015-11-12 10:52:31 +01:00
Julien Duponchelle
3bf12753df Fix format_exception() missing 2 required positional arguments: 'value'
and 'tb' in topologyFile

Fix #823
2015-11-12 10:43:04 +01:00
Julien Duponchelle
8907659220 Fix dialog box not returning their result
Fix #818
2015-11-12 09:34:45 +01:00
Julien Duponchelle
a4ccb0b620 Log to console the Qt Message Boxes 2015-11-11 11:58:31 +01:00
Julien Duponchelle
7d845c0ef8 Add informations about GNS3 VM 2015-11-10 12:45:22 +01:00
Julien Duponchelle
8282ec1da6 Contributing file 2015-11-10 12:43:11 +01:00
Julien Duponchelle
edfe2c7f47 Fix upload images from a non default images path
Fix #797
2015-11-09 17:24:46 +01:00
Julien Duponchelle
ca69673439 Fix error when set cluster size using qemu-img
Fix #798
2015-11-09 15:35:04 +01:00
Julien Duponchelle
8677707a9a Improve export debug informations
* Force adding .zip at the end
* Prevent crash for temporary directory

Fix #408
2015-11-09 14:30:43 +01:00
Julien Duponchelle
f3abbe5d58 Fixes unused import and variables 2015-11-09 12:52:57 +01:00
Julien Duponchelle
c0f36590be Auto pep8 2015-11-09 12:39:14 +01:00
grossmj
bdb097a173 Sets console end port to 7000. 2015-11-08 18:15:14 -07:00
grossmj
510491e26d Merge remote-tracking branch 'origin/unstable' into unstable 2015-11-08 17:23:56 -07:00
grossmj
6f0015a678 Fixes #342. Qemu master image is modified instead of the linked image. 2015-11-08 17:23:43 -07:00
Jeremy Grossmann
690e1d5ea0 Merge pull request #777 from GNS3/export_debug_informations
Allow to export a zip with debug informations
2015-11-07 21:17:48 -08:00
Julien Duponchelle
19734e0bd3 Fix invalid log screen when dumping a topology 2015-11-06 16:29:23 +01:00
Julien Duponchelle
5172c0b19e Warn the user to save the project before exporting debug infos 2015-11-06 10:38:54 +01:00
Julien Duponchelle
d74898c8a3 Allow to export a zip with debug informations
The zip contains:
* informations about the system, network and processus
* configuration
* log
* current .gns3 and screenshoot

Hope it will help to solve issues with can't connect to
http://127.0.0.1:8000

Fix #408
2015-11-05 15:43:11 +01:00
Julien Duponchelle
eb10f10988 Add more safety to HTTP callback to Qt
Fix #757
2015-11-05 11:36:05 +01:00
Julien Duponchelle
eb2e504acc Fix crash on Windows exit
Fix #749
2015-11-05 11:21:54 +01:00
Julien Duponchelle
6c502e1213 Correclty enable faulthandler for dev version 2015-11-05 10:51:08 +01:00
Julien Duponchelle
462c00b358 Fix support of remote servers for VPCS
Fix #756
2015-11-04 15:07:22 +01:00
Julien Duponchelle
b83eb61a42 Drop shellConsole docker will support telnet 2015-11-04 11:53:00 +01:00
Julien Duponchelle
96c35c49ff Drop some references to QtWebkit 2015-11-04 10:20:02 +01:00
Julien Duponchelle
36b48f24dc Prevent call to a destroyed callback in http client connection
Fix #754
2015-11-03 09:55:25 +01:00
Julien Duponchelle
efb21665f0 Avoid crash due to deleted thread
Ref #749
2015-11-03 09:41:14 +01:00
Julien Duponchelle
9ab77cfe20 1.4.0dev11 2015-11-02 21:28:34 +01:00
Julien Duponchelle
6031a349be 1.4.0b5 2015-11-02 18:51:44 +01:00
Julien Duponchelle
7163daf480 Fix crash when loading invalid appliance file
Fix #743
2015-11-02 18:01:46 +01:00
Julien Duponchelle
f662c39df0 Show a message is starting or is stopping in progress dialog
This change the text of the progress dialog when node start
or stop.
2015-11-02 15:34:34 +01:00
Julien Duponchelle
d7caba76c4 Drop duplicate code 2015-11-02 15:34:34 +01:00
grossmj
36ff527cc0 Changes some references for "GNS3 VM" to "Local GNS3 VM". Ref #506. 2015-11-01 18:19:59 -07:00
grossmj
d96ab77a67 Fixes progress dialog remains #741. 2015-10-31 15:34:23 -06:00
Julien Duponchelle
b4ade1982e Add more check to prevent progress dialog crash 2015-10-30 14:34:27 +01:00
Julien Duponchelle
4c9ef92c2d Support more boot order for Qemu
Fix #738
2015-10-30 10:28:10 +01:00
Jeremy Grossmann
078fc0f5ed Merge pull request #740 from GNS3/qpartial
Add a Qt compatible version of partial
2015-10-29 12:03:22 -07:00
Julien Duponchelle
40cc73c4f1 Add Royal TS 2015-10-29 18:18:48 +01:00
Julien Duponchelle
07c1443a8d Fix tests 2015-10-29 16:42:53 +01:00
Julien Duponchelle
dc592e0d4f Wording 2015-10-29 16:38:35 +01:00
Julien Duponchelle
c873b6c603 Fix text not aligned in the appliance wizard 2015-10-29 16:04:32 +01:00
Julien Duponchelle
c7d0a7a504 Add a Qt compatible version of partial
This function detect is the QObject is deleted and
doesn't call the callback.

Fix #596, #735
2015-10-29 15:51:12 +01:00
Julien Duponchelle
e7eddf0a7e Rename Open Appliance to Import appliance 2015-10-29 13:41:50 +01:00
Julien Duponchelle
579e382971 Show Upload filename instead of waiting for 2015-10-29 13:40:55 +01:00
Julien Duponchelle
95ecb05434 Fix error where ISO is detected as an OVA during gns3a import
Fix #737
2015-10-29 11:20:42 +01:00
grossmj
a2353f32e4 Appliance wizard tweaking. 2015-10-28 17:38:43 -06:00
grossmj
793181d208 Removes News Dock Widget. 2015-10-28 14:49:12 -06:00
Julien Duponchelle
932ff53190 Support cdrom image in missing images detections
Fix #737
2015-10-28 17:58:38 +01:00
Julien Duponchelle
e92e23a8be Try to avoid a crash with GUI
Fix #736
2015-10-28 16:42:15 +01:00
Julien Duponchelle
a16adb2b46 Fix crash when appliance is missing 2015-10-28 15:55:41 +01:00
grossmj
f579ebf5a0 Support for capture description in Wireshark window title. 2015-10-26 17:48:57 -06:00
Julien Duponchelle
67593c97ab Move description in the GNS3 appliance 2015-10-23 17:30:05 +02:00
Julien Duponchelle
fe185283a0 Remove a debug 2015-10-22 17:47:26 +02:00
Julien Duponchelle
d2f8214a52 Fix crash with appliance wizard on Linux 2015-10-21 21:15:12 +02:00
Julien Duponchelle
a2293b21ce Set the name of the VM in OSX Terminal application 2015-10-21 17:16:59 +02:00
Julien Duponchelle
57872c38b3 Fix crash of symbol selector if the first file is a non builtin symbol
Fix #734
2015-10-21 13:55:41 +02:00
Julien Duponchelle
51f998c177 Ask for installing appliance
Fix #731
2015-10-21 12:03:36 +02:00
Julien Duponchelle
7abbc630c3 Install appliance from wizard instead of web app
Ref #731
2015-10-21 11:49:44 +02:00
Julien Duponchelle
a0dee6cb42 Fix version number 2015-10-20 16:20:29 +02:00
Julien Duponchelle
d38143ae1c Fix images import
Fix #729
2015-10-20 12:29:07 +02:00
Julien Duponchelle
1db36dfb3e Fix crash when using an old version of 1.4
Fix #733
2015-10-20 12:10:08 +02:00
Julien Duponchelle
a347de96ed Ensure default settings are saved when starting the app
Fix #732
2015-10-20 12:00:58 +02:00
Julien Duponchelle
31c76bf56d Cleanup docker test 2015-10-19 22:15:18 +02:00
Julien Duponchelle
8abf321d9a Use docker for travis build 2015-10-19 22:10:40 +02:00
Julien Duponchelle
3f7b6e7be4 1.4.0dev10 2015-10-19 19:11:45 +02:00
Julien Duponchelle
7b1337cd83 1.4.0b4 2015-10-19 19:04:44 +02:00
Julien Duponchelle
7f63a221af Mokcup of appliances wizard 2015-10-19 18:58:13 +02:00
Julien Duponchelle
cfeb5c9495 Fix tests 2015-10-19 17:04:24 +02:00
Julien Duponchelle
17655cd855 Fix Crash when opening an appliance #728 2015-10-19 17:00:16 +02:00
grossmj
ab071cd989 Mock up for appliance wizard. 2015-10-18 21:54:27 -06:00
grossmj
c73dd10783 Registry: add -nographic to Qemu options by default. Fixes #730. 2015-10-18 21:05:46 -06:00
grossmj
6a5584ae41 Registry: support for initrd, cpu throttling and process priority. 2015-10-18 21:02:42 -06:00
grossmj
16e82592a6 Support for modifications to a base Qemu VM (not a linked clone). 2015-10-18 19:19:27 -06:00
grossmj
842e771eef Registry: adds support for switch category, first_port_name and port_segment_size. 2015-10-18 14:59:35 -06:00
Julien Duponchelle
d31f9c49f5 Fix traceback when exiting the GUI
Ref #726
2015-10-17 21:28:01 +02:00
Julien Duponchelle
8b61c7ddc3 Show a download button
Fix #727
2015-10-17 21:20:19 +02:00
grossmj
af247e2dd6 Corrects some typos. 2015-10-17 11:52:26 -06:00
grossmj
990cb49854 Merge remote-tracking branch 'origin/master' 2015-10-16 15:43:46 -06:00
grossmj
c530924d8a Drops securecrt.vbs 2015-10-16 15:43:31 -06:00
grossmj
35360ae196 Drops securecrt.vbs 2015-10-16 15:42:46 -06:00
grossmj
f37117ed09 Display an error message if QtWebkit is not installed. 2015-10-16 12:51:44 -06:00
Julien Duponchelle
9d2d80db25 Fix console port lost when applying settings
Fix #721
2015-10-16 18:47:20 +02:00
Julien Duponchelle
364d8db5b2 Remove unused code 2015-10-16 17:54:29 +02:00
Julien Duponchelle
0ac5215d4b Merge branch 'master' into unstable 2015-10-16 17:28:50 +02:00
Julien Duponchelle
493d81c519 Fix analytics report on OSX
Fix #724
2015-10-16 17:28:17 +02:00
Julien Duponchelle
c5ab0c8d02 Fix invalid path with frozen application
Fix #722
2015-10-16 11:22:31 +02:00
Julien Duponchelle
03323e5df8 When raising an appliance not found error show full path
Ref #722
2015-10-16 10:43:46 +02:00
grossmj
a8c429b77e Remove unnecessary checks to know if the local server is running. 2015-10-15 18:35:37 -06:00
Julien Duponchelle
52095c8ed9 Merge branch 'master' into unstable 2015-10-15 09:32:43 +02:00
Julien Duponchelle
b13855a062 Analytics send windows 2015-10-15 09:29:37 +02:00
grossmj
0be67907ba Fixes issue when loading a project using VMware vmnet interfaces. Fixes #319. 2015-10-14 21:39:17 -06:00
Julien Duponchelle
cf7285a179 Fix the progress dialog freeze bug 2015-10-14 17:56:18 +02:00
Julien Duponchelle
ba564869a0 Revert "Try to solve Scanning for Appliance images doesn't end"
This reverts commit 07610e7556.
2015-10-14 15:44:26 +02:00
Julien Duponchelle
a6ddddea99 Try to fix the progress dialog freeze bug 2015-10-14 15:32:05 +02:00
Julien Duponchelle
a686a18d56 Drop dead code from getting started dialog
Fix #719
2015-10-14 10:50:14 +02:00
Julien Duponchelle
a98bd132e1 Fix Appliance installs image without adapting the filename
Fix #714
2015-10-14 10:44:02 +02:00
Julien Duponchelle
a100ca33b9 Raise error if reference in GNS3a is invalid 2015-10-13 17:51:54 +02:00
Julien Duponchelle
d2f75262d8 Merge branch 'master' into unstable 2015-10-13 16:56:25 +02:00
Julien Duponchelle
c65fd1683d Fix typo in analytics 2015-10-13 16:55:59 +02:00
Julien Duponchelle
76a8735133 Add information on how to debug 2015-10-13 16:36:47 +02:00
Julien Duponchelle
da3c1334da Merge branch 'master' into unstable 2015-10-13 09:42:15 +02:00
Julien Duponchelle
0c370ec45e Send stats to GNS3 team
Ref #694

* Generate an ID for user and craft a user agent for correct os report
* Track all dialog allowing stats on features
2015-10-13 09:26:45 +02:00
grossmj
ec27e418fc Licenses compliance. 2015-10-12 22:10:43 -06:00
grossmj
92500e96d4 Merge remote-tracking branch 'origin/unstable' into unstable 2015-10-12 15:54:55 -06:00
grossmj
c2ba6f5cb3 Handles warning notifications. 2015-10-12 15:54:32 -06:00
Julien Duponchelle
07610e7556 Try to solve Scanning for Appliance images doesn't end
Ref #713
2015-10-12 19:31:13 +02:00
Julien Duponchelle
741749dffb Fix TypeError: 'NoneType' object is not iterable in isLocalServerRunning
Fix #689
2015-10-12 17:13:23 +02:00
Julien Duponchelle
593c777ff0 Fix crash AttributeError: 'NoneType' object has no attribute
getNewProjectSettings

Fix #711
2015-10-12 17:11:07 +02:00
Julien Duponchelle
8a616fa0c5 Merge branch 'master' into unstable 2015-10-12 15:41:27 +02:00
Julien Duponchelle
a191ec7326 Fix error when importing dynamips config from non existent directory
Fix #710
2015-10-12 15:39:44 +02:00
Julien Duponchelle
f842812745 Merge branch 'master' into unstable 2015-10-12 15:34:00 +02:00
Julien Duponchelle
3cc43aa11f Fix crash when url is invalid
Fix #709
2015-10-12 15:32:04 +02:00
Julien Duponchelle
1d9839ed23 Merge branch 'master' into unstable 2015-10-12 10:25:16 +02:00
Julien Duponchelle
32a64f1259 Add a debug level 2 in the console
Allow to display everything including HTTP queries.
2015-10-12 10:24:38 +02:00
Julien Duponchelle
3c4fc4c1bd Fix a crash when loading appliance 2015-10-08 16:16:17 +02:00
Julien Duponchelle
1013cf527d Merge branch 'master' into unstable 2015-10-08 11:28:38 +02:00
Julien Duponchelle
1684ee3074 Support upload of multiple vmdk file
Fix #702
2015-10-08 11:27:02 +02:00
Julien Duponchelle
f50e08207c 1.3.12dev1 2015-10-07 18:34:39 +02:00
Julien Duponchelle
d3973d4a7b 1.3.11 2015-10-07 18:28:04 +02:00
Julien Duponchelle
7170e58551 Support PNG in the custom symbol selection dialog
Fix #699
2015-10-07 17:53:07 +02:00
grossmj
02968f1ece Add custom messages when computing Idle-PC values. Fixes #704. 2015-10-07 08:41:14 -06:00
Julien Duponchelle
eb94ba8b93 Display the version of Qt in the console 2015-10-07 14:28:47 +02:00
Julien Duponchelle
67c6e03f3b Qt 4.3 is no longer supported 2015-10-07 14:26:16 +02:00
Julien Duponchelle
faa3b519b6 Do not crash when parsing a Qt version with a snapshot notation
Fix #697
2015-10-07 10:39:50 +02:00
Julien Duponchelle
3b4dca7545 Merge branch 'master' into unstable 2015-10-07 10:35:12 +02:00
Julien Duponchelle
66495cbf83 Force nc path to /usr/bin/nc on Apple
Homebrew or macport can break it. Because they install the GNU
verison which doesn't support socket unix.

Fix #664
2015-10-07 10:32:53 +02:00
Julien Duponchelle
95fea87b29 Revert "Drop netcat for unix socket it's not supported by OSX"
This reverts commit a0271926e6.
2015-10-07 10:32:09 +02:00
Julien Duponchelle
4b0cea36d0 Catch errors when we have an infinite recursion when copying a folder
Fix #696
2015-10-07 10:16:21 +02:00
Julien Duponchelle
ec27683f0e Fix crash in recent files when changing locale
Fix #690
2015-10-07 10:12:12 +02:00
Julien Duponchelle
a4d7aaf0a1 Merge branch 'master' into unstable 2015-10-07 10:05:00 +02:00
Julien Duponchelle
e2bcd1fdd1 Catch error when we can't extract egg
Fix #693
2015-10-07 10:04:01 +02:00
Julien Duponchelle
d7fd04de03 Fix securecrt command line (backported from master) 2015-10-07 10:01:20 +02:00
Julien Duponchelle
f447cd9be6 Merge branch 'master' into unstable 2015-10-06 19:17:26 +02:00
grossmj
a080b65615 Updates SecureCRT command line. 2015-10-06 11:10:50 -06:00
Julien Duponchelle
bd034237c0 When it's an ova explain to user he need to download the ova 2015-10-06 18:00:10 +02:00
Julien Duponchelle
66aff0d9a2 OVA file support 2015-10-06 17:49:55 +02:00
Julien Duponchelle
04a4142128 Ignore .cache directory 2015-10-06 10:57:50 +02:00
Julien Duponchelle
ec71da4062 Fix update manager crash on Windows 2015-10-06 10:57:33 +02:00
Julien Duponchelle
0ae97aa933 Support for image in local subdirectory
Ref #700
2015-10-05 11:38:57 +02:00
Julien Duponchelle
580e19de0c Fix duplicate code 2015-10-05 08:51:47 +02:00
grossmj
e41e95b0e6 Fixes issue when saving Idle-PC into template. Fixes #674. 2015-10-04 14:56:34 -06:00
Julien Duponchelle
1adeee9ba1 Add link for downloading VMware 2015-10-02 18:17:09 +02:00
Julien Duponchelle
b598339e55 Cache md5sum in memory when loading a gns3a
Fix #681
2015-10-02 18:12:47 +02:00
Julien Duponchelle
c65e5d8795 Support symbol import from GNS3A
Fix #692
2015-10-02 17:57:49 +02:00
Julien Duponchelle
b8597bc196 Allow user to select symbol from his library
Fix #691
2015-10-02 17:19:45 +02:00
Julien Duponchelle
be75a8e2b8 Merge branch 'master' into unstable 2015-10-02 13:21:28 +02:00
Julien Duponchelle
2352840c3b Improve HTTP progress reliability 2015-10-02 11:21:44 +02:00
Julien Duponchelle
98f9f59af3 Support ubuntu default VNC client (Vinagre)
Fix #687
2015-10-02 11:11:05 +02:00
grossmj
00ba2f1046 Merge remote-tracking branch 'origin/master' 2015-10-02 03:07:39 -06:00
grossmj
0389245cb7 Adds the COPYING file. 2015-10-02 03:06:52 -06:00
Julien Duponchelle
2f746ff791 Fix error when receiving an HTTP error during HTTP progress
Fix #686
2015-10-02 11:05:47 +02:00
Julien Duponchelle
8b881dfd20 Support port_name_format in GNS3 a files
Fix #684
2015-10-01 15:59:50 +02:00
Julien Duponchelle
cfece32185 options is not mandatory in a .gns3 file 2015-10-01 15:12:24 +02:00
Julien Duponchelle
d09ce859aa Merge branch 'master' into unstable 2015-09-30 09:41:52 +02:00
Julien Duponchelle
227a8a0c9e Xshell 5 support
Source: http://www.packetctrl.com/xshell-5-and-gns3-integration/
2015-09-30 09:38:48 +02:00
Julien Duponchelle
1b0da301e3 Add missing gns3-converter to requirements.txt 2015-09-30 08:21:54 +02:00
Julien Duponchelle
0f3efaa3fe Add missing gns3-converter in requirements.txt 2015-09-25 20:08:54 +02:00
grossmj
55b5f49c79 Fixes Qemu binaries not listed in the node configuration dialog. Fixes #683. 2015-09-25 06:06:22 -06:00
grossmj
3601acf66e Fixes SecureCRT command line. 2015-09-24 15:15:43 -06:00
Julien Duponchelle
383306c0ab Speedup directory scan for images when loading a gns3a file
Fix #682
2015-09-23 14:58:04 +02:00
Julien Duponchelle
5f87be16d7 Show a progress bar during directory scan when searching for appliances
Fix #680
2015-09-23 14:37:37 +02:00
Julien Duponchelle
63ab01d364 Search image by default also in the download directory 2015-09-23 14:22:59 +02:00
grossmj
4252f19819 Fixes issue when Telnet doesn't let you to login to an appliance on Linux. 2015-09-22 16:11:35 -06:00
grossmj
1095ed1663 Bump version to 1.4.0.dev9 2015-09-22 16:07:21 -06:00
grossmj
c5557e45c4 Fixes issue when Telnet doesn't let you to login to an appliance on Linux. 2015-09-22 16:06:35 -06:00
Julien Duponchelle
f5159a93f3 Prepare 1.4.0b3 2015-09-22 17:20:24 +02:00
Julien Duponchelle
b00d39e531 Add a warning if you don't select VMware or VBox in Setup Wizard
Fix #677
2015-09-22 15:13:56 +02:00
Julien Duponchelle
0c077b5647 Fix Configuration not always saved in client
Fix #678
2015-09-22 13:38:34 +02:00
grossmj
98aed2a8b6 Fixes file path reference issue. 2015-09-22 03:46:50 -06:00
Julien Duponchelle
0523873e5e Fix Appliance doesn't work on local Server #669 2015-09-22 11:16:40 +02:00
Julien Duponchelle
ed5ce93df6 Merge remote-tracking branch 'origin/master' into unstable 2015-09-22 11:01:44 +02:00
Julien Duponchelle
ab4f85b862 Merge branch 'master' into unstable 2015-09-22 11:01:18 +02:00
grossmj
67912a918f Allows Qemu VM template editing if the server is not running. Fixes #671. 2015-09-20 11:39:36 -06:00
grossmj
0af939bd37 Fixes NIO_VMNET != NIO_VMnet. 2015-09-19 03:34:10 -06:00
grossmj
b712b54786 Bump version to 1.4.0dev8 2015-09-18 15:41:34 -06:00
grossmj
19bf52661e Automatically add the -no-kvm option if -icount is detected to help with the migration of ASA VMs created before version 1.4 2015-09-18 15:38:55 -06:00
grossmj
c0c0261c68 Some improvements. 2015-09-18 09:46:51 -06:00
Julien Duponchelle
1eea941b55 Try to fix the Runtime error
Fix #668
2015-09-18 09:04:31 +02:00
Julien Duponchelle
547a8fb6c4 1.4.0 beta2 2015-09-17 16:41:08 +02:00
Julien Duponchelle
a0271926e6 Drop netcat for unix socket it's not supported by OSX
Fix #664
2015-09-17 16:08:56 +02:00
Julien Duponchelle
7e796fc3f1 Drop a debug 2015-09-17 16:01:49 +02:00
Julien Duponchelle
ba17b9d789 Fix for fusion 2015-09-17 15:22:45 +02:00
Julien Duponchelle
a4c17562ad VMware fusion is supported
Fix #661
2015-09-16 22:04:13 +02:00
Julien Duponchelle
ca173fb491 Fix a crash when a process return an error in sudo 2015-09-16 17:26:44 +02:00
Julien Duponchelle
9330689641 Last 1.3.X is 1.3.10. 2015-09-16 16:12:45 +02:00
Julien Duponchelle
ca67553e66 Fix race conditions in http_client
Fix #660
2015-09-16 15:57:59 +02:00
grossmj
dabcb6d4f4 Merge remote-tracking branch 'origin/unstable' into unstable 2015-09-16 05:26:18 -06:00
grossmj
753eb79a15 Fixes output issue when trying to run gns3vmnet.exe 2015-09-16 05:25:08 -06:00
Julien Duponchelle
034c9e8ad5 Forgot to remove this code 2015-09-16 13:19:29 +02:00
Julien Duponchelle
ba04783e8f Drop the licence POC 2015-09-16 13:16:40 +02:00
grossmj
a8ba51cdee Merge remote-tracking branch 'origin/unstable' into unstable 2015-09-16 05:11:36 -06:00
grossmj
24997c39f5 Fixes issues when trying to run gns3vmnet.exe 2015-09-16 05:11:11 -06:00
Julien Duponchelle
919311e2ab Dissalow gns3a import on local server for win and mac 2015-09-16 12:11:46 +02:00
Julien Duponchelle
61c3b0bfff Cleanup and test server select box 2015-09-16 11:54:50 +02:00
Julien Duponchelle
8024738400 Fix http_client tests 2015-09-16 11:04:03 +02:00
Julien Duponchelle
ac58bc677f On OSX show a warning when using an old Qemu
Fix #658
2015-09-16 10:13:48 +02:00
Julien Duponchelle
3b887f7f1b Refactor select code to use the same for appliance and graphics 2015-09-15 22:36:25 +02:00
Julien Duponchelle
8ea24a156e Disallow to install an appliance when an appliance twice 2015-09-15 22:11:01 +02:00
Julien Duponchelle
264fe8f0c8 Show a progress dialog when importing appliances 2015-09-15 16:11:14 +02:00
Julien Duponchelle
3f67cd4a99 Install gns3a from current directory
Fix #652
2015-09-15 15:36:26 +02:00
Julien Duponchelle
5d9e9dfc4a Tab support for xfce4-terminal 2015-09-15 11:13:30 +02:00
grossmj
95bec70e27 Improve alignments. Fixes #215. 2015-09-14 15:43:26 -06:00
grossmj
c1c6c812c8 Improve alignments. Fixes #215. 2015-09-14 15:41:37 -06:00
grossmj
0a8d947375 Fixes ethertype validation error. 2015-09-14 15:32:49 -06:00
grossmj
c9a699fc30 Bump version to 1.4.0dev7. 2015-09-14 15:19:57 -06:00
grossmj
8bf350a7dd Improve check for uBridge permissions. 2015-09-14 14:58:35 -06:00
Julien Duponchelle
bbc468db0f Fix an uuid is display instead of the server url
Fix #657
2015-09-14 18:10:16 +02:00
Julien Duponchelle
2353f074ab Set root permission to ubridge on OSX
Fix #655
2015-09-14 17:49:23 +02:00
Julien Duponchelle
79b7174a83 Show GNS3 version at startup 2015-09-14 17:49:23 +02:00
grossmj
82e1c088c7 Allows to select a remote server to run a switch. Fixes #653. 2015-09-13 11:11:14 -06:00
grossmj
1d13a3de3c Support for packet capture on VMware VM links. 2015-09-13 09:40:09 -06:00
Julien Duponchelle
0fb0bafc14 VMware OSx use local server by default 2015-09-11 16:33:31 +02:00
Julien Duponchelle
6cc5547509 Always use ubridge with VMware by default
Fix #648
2015-09-11 16:32:36 +02:00
Julien Duponchelle
a72df53b69 Another fix for docker module missing on frozen OSX application
Fix #649
2015-09-11 16:13:20 +02:00
Julien Duponchelle
f0802038db Fix docker module missing on frozen OSX application
Fix #649
2015-09-11 15:49:25 +02:00
Julien Duponchelle
631b4b7a61 Fix appliance error display when opening invalid gns3a 2015-09-11 14:37:58 +02:00
Julien Duponchelle
d9436520af Split the open menu between Open project & Open appliance
Fix #651
2015-09-11 14:33:59 +02:00
Julien Duponchelle
d805cfbd6a Fix PermissionError: [Errno 1] Operation not permitted when kill process
Fix #626
2015-09-11 14:19:54 +02:00
Julien Duponchelle
078d5023c4 VMware fusion support is no longer experimental
Fix #656
2015-09-11 14:13:33 +02:00
Julien Duponchelle
bc053da538 Support drag & drop gns3a
Fix #650
2015-09-11 12:13:02 +02:00
Julien Duponchelle
5d79bbce39 Fix crash when opening gns3a from double click 2015-09-11 09:57:21 +02:00
Julien Duponchelle
98c8d56dc5 Fix an error when loading IOU schema with private_config 2015-09-11 09:15:28 +02:00
Julien Duponchelle
1acaa18c3c UTF-8 support for appliance 2015-09-10 14:08:27 +02:00
Julien Duponchelle
8325fe0cee Try to support open a file via double click on OSX
Ref #646
2015-09-10 13:44:42 +02:00
Julien Duponchelle
eedd12207c Move HTML templates to static 2015-09-10 10:39:01 +02:00
Julien Duponchelle
64ed8c3de0 Fix open project via file menu 2015-09-10 10:24:46 +02:00
Julien Duponchelle
ab4f2f3922 Add templates as resources 2015-09-09 22:11:51 +02:00
Julien Duponchelle
fc5bf2dc4b Initial version of an appliance file format 2015-09-09 21:24:28 +02:00
Julien Duponchelle
bd0fabd1f6 Fix Missing configChangedSlot in Docker
Fix #645
2015-09-09 21:24:28 +02:00
grossmj
a4a2963bc3 Add white background for Docker symbol. 2015-09-09 10:37:03 -06:00
grossmj
310f47b52a Turn off local Docker support for Windows and OSX. Fixes #641. 2015-09-09 05:01:11 -06:00
grossmj
b4a187dd02 Adds docker symbols. Fixes #643. 2015-09-09 04:43:45 -06:00
grossmj
f4afdca576 Call the vmnet management script from the GUI (with admin rights). Implements #639. 2015-09-09 02:46:30 -06:00
Julien Duponchelle
28bebff7b0 Add a scary warning for the experimental mode 2015-09-08 21:34:53 +02:00
Julien Duponchelle
c3a4daef87 Allow to use vmware fusion with experimental support 2015-09-08 21:31:42 +02:00
Julien Duponchelle
18a5a28283 Fix crash at gui startup
Fix #642
2015-09-08 15:10:49 +02:00
Julien Duponchelle
36d08d2dca Fix Docker wizard when used with a remote server 2015-09-08 15:05:01 +02:00
Julien Duponchelle
91bf6e667d Use self update only if experimental features are allowed 2015-09-08 13:30:35 +02:00
Julien Duponchelle
a88a47e223 Fix default page in general preferences 2015-09-08 13:22:43 +02:00
Julien Duponchelle
8ede0f2089 Rename docker VMs to Docker Containers 2015-09-08 13:19:32 +02:00
Julien Duponchelle
576f6c81a1 Add an option for enabling experimental features 2015-09-08 11:50:55 +02:00
Goran Cetusic
1666fc4aa0 Backport docker support from Google Summer Of Code 2015-09-08 11:06:49 +02:00
grossmj
1216882e89 Merge branch 'Bevaz-qinq_ethertype' into unstable 2015-09-08 02:47:58 -06:00
grossmj
3f825a163b Merge branch 'qinq_ethertype' of https://github.com/Bevaz/gns3-gui into Bevaz-qinq_ethertype
Conflicts:
	gns3/modules/dynamips/ui/ethernet_switch_configuration_page_ui.py
2015-09-08 02:45:08 -06:00
Julien Duponchelle
2fcfa10573 Add a run with sudo class for OSX and Linux
Ref #639
2015-09-07 22:01:56 +02:00
Jeremy Grossmann
dd95b24d32 Merge pull request #640 from ryanchapman/master
Spelling correction
2015-09-05 15:40:40 -06:00
Ryan A. Chapman
bb2777ed5b Spelling correction 2015-09-05 15:26:22 -06:00
grossmj
e0f03ec582 Allows VMware VMs to use vmnet interfaces for connections without using uBridge. 2015-09-05 14:38:44 -06:00
Julien Duponchelle
51e844559e Prepare next release 2015-09-04 21:34:20 +02:00
Julien Duponchelle
dc4a984c41 Prepare version 1.3.10 2015-09-04 18:13:18 +02:00
Julien Duponchelle
54139845ac Add a firewall symbol
Fix #631
2015-09-04 10:41:54 +02:00
grossmj
8f9672d9e2 Updates kernel command line of ASA. 2015-09-03 16:52:36 -06:00
Julien Duponchelle
b007aea2f5 Detect broken link in topologies
Ref #635
2015-09-03 18:26:59 +02:00
Julien Duponchelle
3900645d1f Fix project not closing
https://github.com/GNS3/gns3-server/issues/305
2015-09-03 16:54:48 +02:00
Julien Duponchelle
5d644467fc Fix autostart
Fix #633
2015-09-03 15:16:23 +02:00
Julien Duponchelle
29fcd07b1c Merge branch 'master' into unstable 2015-09-03 10:20:37 +02:00
Julien Duponchelle
22c2ee31c4 Fix file not found exception in vpcs list dir
Fix #632
2015-09-03 10:19:55 +02:00
Julien Duponchelle
69f99742a8 Add missing virtio-net-pci to the json schema 2015-09-02 11:03:23 +02:00
Julien Duponchelle
e7dc901d5e Fix the all devices views
Fix #630
2015-08-28 20:55:59 +02:00
Julien Duponchelle
4dba7fa7fc Fix double click event on Note Item
Fix #629
2015-08-28 15:44:10 +02:00
Julien Duponchelle
55d4201aaf Fix Accepting insecure https connections creates additional server entry
And fix server password is regenerated

Fix #605, #627
2015-08-27 15:40:31 +02:00
Julien Duponchelle
74a4e464c8 Allow developer to debug packet capture on Windows 2015-08-26 18:25:10 +02:00
Julien Duponchelle
329c196047 Merge branch 'master' into unstable 2015-08-26 14:36:40 +02:00
Julien Duponchelle
85e74b482e Fix saveAs error unsupported operand type(s) for +=: 'NoneType' and
'str'

Fix #615
2015-08-26 14:16:04 +02:00
Julien Duponchelle
dc12fdd1c9 Drop unused test 2015-08-26 13:54:29 +02:00
Julien Duponchelle
0ee64ecbb8 Catch error when antivirus corrupt our own JSON errors
Fix #611
2015-08-26 13:53:04 +02:00
Julien Duponchelle
2946e931aa Add a note about VIX API require for VMware player 2015-08-26 13:21:33 +02:00
Julien Duponchelle
30871f0cd1 Return original path in case of error when moving image 2015-08-26 10:06:07 +02:00
Julien Duponchelle
70aa165444 Create image directory if not exists 2015-08-26 10:04:41 +02:00
Julien Duponchelle
beb35c6108 Allow GUI to be start with python -m gns3 2015-08-25 18:45:16 +02:00
Julien Duponchelle
90d1cb9a7f Avoid errors in qemu configuration if server has been deleted
Fix #610
2015-08-25 10:20:48 +02:00
Julien Duponchelle
5754d66560 Fix error when the IOS image directory is not writable
Fix #614
2015-08-25 10:05:28 +02:00
Julien Duponchelle
77ba02f0ae Do not crash if something intercept the call to the update server
Fix #620
2015-08-25 10:02:24 +02:00
Julien Duponchelle
effdc862a2 Fix super(): no arguments in SSH client
Fix #622
2015-08-25 09:54:02 +02:00
Julien Duponchelle
59c4736f41 Catch error when configuration file contain invalid UTF-8 chars
Fix #613
2015-08-25 09:50:10 +02:00
grossmj
dff831eafe Merge remote-tracking branch 'origin/unstable' into unstable 2015-08-24 20:18:55 -06:00
grossmj
4765640dc7 Fixes #621. 2015-08-24 20:16:59 -06:00
Julien Duponchelle
b8799a91b4 1.4.0dev6 2015-08-24 17:15:30 +02:00
Julien Duponchelle
17aaa90fb1 Fix JSON schema for dynamips power supply and sensors 2015-08-24 15:35:13 +02:00
Julien Duponchelle
40b8969d44 Fix tests conflict between request fixture and our http_request fixture
Also turn on stdout capture.
2015-08-24 15:09:30 +02:00
Julien Duponchelle
73454d97e1 Fix missing boot_priority in JSON schema 2015-08-24 14:40:05 +02:00
Julien Duponchelle
7cf39aac5a Complete the error message about corrupted topologies
I hope users will understand it better and send
us the .gns3 file with the error.
2015-08-24 14:06:12 +02:00
grossmj
2880168566 Removes ASAv warning in Qemu Wizard. 2015-08-22 17:20:37 -06:00
Anton Fedotov
615d9240ad EthernetSwitch: Allow to choose ethertype for QinQ outer tag. 2015-08-20 10:06:59 +03:00
grossmj
76f8aa4f60 Adds missing properties for rectangle and ellipse in schema validation. 2015-08-17 21:46:32 -06:00
grossmj
0172dd0b53 It is not necessary to catch the FileExistsError exception. 2015-08-13 20:16:41 -06:00
grossmj
ccb6a5cf0f Use Qemu 0.11.0 instead of version 0.13.0 on Windows. 2015-08-11 17:03:12 -06:00
grossmj
7dc37c9dca Removes "resources_type" references. Fixes #493. 2015-08-10 22:21:10 -06:00
grossmj
69f13621ca A thread is not needed to check for local config file changes. Fixes #607. 2015-08-10 18:33:59 -06:00
grossmj
a10f8939e2 Fixes bug when opening Node properties dialog via a double click. 2015-08-05 18:53:56 -06:00
grossmj
4b681a5e55 SecureCRT (installed on personal profile) command line. 2015-08-05 18:45:59 -06:00
265 changed files with 157561 additions and 141639 deletions

1
.gitignore vendored
View File

@@ -58,3 +58,4 @@ keys
# Custom config
/gns3_server.ini
updates
.cache

View File

@@ -1,23 +1,7 @@
language: python
sudo: required
#New container architecture
#http://docs.travis-ci.com/user/workers/container-based-infrastructure/
#sudo: false
python:
- "3.4"
cache:
apt: true
directories:
- build
before_install:
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
- sudo apt-get update -qq
- sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
- sudo apt-get install qt5-default qttools5-dev-tools
- sh scripts/prepare_travis.sh
services:
- docker
notifications:
email: false
@@ -29,9 +13,7 @@ notifications:
# on_success: change
# on_failure: always
install:
- "pip install -r dev-requirements.txt"
script:
- "xvfb-run py.test -vv" # Run tests in a fake X server
# - "pep8 --exclude=build,.git,ui"
- docker build -t gns3-gui-test .
- docker run gns3-gui-test

403
CHANGELOG
View File

@@ -1,5 +1,408 @@
# Change Log
## 1.4.2 17/02/2016
* Allow gif image (not animated) since pattent expire in 2004
* Countdown before starting the GNS3 VM
* Prevent IOU GNS3A install on Windows
* Set timeout from 1 to 3 seconds when waiting for GNS3 VM server. Ref #1034.
* Redirect stderr to stdout when executing VBoxManage or vmrun. Ref #1027.
* Update VMware banners
* Prevent a crash in progress dialog
* Update for 4K monitor
* Allow to cancel the start of the GNS3 VM
* Update the .net converter
* Detect and fix duplicate port name in topology
* Allow to open a custom console on any node
* All node are now SVG items
* Move Qt code to a module so we can add new code in differents files
* Fix rare crash when updating node
* Prevent duplicate server in server summary
* Fix a regression in Host and Cloud
* Check if GNS3 is not installed twice in doctor
* Allow to add custom command to the list
* Update Readme Python 3.4 is require
* Allow to import unknow files via GNS3A
* Fix a problem with gns3 running in background after exit
* Move common code for ports dump to vm.py
* Move common code _updatePortSettings to vm.py
* Fix a crash when searching for alternative images
* Fix a crash with corrupted topology from 1.0
* Remove all the docker code from 1.4 gui to avoid confusion
* Create a dialog for choosing the console command.
* Catch error if dynamips is disabled for local and no remote available
* Put a link for the GNS3 VM in the setup wizard
* When importing appliance explain why options is gray
* User configurable default name format for VMware and VirtualBox. Ref #748
* Changes "base name prefix" to "default name format". Ref #748.
* User configurable base name support for Dynamips, IOU and VPCS. Ref #748.
* Refactor "Import config" router dialog. Fixes #752.
* Fixes ValueError: cannot mmap an empty file. Fixes #723.
* Saves the "show port names" state in topology files. Fixes #778.
* Fix KeyError: 'midplane' when loading 7200 in some cases
* Hide the server select box for builtin switch if dynamips local is off
* Fix an issue where the Existing image button can disapear from wizard
* Fix a race condition when you ask for image list but close the windows
* Fix alignements of VMware and VirtualBox in VM choice type
* Better explanation during server choice
* Disabled remote button when we have no remote in server wizard
* Improved lookup for VMware host type. Fixes #970.
* Change some text in nodes view.
* Allow to edit a node via a right click in the node
* Show error if a problem occur when getting remote server KVM status
* Display a clean error when an appliance has an invalid JSON
* MobaXterm integration
* Allow to show the command line used to start a VM
* Add - GNS3 at the end of the windows name
* Fix a crash with doctor on windows
* Fix crash in doctor if ubridge path is empty
## 1.4.1 01/02/2016
* Improvement to detect VMware Player on Linux. Ref #970.
* You can move Dock widgets everywhere
* Link to download VIX api
* Fix SSH present in the server preferences
* Check dynamips and ubridge permission.
* Show a dialog for checking some common issues
* Show a summary with server usages
* Close project on VM when closing the project on all servers
* Allow to not rotate the text when changing all text colors
* Raise an error when psutil is too old
* Fix a race condition when closing the server
* Fix a very very rare crash in topology summary view
* Reset port label positions. Fixes #811.
* Drop SSH support
* Fix inversion of port label when loading a topology
* Fix error when importing Windows XP OVA
* Warn if configuration file contain invalid unicode characters
* Fix a crash when importing some OVA
* Reset the telnet command on Mac
* Fix only one console work for OSX
* Fix a rare crash when loading topology with missing image
* Fix a rare race condition when inserting an image
* Fix a crash when pid file is empty
* Fix a rare crash in progress dialogs
* Fix a crash if you don't have vms when importing a gns3a
* Warn user during appliance install if server is not avaible
* Fix error when you stop the GNS3 VM but break the config before
* startup_config is not mandatory inside .gns3 file
* Fix wrong host on SSH connection
* Fix select of image broken if you need to select multiple images
* Fix a crash when changing qemu cluster size to more than 512
* Fix IOU support in gns3a
* Add urxvt support
* Add a step in the wizard checking KVM support
## 1.4.0 12/01/2016
* Fix rare crash when showing the progress dialog
* Fix crash on Windows when a gui is already running
* Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389.
## 1.4.0rc3 05/01/2016
* Add information about antivirus and firewall in case of connection fail
* Change link to doc for missing router image
* On windows and OSX experimental Qemu GNS3A support
* Wait server in thread
* Fix local server non avaible in appliance wizard when local server started by hand.
* Improve pid checks
* Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide").
* Fix project non closing when you have only remote servers
* Fix Windows layout not saved in some scenario
* Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables.
* Added missing Qemu adapters to the topology schema.
* Fix Cannot save my topology getting an error message for temporary topology
* Fix creation of ASA devices
* Turn off Docker until 1.5
* Fix display of server preferences on small screen
* Fix If you turn off the local server and close the gui and reopen preferences you have an issue
* Zoc 7 support
* Fix warning when closing GUI
* Fix crash when opening a new topologies after gns3 converter failure
## 1.3.13 11/12/2015
* Release server 1.3.13
## 1.3.12 11/12/2015
* Fix warning when closing GUI
* Update links for new website.
* Ask user to send bug reports to GNS3.com
* Fix travis dependencies installation
* Drop Webkit from 1.3.X
* Fix crash when opening a new topologies after gns3 converter failure
* Set Wireshark 2.0 as default OSX version
* iTerm 2.9 support
* Add informations about GNS3 VM
* Drops securecrt.vbs
* Fix analytics report on OSX
* Analytics send windows
* Fix the progress dialog freeze bug
* Fix typo in analytics
* Send stats to GNS3 team
* Licenses compliance.
* Fix error when importing dynamips config from non existent directory
* Fix crash when url is invalid
* Add a debug level 2 in the console
## 1.4.0rc2 10/12/2015
* Prevent user turning off the Local server when using the GNS3 VM
* Force VM wizard to be modal
* Fix unicode error when exporting debug informations
* Fix Debug can't be deactivated for current session
* Support relative path for configuration file.
* Report bug to GNS3.com
* Avoid crash when cancel connection to a server
* Fix version number display twice when installing appliance
* Show a warning when you try to save as a remote topology
* Fix application restart after self upgrade
* Cleanup server autostart
* Have default console port start from 2000.
* Fix crash when opening a new topologies after gns3 converter failure
* Fix SSH support
* Fix bus error when writting on console
* Fix Ok & Cancel button in preferences are broken
* Fix After a project load failure you can't open new project
* More fix around closing the GUI
* Fix crash in progress dialog on OSX
* Kill already running zombie server
* Store the pid of the server when started
* Fix a crash in rare case after a PyQT garbage collect
* Allow to use the VNC port range for console
* Preferences dialog resize
* Fix a race condition when opening telnet from apple script
* Experimental support for tabbed terminal on OSX
* Fix validation error when saving topology with an usage
* Fix another case of not closing windows
* Fix GUI doesn't close after connection error to remote server
* Add usage text to device template and on hover
* Fix a rare crash in progress dialog
* Fix crash when displaying an error from the update
* Url encode royal tx url
* Use Royal TX URI scheme thanks to @lemonmojo
* OSX support for Royal TSX
* Merge branch 'master' into unstable
* Set Wireshark 2.0 as default OSX version
* iTerm 2.9 support
* Update debug information dialog.
* Change text for export debug information.
* Add informations about GNS3 VM
## 1.4.0rc1 12/15/2015
* Rename an appliance if the default name is already taken
* Existing image option should be hidden when none is available
* Warn users about the need to uncompress the image
* Fix crash when you have no qemu vms and use gns3a
* Change sentry key
* Fix format_exception() missing 2 required positional arguments: 'value' and 'tb' in topologyFile
* Fix dialog box not returning their result
* Log to console the Qt Message Boxes
* Drops securecrt.vbs
## 1.4.0b5 02/11/2015
* Fix crash when loading invalid appliance file
* Show a message is starting or is stopping in progress dialog
* Drop duplicate code
* Changes some references for "GNS3 VM" to "Local GNS3 VM". Ref #506.
* Fixes progress dialog remains #741.
* Support more boot order for Qemu
* Add Royal TS terminal
* Add a Qt compatible version of partial
* Show Upload filename instead of waiting for
* Fix error where ISO is detected as an OVA during gns3a import
* Removes News Dock Widget.
* Support cdrom image in missing images detections
* Fix crash when appliance is missing
* Support for capture description in Wireshark window title.
* Set the name of the VM in OSX Terminal application
* Fix crash of symbol selector if the first file is a non builtin symbol
* Install appliance from wizard instead of web app
* Fix crash when using an old version of 1.4 server
* Ensure default settings are saved when starting the app
## 1.4.0b4 19/10/2015
* Mockup of appliances wizard
* Fix tests
* Fix Crash when opening an appliance #728
* Mock up for appliance wizard.
* Registry: add -nographic to Qemu options by default. Fixes #730.
* Registry: support for initrd, cpu throttling and process priority.
* Support for modifications to a base Qemu VM (not a linked clone).
* Registry: adds support for switch category, first_port_name and port_segment_size.
* Fix traceback when exiting the GUI
* Show a download button
* Corrects some typos.
* Drops securecrt.vbs
* Display an error message if QtWebkit is not installed.
* Fix console port lost when applying settings
* Remove unused code
* Fix analytics report on OSX
* Fix invalid path with frozen application
* When raising an appliance not found error show full path
* Remove unnecessary checks to know if the local server is running.
* Analytics send windows
* Fixes issue when loading a project using VMware vmnet interfaces. Fixes #319.
* Fix the progress dialog freeze bug
* Revert "Try to solve Scanning for Appliance images doesn't end"
* Try to fix the progress dialog freeze bug
* Drop dead code from getting started dialog
* Fix Appliance installs image without adapting the filename
* Raise error if reference in GNS3a is invalid
* Fix typo in analytics
* Add information on how to debug
* Send stats to GNS3 team
* Licenses compliance.
* Handles warning notifications.
* Try to solve Scanning for Appliance images doesn't end
* Fix TypeError: 'NoneType' object is not iterable in isLocalServerRunning
* Fix crash AttributeError: 'NoneType' object has no attribute getNewProjectSettings
* Fix error when importing dynamips config from non existent directory
* Fix crash when url is invalid
* Add a debug level 2 in the console
* Fix a crash when loading appliance
* Merge branch 'master' into unstable
* Support upload of multiple vmdk file
* Support PNG in the custom symbol selection dialog
* Add custom messages when computing Idle-PC values. Fixes #704.
* Display the version of Qt in the console
* Do not crash when parsing a Qt version with a snapshot notation
* Force nc path to /usr/bin/nc on Apple
* Revert "Drop netcat for unix socket it's not supported by OSX"
* Catch errors when we have an infinite recursion when copying a folder
* Fix crash in recent files when changing locale
* Catch error when we can't extract egg
* Fix securecrt command line (backported from master)
* Updates SecureCRT command line.
* When it's an ova explain to user he need to download the ova
* OVA file support
* Ignore .cache directory
* Fix update manager crash on Windows
* Support for image in local subdirectory
* Fix duplicate code
* Fixes issue when saving Idle-PC into template. Fixes #674.
* Add link for downloading VMware
* Cache md5sum in memory when loading a gns3a
* Support symbol import from GNS3A
* Allow user to select symbol from his library
* Improve HTTP progress reliability
* Support ubuntu default VNC client (Vinagre)
* Adds the COPYING file.
* Fix error when receiving an HTTP error during HTTP progress
* Support port_name_format in GNS3 a files
* options is not mandatory in a .gns3 file
* Xshell 5 support
* Add missing gns3-converter to requirements.txt
* Fixes Qemu binaries not listed in the node configuration dialog. Fixes #683.
* Fixes SecureCRT command line.
* Speedup directory scan for images when loading a gns3a file
* Show a progress bar during directory scan when searching for appliances
* Search image by default also in the download directory
* Fixes issue when Telnet doesn't let you to login to an appliance on Linux.
## 1.3.11 07/10/2015
* Display the version of Qt in the console
* Catch errors when we have an infinite recursion when copying a folder
* Fix crash in recent files when changing locale
* Catch error when we can't extract egg
* Updates SecureCRT command line.
* Fixes issue when saving Idle-PC into template. Fixes #674.
* Adds the COPYING file.
* Xshell 5 support
* Add missing gns3-converter to requirements.txt
* Fixes issue when Telnet doesn't let you to login to an appliance on Linux.
* Improve alignments. Fixes #215.
* Spelling correction
## 1.4.0b3 22/09/15
* Add a warning if you don't select VMware or VBox in Setup Wizard
* Fix Configuration not always saved in client
* Fixes file path reference issue.
* Fix Appliance doesn't work on local Server #669
* Allows Qemu VM template editing if the server is not running. Fixes #671.
* Fixes NIO_VMNET != NIO_VMnet.
* Automatically add the -no-kvm option if -icount is detected to help with the migration of ASA VMs created before version 1.4
* Fix the Runtime error
* Improve alignments. Fixes #215.
* Updates kernel command line of ASA.
## 1.4.0beta2 17/09/15
* Drop netcat for unix socket it's not supported by OSX
* VMware fusion is supported
* Fix race conditions in http_client
* On OSX show a warning when using an old Qemu
* Tab support for xfce4-terminal
* Improve alignments. Fixes #215.
* Fixes ethertype validation error.
* Improve check for uBridge permissions.
* Fix an uuid is display instead of the server url
* Set root permission to ubridge on OSX
* Show GNS3 version at startup
* Allows to select a remote server to run a switch. Fixes #653.
* Support for packet capture on VMware VM links.
* Always use ubridge with VMware by default
* Fix PermissionError: [Errno 1] Operation not permitted when kill process
* Support drag & drop gns3a
* Fix an error when loading IOU schema with private_config
* Support open a file via double click on OSX
* Initial version of an appliance file format
* Adds docker symbols. Fixes #643.
* Call the vmnet management script from the GUI (with admin rights). Implements #639.
* Use self update only if experimental features are allowed
* Add an option for enabling experimental features
* Backport docker support from Google Summer Of Code (not enabled)
* Merge branch 'qinq_ethertype' of https://github.com/Bevaz/gns3-gui into Bevaz-qinq_ethertype
* Allows VMware VMs to use vmnet interfaces for connections without using uBridge.
* Add a firewall symbol
* Detect broken link in topologies
* Fix project not closing
* Fix autostart
* Fix file not found exception in vpcs list dir
* Add missing virtio-net-pci to the json schema
* Fix the all devices views
* Fix double click event on Note Item
* Fix Accepting insecure https connections creates additional server entry
* Allow developer to debug packet capture on Windows
* Fix saveAs error unsupported operand type(s) for +=: 'NoneType' and 'str'
* Catch error when antivirus corrupt our own JSON errors
* Add a note about VIX API require for VMware player
* Create image directory if not exists
* Allow GUI to be start with python -m gns3
* Avoid errors in qemu configuration if server has been deleted
* Fix error when the IOS image directory is not writable
* Do not crash if something intercept the call to the update server
* Fix super(): no arguments in SSH client
* Catch error when configuration file contain invalid UTF-8 chars
* Fix JSON schema for dynamips power supply and sensors
* Fix missing boot_priority in JSON schema
* Complete the error message about corrupted topologies
* Removes ASAv warning in Qemu Wizard.
* EthernetSwitch: Allow to choose ethertype for QinQ outer tag.
* Adds missing properties for rectangle and ellipse in schema validation.
* Use Qemu 0.11.0 instead of version 0.13.0 on Windows.
* Fixes bug when opening Node properties dialog via a double click.
* SecureCRT (installed on personal profile) command line.
## 1.3.10 04/09/2015
* Updates kernel command line of ASA.
* Fix file not found exception in vpcs list dir
* Fix saveAs error unsupported operand type(s) for +=: 'NoneType' and 'str'
* Catch error when antivirus corrupt our own JSON errors
* Use Qemu 0.11.0 instead of version 0.13.0 on Windows.
* Removes "resources_type" references. Fixes #493.
* Fixes bug when opening Node properties dialog via a double click.
* SecureCRT (installed on personal profile) command line.
## 1.4.0beta1 07/08/2015
* Show an error if you try to use a local server not started

50
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,50 @@
# Contributing to GNS3
We welcome contributions and bugs reports from everyone.
We are friendly so don't be afraid to ask questions.
## Bug reports
Before reporting an issue:
* check our website over at https://gns3.com
* check if an issue already exists on https://github.com/GNS3/gns3-gui
* check if an issue already exists on https://github.com/GNS3/gns3-server
Please post on our community website if you are unsure you found a bug,
you will get faster support and be able to exchange with more users.
If you are unsure which project you should create an issue for, just do
it on https://github.com/GNS3/gns3-gui we will take care of the triage.
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
## Asking for new features
The best is to start a discussion on the community website in order to get feedback
from the whole community.
## Contributing code
We welcome code contribution from everyone including beginners.
Don't be afraid to submit a half finished or mediocre contribution and we will help you.
Don't hesitate to share your plans before starting working on a contribution, we can help
you to find the best approach.
### Contributors License Agreements
We at GNS3 are eager to work with you. For small changeslittle bugfixes, correcting typos, and the likeplease just submit pull requests to any of our projects. For larger changes, though, we have to ask you to jump through a little hoop.
In particular, in order for us to accept any major patches from you, you will have to electronically sign a statement that indicates two things:
- You are willingly licensing your contributions under the terms of the open source license of the project that youre contributing to.
- You are legally able to license your contributions as stated.
The reason we do this is to ensure, to the extent possible, that we dont “taint” the projects we manage with contributions that turn out to be improper. This protects everyone who wants to use the projects, including you!
More information there: https://github.com/GNS3/cla
### Pull requests
Creating a pull request is the easiest way to contribute code. Do not hesitate to create one early when contributing for new feature in order to get our feedback.

522
COPYING Normal file
View File

@@ -0,0 +1,522 @@
GNU Public License (GPL)
------------------------
GNS3 is released under the GPLv3 (see LICENSE) with the additional
exemption that compiling, linking, and/or using OpenSSL is allowed.
GNS3 trademark
--------------
"GNS3" is a trademark of GNS3 Technologies, Inc.
Windows Driver Kit
------------------
The Windows binary distribution includes devcon.exe, a Microsoft(R)
Windows Driver Kit (WDK) sample in object code form which is
redistributed under the terms of the WDK License terms.
With respect to binaries built using the Microsoft(R) Windows
Driver Kit (WDK), GPLv3 does not extend to any WDK Distributable Code.
All WDK Distributable Code is considered by the licensors of GNS3
to constitute, or be equivalent to, "System Libraries" as defined in
section 1 of GPLv3.
OpenSSL License
---------------
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts. Actually both licenses are BSD-style
Open Source licenses. In case of any license issues related to OpenSSL
please contact openssl-core@openssl.org.
/* ====================================================================
* Copyright (c) 1998-2003 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
Original SSLeay License
-----------------------
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/
=====================================================================================================
Several fantastic pieces of free and open-source software have really get GNS3 to where it is today.
A few require that we include their license agreements within our software.
=====================================================================================================
License notice for Qt
---------------------
http://doc.qt.io/qt-4.8/gpl.html
License notice for PyQt
-----------------------
http://www.gnu.org/licenses/gpl.html
License notice for jsonschema
-----------------------------
https://github.com/Julian/jsonschema/blob/master/COPYING
Copyright (c) 2013 Julian Berman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
License notice for aiohttp
--------------------------
https://github.com/KeepSafe/aiohttp/blob/master/LICENSE.txt
Copyright (c) 2013, 2014, 2015 Nikolay Kim and Andrew Svetlov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
License notice for Jinja
------------------------
https://github.com/KeepSafe/aiohttp/blob/master/LICENSE.txt
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
Some rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License notice for raven
------------------------
https://github.com/getsentry/raven-python/blob/master/LICENSE
Copyright (c) 2015 Functional Software, Inc and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License notice for Apache Libcloud
----------------------------------
https://github.com/apache/libcloud/blob/trunk/LICENSE
Copyright (c) 2010-2015 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
License notice for requests
---------------------------
https://github.com/kennethreitz/requests/blob/master/LICENSE
Copyright (c) 2015 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
License notice for gns3-converter
---------------------------------
https://github.com/dlintott/gns3-converter/blob/master/COPYING
License notice for paramiko
---------------------------
https://github.com/paramiko/paramiko/blob/master/LICENSE
License notice for pywin32
--------------------------
https://github.com/SublimeText/Pywin32/blob/master/License.txt
Unless stated in the specfic source file, this work is
Copyright (c) 1996-2008, Greg Stein and Mark Hammond.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
Neither names of Greg Stein, Mark Hammond nor the name of contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License notice for Winpcap
--------------------------
https://www.winpcap.org/misc/copyright.htm
Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy).
Copyright (c) 2005 - 2010 CACE Technologies, Davis (California).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License notice for cpulimit
---------------------------
https://github.com/opsengine/cpulimit/blob/master/LICENSE
Copyright (C) 2005-2012, by: Angelo Marletta <angelo dot marletta at gmail dot com>
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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
License notice for Cygwin
-------------------------
https://cygwin.com/licensing.html
License notice for SuperPutty
-----------------------------
https://github.com/jimradford/superputty/blob/master/License.txt
Copyright (c) 2009 Jim Radford http://www.jimradford.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
License notice for Putty
------------------------
http://www.chiark.greenend.org.uk/~sgtatham/putty/licence.html
PuTTY is copyright 1997-2015 Simon Tatham.
Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar,
Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn,
Colin Watson, Christopher Staite, and CORE SDI S.A.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
License notice for iouyap
-------------------------
https://github.com/GNS3/iouyap/blob/master/LICENSE
License notice for Dynamips
---------------------------
https://github.com/GNS3/dynamips/blob/master/COPYING
License notice for Qemu
-----------------------
http://wiki.qemu.org/License
License notice for VPCS
-----------------------
Copyright (c) 2007-2013, Paul Meng (mirnshi@gmail.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
License notice for Python
-------------------------
https://www.python.org/download/releases/3.4.2/license/

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
# Run tests inside a container
FROM ubuntu:vivid
MAINTAINER GNS3 Team
#ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get install -y --force-yes python3.4 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3.4-dev xvfb
RUN apt-get clean
ADD dev-requirements.txt /dev-requirements.txt
ADD requirements.txt /requirements.txt
RUN pip3 install -r /dev-requirements.txt
ADD . /src
WORKDIR /src
CMD xvfb-run python3.4 -m pytest -vv

View File

@@ -18,7 +18,7 @@ You must be connected to the Internet in order to install the dependencies.
Dependencies:
- Python 3.3 or above
- Python 3.4 or above
- Setuptools
- PyQt 5 libraries
- Apache Libcloud library
@@ -50,7 +50,7 @@ Finally these commands will install the GUI as well as the rest of the dependenc
Windows
-------
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
Please use our `all-in-one installer <https://gns3.com/software/download>`_ to install the stable build.
If you install via source you need to first install:
@@ -103,7 +103,7 @@ Finally, install both the GUI & server from the source.
Or follow this `HOWTO that uses MacPorts <http://binarynature.blogspot.ca/2014/05/install-gns3-early-release-on-mac-os-x.html>`_.
Developement
Development
-------------
If you want to update the interface, modify the .ui files using QT tools. And:
@@ -113,6 +113,21 @@ If you want to update the interface, modify the .ui files using QT tools. And:
cd scripts
python build_pyqt.py
Debug
"""""
If you want to see the full logs in the internal shell you can type:
.. code:: bash
debug 2
Or start the app with --debug flag.
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
https://github.com/Kozea/wdb
Test with PyQT4
~~~~~~~~~~~~~~~~

20
gns3/__main__.py Normal file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .main import main
main()

50
gns3/application.py Normal file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from .qt import QtWidgets, QtGui, QtCore
from .version import __version__
import logging
log = logging.getLogger(__name__)
class Application(QtWidgets.QApplication):
file_open_signal = QtCore.pyqtSignal(str)
def __init__(self, argv):
super().__init__(argv)
# this info is necessary for QSettings
self.setOrganizationName("GNS3")
self.setOrganizationDomain("gns3.net")
self.setApplicationName("GNS3")
self.setApplicationVersion(__version__)
# File path if we have received the path to
# a file on system via an OSX event
self.open_file_at_startup = None
def event(self, event):
# When you double click file you receive an event
# and not the file as command line parameter
if sys.platform.startswith("darwin"):
if isinstance(event, QtGui.QFileOpenEvent):
self.open_file_at_startup = str(event.file())
self.file_open_signal.emit(str(event.file()))
return super().event(event)

View File

@@ -200,7 +200,7 @@ class ConsoleCmd(cmd.Cmd):
def do_debug(self, args):
"""
Activate or deactivate debugging messages
debug [level] (0 or 1).
debug [level] (0, 1 or 2).
"""
if '?' in args or args.strip() == "":
@@ -208,16 +208,22 @@ class ConsoleCmd(cmd.Cmd):
return
root = logging.getLogger()
ch = logging.StreamHandler(sys.stdout)
if len(args) == 1:
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
for handler in root.handlers:
if isinstance(handler, logging.StreamHandler):
root.removeHandler(handler)
root.setLevel(logging.INFO)
else:
print("Activating debugging")
root.addHandler(ch)
root.addHandler(logging.StreamHandler(sys.stdout))
if level == 1:
print("Activating debugging")
else:
print("Activating full debugging")
root.setLevel(logging.DEBUG)
from .main_window import MainWindow
MainWindow.instance().setSettings({"debug_level": level})
else:

View File

@@ -27,6 +27,7 @@ from .version import __version__
from .console_cmd import ConsoleCmd
from .pycutext import PyCutExt
from .modules import MODULES
from .local_config import LocalConfig
class ConsoleView(PyCutExt, ConsoleCmd):
@@ -40,8 +41,13 @@ class ConsoleView(PyCutExt, ConsoleCmd):
# Set introduction message
bitness = struct.calcsize("P") * 8
current_year = datetime.date.today().year
self.intro = "GNS3 management console. Running GNS3 version {} on {} ({}-bit) with Qt {}.\n" \
"Copyright (c) 2006-{} GNS3 Technologies.".format(__version__, platform.system(), bitness, QtCore.QT_VERSION_STR, current_year)
self.intro = "GNS3 management console.\nRunning GNS3 version {} on {} ({}-bit) with Python {} Qt {}.\n" \
"Copyright (c) 2006-{} GNS3 Technologies.\n" \
"Use Help -> GNS3 Doctor to detect common issues." \
"".format(__version__, platform.system(), bitness, platform.python_version(), QtCore.QT_VERSION_STR, current_year)
if LocalConfig.instance().experimental():
self.intro += "\nWARNING: Experimental features enable. You can use some unfinished features and lost data."
# Parent class initialization
try:

View File

@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import psutil
import os
import platform
import struct
@@ -28,14 +29,14 @@ except ImportError:
RAVEN_AVAILABLE = False
from .utils.get_resource import get_resource
from .version import __version__
from .version import __version__, __version_info__
import logging
log = logging.getLogger(__name__)
# Dev build
if __version__[4] != 0:
if __version_info__[3] != 0:
import faulthandler
# Display a traceback in case of segfault crash. Usefull when frozen
# Not enabled by default for security reason
@@ -49,7 +50,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://a86f12c42f7746288a81af9a9c1145a4:db8b6973bd2c448ea0a98675119ff8ee@app.getsentry.com/38506"
DSN = "sync+https://3d44e34021504514a5fb0539ae6f8f92:af41562761754b4c9beca492d1b9115d@app.getsentry.com/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
@@ -94,7 +95,7 @@ class CrashReport:
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
}
context = self._add_qt_informations(context)
context = self._add_qt_information(context)
client.tags_context(context)
try:
report = client.captureException((exception, value, tb))
@@ -103,12 +104,13 @@ class CrashReport:
return
log.info("Crash report sent with event ID: {}".format(client.get_ident(report)))
def _add_qt_informations(self, context):
def _add_qt_information(self, context):
try:
from .qt import QtCore
import sip
except ImportError:
return context
context["psutil:version"] = psutil.__version__
context["pyqt:version"] = QtCore.PYQT_VERSION_STR
context["qt:version"] = QtCore.QT_VERSION_STR
context["sip:version"] = sip.SIP_VERSION_STR

View File

@@ -0,0 +1,511 @@
#!/usr/bin/env python
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
from ..qt import QtWidgets, QtCore, QtGui, qpartial
from ..ui.appliance_wizard_ui import Ui_ApplianceWizard
from ..image_manager import ImageManager
from ..modules import Qemu
from ..registry.appliance import Appliance
from ..registry.registry import Registry
from ..registry.config import Config, ConfigException
from ..registry.image import Image
from ..utils import human_filesize
from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
from ..utils.progress_dialog import ProgressDialog
from ..servers import Servers
from ..gns3_vm import GNS3VM
from ..local_config import LocalConfig
class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
def __init__(self, parent, path):
super().__init__(parent)
self._path = path
self.setupUi(self)
images_directories = list()
images_directories.append(os.path.dirname(self._path))
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
if download_directory != "" and download_directory != os.path.dirname(self._path):
images_directories.append(download_directory)
self._registry = Registry(images_directories)
self._appliance = Appliance(self._registry, self._path)
self._registry.appendImageDirectory(os.path.join(ImageManager.instance().getDirectory(), self._appliance.image_dir_name()))
self.uiApplianceVersionTreeWidget.currentItemChanged.connect(self._applianceVersionCurrentItemChangedSlot)
self.uiRefreshPushButton.clicked.connect(self._refreshVersions)
self.uiDownloadPushButton.clicked.connect(self._downloadPushButtonClickedSlot)
self.uiImportPushButton.clicked.connect(self._importPushButtonClickedSlot)
self.uiCreateVersionPushButton.clicked.connect(self._createVersionPushButtonClickedSlot)
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
if hasattr(self, "uiVMRadioButton"):
self.uiVMRadioButton.toggled.connect(self._vmToggledSlot)
self.uiLocalRadioButton.toggled.connect(self._localToggledSlot)
if hasattr(self, "uiLoadBalanceCheckBox"):
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete
def initializePage(self, page_id):
"""
Initialize Wizard pages.
:param page_id: page identifier
"""
super().initializePage(page_id)
if self._appliance["category"] == "guest":
symbol = ":/symbols/computer.svg"
else:
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
if "qemu" in self._appliance:
type = "qemu"
elif "iou" in self._appliance:
type = "iou"
elif "dynamips" in self._appliance:
type = "dynamips"
if self.page(page_id) == self.uiInfoWizardPage:
self.uiInfoWizardPage.setTitle(self._appliance["product_name"])
self.uiDescriptionLabel.setText(self._appliance["description"])
info = (
("Category", "category"),
("Product", "product_name"),
("Vendor", "vendor_name"),
("Status", "status"),
("Maintainer", "maintainer"),
("Architecture", "qemu/arch"),
("KVM", "qemu/kvm")
)
self.uiInfoTreeWidget.clear()
for (name, key) in info:
if "/" in key:
key, subkey = key.split("/")
value = self._appliance.get(key, {}).get(subkey, None)
else:
value = self._appliance.get(key, None)
if value is None:
continue
item = QtWidgets.QTreeWidgetItem([name + ":", value])
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
self.uiInfoTreeWidget.addTopLevelItem(item)
elif self.page(page_id) == self.uiServerWizardPage:
self.uiRemoteServersComboBox.clear()
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem(server.url(), server)
if not GNS3VM.instance().isRunning():
self.uiVMRadioButton.setEnabled(False)
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
if type == "qemu":
# Qemu has issues on OSX and Windows we disallow usage of the local server
if not LocalConfig.instance().experimental():
self.uiLocalRadioButton.setEnabled(False)
elif type != "dynamips":
self.uiLocalRadioButton.setEnabled(False)
if GNS3VM.instance().isRunning():
self.uiVMRadioButton.setChecked(True)
elif Servers.instance().localServer().isLocalServerRunning() and self.uiLocalRadioButton.isEnabled():
self.uiLocalRadioButton.setChecked(True)
elif len(Servers.instance().remoteServers().values()) > 0:
self.uiRemoteRadioButton.setChecked(True)
else:
self.uiRemoteRadioButton.setChecked(False)
elif self.page(page_id) == self.uiFilesWizardPage:
self._refreshVersions()
elif self.page(page_id) == self.uiQemuWizardPage:
Qemu.instance().getQemuBinariesFromServer(self._server, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
elif self.page(page_id) == self.uiSummaryWizardPage:
self.uiSummaryTreeWidget.clear()
for key in self._appliance[type]:
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance[type][key])])
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
self.uiSummaryTreeWidget.addTopLevelItem(item)
self.uiSummaryTreeWidget.resizeColumnToContents(0)
elif self.page(page_id) == self.uiUsageWizardPage:
self.uiUsageTextEdit.setText("The appliance is available in the {} category. \n\n{}".format(
self._appliance["category"].replace("_", " "),
self._appliance.get("usage", ""))
)
elif self.page(page_id) == self.uiCheckServerWizardPage:
self.uiCheckServerLabel.setText("Please wait while checking server capacities...")
if 'qemu' in self._appliance:
if self._appliance['qemu'].get('kvm', 'require') == 'require':
self._server_check = False # If the server as the capacities for running the appliance
Qemu.instance().getQemuCapabilitiesFromServer(self._server, qpartial(self._qemuServerCapabilitiesCallback))
return
self.uiCheckServerLabel.setText("")
self._server_check = True
self.next()
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
"""
Check if server support KVM or not
"""
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
self._server_check = True
self.uiCheckServerLabel.setText("GNS3 server requirements is OK you can continue the installation")
else:
if error:
msg = result["message"]
else:
msg = "The remote server doesn't support KVM. You need a Linux server or the GNS3 VM with VMware and CPU virtualization instructions."
self.uiCheckServerLabel.setText(msg)
QtWidgets.QMessageBox.critical(self, "Qemu", msg)
self._server_check = False
def _uiServerWizardPage_isComplete(self):
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
def _refreshVersions(self):
"""
Refresh the list of files for different version of the appliance
"""
self.uiFilesWizardPage.setSubTitle("The following versions are available for " + self._appliance["product_name"] + ". Check the status of files required to install.")
self.uiApplianceVersionTreeWidget.clear()
worker = WaitForLambdaWorker(lambda: self._resfreshDialogWorker())
progress_dialog = ProgressDialog(worker, "Add appliance", "Scanning directories for files...", None, busy=True, parent=self)
progress_dialog.show()
if progress_dialog.exec_():
for version in self._appliance["versions"]:
top = QtWidgets.QTreeWidgetItem(["{} {}".format(self._appliance["product_name"], version["name"])])
size = 0
status = "Ready to install"
for image in version["images"].values():
if image["status"] == "Missing":
status = "Missing files"
size += image.get("filesize", 0)
image_widget = QtWidgets.QTreeWidgetItem(
[
"",
image["filename"],
human_filesize(image.get("filesize", 0)),
image["status"],
image["version"],
image.get("md5sum", "")
])
if image["status"] == "Missing":
image_widget.setForeground(3, QtGui.QBrush(QtGui.QColor("red")))
else:
image_widget.setForeground(3, QtGui.QBrush(QtGui.QColor("green")))
# Associated data stored are col 0: version, col 1: image
image_widget.setData(0, QtCore.Qt.UserRole, version)
image_widget.setData(1, QtCore.Qt.UserRole, image)
image_widget.setData(2, QtCore.Qt.UserRole, self._appliance)
top.addChild(image_widget)
font = top.font(0)
font.setBold(True)
top.setFont(0, font)
expand = True
if status == "Missing files":
top.setForeground(3, QtGui.QBrush(QtGui.QColor("red")))
else:
expand = False
top.setForeground(3, QtGui.QBrush(QtGui.QColor("green")))
top.setData(2, QtCore.Qt.DisplayRole, human_filesize(size))
top.setData(3, QtCore.Qt.DisplayRole, status)
top.setData(2, QtCore.Qt.UserRole, self._appliance)
top.setData(0, QtCore.Qt.UserRole, version)
self.uiApplianceVersionTreeWidget.addTopLevelItem(top)
if expand:
top.setExpanded(True)
self.uiApplianceVersionTreeWidget.resizeColumnToContents(0)
self.uiApplianceVersionTreeWidget.resizeColumnToContents(1)
self.uiApplianceVersionTreeWidget.setCurrentItem(self.uiApplianceVersionTreeWidget.topLevelItem(0))
def _resfreshDialogWorker(self):
"""
Scan local directory in order to found the images on disk
"""
for version in self._appliance["versions"]:
for image in version["images"].values():
img = self._registry.search_image_file(image["filename"], image.get("md5sum"), image.get("filesize"))
if img:
image["status"] = "Found"
image["md5sum"] = img.md5sum
image["filesize"] = img.filesize
else:
image["status"] = "Missing"
def _applianceVersionCurrentItemChangedSlot(self, current, previous):
"""
Called when user select a different item in the list of appliance files
"""
self.uiDownloadPushButton.hide()
self.uiImportPushButton.hide()
self.uiExplainDownloadLabel.hide()
if current is None:
return
image = current.data(1, QtCore.Qt.UserRole)
if image is not None:
if "direct_download_url" in image or "download_url" in image:
self.uiDownloadPushButton.show()
self.uiImportPushButton.show()
def _downloadPushButtonClickedSlot(self):
"""
Called when user want to download an appliance images.
He should have selected the file before.
"""
current = self.uiApplianceVersionTreeWidget.currentItem()
data = current.data(1, QtCore.Qt.UserRole)
if data is not None:
if "direct_download_url" in data:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["direct_download_url"]))
if "compression" in data:
QtWidgets.QMessageBox.warning(self, "Add appliance", "The file is compressed with {} you need to uncompress it before using it.".format(data["compression"]))
else:
QtWidgets.QMessageBox.warning(self, "Add appliance", "Download will redirect you where the required file can be downloaded, you may have to be registered with the vendor in order to download the file.")
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["download_url"]))
def _createVersionPushButtonClickedSlot(self):
"""
Allow user to create a new version of an appliance
"""
new_version, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Creating a new version allows to import unknown files to use with this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal)
if ok:
self._appliance.create_new_version(new_version)
self._refreshVersions()
def _importPushButtonClickedSlot(self):
"""
Called when user want to import an appliance images.
He should have selected the file before.
"""
current = self.uiApplianceVersionTreeWidget.currentItem()
disk = current.data(1, QtCore.Qt.UserRole)
path, _ = QtWidgets.QFileDialog.getOpenFileName()
if len(path) == 0:
return
image = Image(path)
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct file. The MD5 sum is {} and should be {}. For OVA you need to import the OVA/OVF not the file inside the archive.".format(image.md5sum, disk["md5sum"]))
return
config = Config()
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, self._appliance.image_dir_name()), disk["filename"]), allowed_exceptions=[OSError, ValueError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Importing the appliance...", None, busy=True, parent=self)
if not progress_dialog.exec_():
return
self._refreshVersions()
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
if self.uiQemuListComboBox.count() == 1:
self.next()
def _install(self, version):
"""
Install the appliance to GNS3
:params version: Version name
"""
try:
config = Config()
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
return False
appliance_configuration = self._appliance.search_images_for_version(version)
if self._server.isLocal():
server_string = "local"
elif self._server.isGNS3VM():
server_string = "vm"
else:
server_string = self._server.url()
while len(appliance_configuration["name"]) == 0 or not config.is_name_available(appliance_configuration["name"]):
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "The name \"{}\" is already used by another appliance".format(appliance_configuration["name"]))
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add appliance", "New name:", QtWidgets.QLineEdit.Normal, appliance_configuration["name"])
appliance_configuration["name"] = appliance_configuration["name"].strip()
if "qemu" in appliance_configuration:
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
worker = WaitForLambdaWorker(lambda: config.add_appliance(appliance_configuration, server_string), allowed_exceptions=[ConfigException, OSError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():
return False
worker = WaitForLambdaWorker(lambda: config.save(), allowed_exceptions=[ConfigException, OSError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
progress_dialog.show()
if progress_dialog.exec_():
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
return True
def nextId(self):
if self.currentPage() == self.uiServerWizardPage:
if "qemu" not in self._appliance:
return super().nextId() + 1
elif self.currentPage() == self.uiFilesWizardPage:
if "qemu" not in self._appliance:
return super().nextId() + 1
return super().nextId()
def validateCurrentPage(self):
"""
Validates the settings.
"""
if self.currentPage() == self.uiFilesWizardPage:
current = self.uiApplianceVersionTreeWidget.currentItem()
version = current.data(0, QtCore.Qt.UserRole)
appliance = current.data(2, QtCore.Qt.UserRole)
if not self._appliance.is_version_installable(version["name"]):
QtWidgets.QMessageBox.warning(self, "Appliance", "Sorry, you cannot install {} with missing files".format(appliance["name"]))
return False
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Would you like to install {} version {}?".format(appliance["name"], version["name"]),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return False
elif self.currentPage() == self.uiUsageWizardPage:
current = self.uiApplianceVersionTreeWidget.currentItem()
version = current.data(0, QtCore.Qt.UserRole)
return self._install(version["name"])
elif self.currentPage() == self.uiServerWizardPage:
if self.uiRemoteRadioButton.isChecked():
if not Servers.instance().remoteServers():
QtWidgets.QMessageBox.critical(self, "Remote server", "There is no remote server registered in your preferences")
return False
self._server = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex())
elif hasattr(self, "uiVMRadioButton") and self.uiVMRadioButton.isChecked():
gns3_vm_server = Servers.instance().vmServer()
if gns3_vm_server is None:
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The GNS3 VM is not running")
return False
self._server = gns3_vm_server
else:
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
if "qemu" in self._appliance:
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and MacOSX is not supported by the GNS3 team. Are you sur to continue?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return False
self._server = Servers.instance().localServer()
elif self.currentPage() == self.uiQemuWizardPage:
if self.uiQemuListComboBox.currentIndex() == -1:
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
return False
elif self.currentPage() == self.uiCheckServerWizardPage:
return self._server_check
return True
def _vmToggledSlot(self, checked):
"""
Slot for when the VM radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiRemoteServersGroupBox.setEnabled(False)
self.uiRemoteServersGroupBox.hide()
def _remoteServerToggledSlot(self, checked):
"""
Slot for when the remote server radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiRemoteServersGroupBox.setEnabled(True)
self.uiRemoteServersGroupBox.show()
def _localToggledSlot(self, checked):
"""
Slot for when the local server radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiRemoteServersGroupBox.setEnabled(False)
self.uiRemoteServersGroupBox.hide()
def _loadBalanceToggledSlot(self, checked):
"""
Slot for when the load balance checkbox is toggled.
:param checked: either the box is checked or not
"""
if checked:
self.uiRemoteServersComboBox.setEnabled(False)
else:
self.uiRemoteServersComboBox.setEnabled(True)

View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 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 sys
import copy
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.ui.console_command_dialog_ui import Ui_uiConsoleCommandDialog
from gns3.settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, \
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS, \
PRECONFIGURED_VNC_CONSOLE_COMMANDS, \
CUSTOM_CONSOLE_COMMANDS_SETTINGS
import logging
log = logging.getLogger(__name__)
class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
"""
This dialog allow user to select the command used to start a
console.
"""
def __init__(self, parent, console_type="telnet", current=None):
"""
:params console_type: telnet, serial or vnc
:params current: Current console command
"""
super().__init__(parent)
self.setupUi(self)
self._console_type = console_type
self._current = current
self._settings = LocalConfig.instance().loadSectionSettings("CustomConsoleCommands", CUSTOM_CONSOLE_COMMANDS_SETTINGS)
self.uiCommandComboBox.currentIndexChanged.connect(self.commandComboBoxCurrentIndexChangedSlot)
self.uiCommandPlainTextEdit.textChanged.connect(self.textChangedSlot)
self.uiSavePushButton.clicked.connect(self.savePushButtonClickedSlot)
self.uiRemovePushButton.clicked.connect(self.removePushButtonClickedSlot)
self._refreshList()
def _refreshList(self):
if self._console_type == "telnet":
self._consoles = copy.copy(PRECONFIGURED_TELNET_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
elif self._console_type == "vnc":
self._consoles = copy.copy(PRECONFIGURED_VNC_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
else:
self._consoles = copy.copy(PRECONFIGURED_SERIAL_CONSOLE_COMMANDS)
self._consoles.update(self._settings[self._console_type])
self.uiCommandComboBox.clear()
self.uiCommandComboBox.addItem("Custom", "")
for name, cmd in sorted(self._consoles.items(), key=(lambda item: item[0].lower())):
self.uiCommandComboBox.addItem(name, cmd)
if self._current:
self.uiCommandPlainTextEdit.setPlainText(self._current)
else:
self.uiCommandComboBox.setCurrentIndex(1)
def removePushButtonClickedSlot(self):
"""
Remove the custom command from the custom list
"""
self._settings[self._console_type].pop(self.uiCommandComboBox.currentText())
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
self._current = None
self._refreshList()
def savePushButtonClickedSlot(self):
"""
Save a custom command to the list
"""
name, ok = QtWidgets.QInputDialog.getText(self, "Add a command", "Command name:", QtWidgets.QLineEdit.Normal)
command = self.uiCommandPlainTextEdit.toPlainText().strip()
if ok and len(command) > 0:
if command not in self._consoles.values():
self._settings[self._console_type][name] = command
self._current = command
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
self._refreshList()
def textChangedSlot(self):
index = self.uiCommandComboBox.findData(self.uiCommandPlainTextEdit.toPlainText())
if index == -1:
index = 0
self.uiCommandComboBox.setCurrentIndex(index)
def commandComboBoxCurrentIndexChangedSlot(self, index):
self.uiRemovePushButton.hide()
# Ignore custom command
if index != 0:
self.uiCommandPlainTextEdit.setPlainText(self.uiCommandComboBox.currentData())
self.uiSavePushButton.hide()
if self.uiCommandComboBox.currentText() in self._settings[self._console_type].keys():
self.uiRemovePushButton.show()
else:
self.uiSavePushButton.show()
@staticmethod
def getCommand(parent, console_type="telnet", current=None):
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
dialog.show()
if dialog.exec_():
return (True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " "))
return (False, None)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = QtWidgets.QMainWindow()
(ok, command) = ConsoleCommandDialog.getCommand(main, console_type="telnet", current=list(PRECONFIGURED_TELNET_CONSOLE_COMMANDS.items())[0][1])
print(ok)
print(command)

View File

@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 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 psutil
import platform
import os
import stat
import sys
import struct
from gns3.qt import QtWidgets
from gns3.ui.doctor_dialog_ui import Ui_DoctorDialog
from gns3.servers import Servers
from gns3.local_config import LocalConfig
from gns3 import version
from gns3.modules.vmware import VMware
import logging
log = logging.getLogger(__name__)
class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
"""
This dialog allow user to detect error in his GNS3 installation.
If you want to add a test add a method starting by check. The
check return a tuple result and a message in case of failure.
"""
def __init__(self, parent, console=False):
super().__init__(parent)
self._console = console
self.setupUi(self)
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
for method in sorted(dir(self)):
if method.startswith('check'):
self.write(getattr(self, method).__doc__ + "...")
(res, msg) = getattr(self, method)()
if res == 0:
self.write('<span style="color: green"><strong>OK</strong></span>')
elif res == 1:
self.write('<span style="color: orange"><strong>WARNING</strong> {}</span>'.format(msg))
elif res == 2:
self.write('<span style="color: red"><strong>ERROR</strong> {}</span>'.format(msg))
self.write("<br/>")
def write(self, text):
"""
Add text to the text windows
"""
if self._console:
print(text)
self.uiDoctorResultTextEdit.setHtml(self.uiDoctorResultTextEdit.toHtml() + text)
def _okButtonClickedSlot(self):
self.accept()
def checkLocalServerEnabled(self):
"""Checking if the local server is enabled"""
if Servers.instance().shouldLocalServerAutoStart() is False:
return (2, "The local server is disabled. Go to Preferences -> Server -> Local Server and enable the local server.")
return (0, None)
def checkDevVersionOfGNS3(self):
"""Checking for stable GNS3 version"""
if version.__version_info__[3] != 0:
return (1, "You are using a unstable version of GNS3.")
return (0, None)
def checkExperimentalFeaturesEnabled(self):
"""Checking if experimental features are not enabled"""
if LocalConfig.instance().experimental():
return (1, "Experimental features are enabled. Turn them off by going to Preferences -> General -> Miscellaneous.")
return (0, None)
def checkAVGInstalled(self):
"""Checking if AVG software is not installed"""
for proc in psutil.process_iter():
try:
psinfo = proc.as_dict(["exe"])
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
except psutil.NoSuchProcess:
pass
return (0, None)
def checkFreeRam(self):
"""Checking for amount of free virtual memory"""
if int(psutil.virtual_memory().available / (1024 * 1024)) < 600:
return (2, "You have less than 600MB of available virtual memory, this could prevent nodes to start")
return (0, None)
def checkVmrun(self):
"""Checking if vmrun is installed"""
vmrun = VMware.instance().findVmrun()
if len(vmrun) == 0:
return (1, "The vmrun executable could not be found, VMware VMs cannot be used")
return (0, None)
def check64Bit(self):
"""Check if processor is 64 bit"""
if platform.architecture()[0] != "64bit":
return (2, "The architecture {} is not supported.".format(platform.architecture()[0]))
return (0, None)
def checkUbridgePermission(self):
"""Check if ubridge has the correct permission"""
if not sys.platform.startswith("win") and os.geteuid() == 0:
# we are root, so we should have privileged access.
return (0, None)
path = Servers.instance().localServerSettings().get("ubridge_path")
if path is None:
return (0, None)
if not os.path.exists(path):
return (2, "Ubridge path {path} doesn't exists".format(path=path))
request_setuid = False
if sys.platform.startswith("linux"):
if "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return(2, "Ubridge require CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
else:
# capabilities not supported
request_setuid = True
if sys.platform.startswith("darwin") or request_setuid:
if os.stat(path).st_uid != 0 or not os.stat(path).st_mode & stat.S_ISUID:
return (2, "Ubridge should be setuid. Run sudo chown root {path} and sudo chmod 4755 {path}".format(path=path))
return (0, None)
def checkDynamipsPermission(self):
"""Check if dynamips has the correct permission"""
if not sys.platform.startswith("win") and os.geteuid() == 0:
# we are root, so we should have privileged access.
return (0, None)
path = Servers.instance().localServerSettings().get("dynamips_path")
if path is None:
return (0, None)
if not os.path.exists(path):
return (2, "Dynamips path {path} doesn't exists".format(path=path))
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return(2, "Dynamips require CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
return (0, None)
def checkGNS3InstalledTwice(self):
"""Check if gns3 is not installed twice"""
if not sys.platform.startswith("win"):
return (0, None)
try:
if os.path.exists("/usr/local/bin/gns3server") and os.path.exists("/usr/bin/gns3server"):
return(2, "GNS3 is installed twice please remove it from /usr/local/bin")
except OSError:
pass
return (0, None)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = QtWidgets.QMainWindow()
dialog = DoctorDialog(main, console=True)
#dialog.show()
#exit_code = app.exec_()

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from zipfile import ZipFile
import platform
import psutil
import os
from gns3.version import __version__
from gns3.qt import QtWidgets, QtCore
from gns3.ui.export_debug_dialog_ui import Ui_ExportDebugDialog
from gns3.local_config import LocalConfig
import logging
log = logging.getLogger(__name__)
class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
"""
This dialog allow user to export useful information
for remote debugging by a GNS3 developers.
"""
def __init__(self, parent, project):
super().__init__(parent)
self._project = project
self.setupUi(self)
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
def _okButtonClickedSlot(self):
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export debug file", None, "Zip file (*.zip)", "Zip file (*.zip)")
if len(path) == 0:
self.reject()
return
log.info("Export debug information to %s", path)
try:
with ZipFile(path, 'w') as zip:
zip.writestr("debug.txt", self._getDebugData())
dir = LocalConfig.configDirectory()
for filename in os.listdir(dir):
path = os.path.join(dir, filename)
if os.path.isfile(path):
zip.write(path, filename)
dir = self._project.filesDir()
if dir:
for filename in os.listdir(dir):
path = os.path.join(dir, filename)
if os.path.isfile(path):
zip.write(path, filename)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug information: {}".format(str(e)))
self.accept()
def _getDebugData(self):
try:
connections = psutil.net_connections()
# You need to be root for OSX
except psutil.AccessDenied:
connections = None
try:
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
except UnicodeDecodeError:
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
data = """Version: {version}
OS: {os}
Python: {python}
Qt: {qt}
CPU: {cpu}
Memory: {memory}
Networks:
{addrs}
Open connections:
{connections}
Processus:
""".format(
version=__version__,
qt=QtCore.BINDING_VERSION_STR,
os=platform.platform(),
python=platform.python_version(),
memory=psutil.virtual_memory(),
cpu=psutil.cpu_times(),
connections=connections,
addrs="\n".join(addrs)
)
for proc in psutil.process_iter():
try:
psinfo = proc.as_dict(attrs=["name", "exe"])
data += "* {} {}\n".format(psinfo["name"], psinfo["exe"])
except psutil.NoSuchProcess:
pass
return data
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
print(ExportDebugDialog(None)._getDebugData())

View File

@@ -1,82 +0,0 @@
# -*- 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
from ..qt import QtCore, QtGui, QtWebKitWidgets, QtWidgets
from ..ui.getting_started_dialog_ui import Ui_GettingStartedDialog
from ..utils.get_resource import get_resource
from ..local_config import LocalConfig
class GettingStartedDialog(QtWidgets.QDialog, Ui_GettingStartedDialog):
"""
GettingStarted dialog.
"""
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.adjustSize()
self.uiWebView.page().setLinkDelegationPolicy(QtWebKitWidgets.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self._local_config = LocalConfig.instance()
settings = parent.settings()
self.uiCheckBox.setChecked(settings["hide_getting_started_dialog"])
getting_started = get_resource(os.path.join("static", "getting_started.html"))
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.fromLocalFile(getting_started))
else:
self.uiCheckBox.setChecked(True)
self.accept()
def showit(self):
"""
Either this dialog should be automatically showed at startup.
:returns: boolean
"""
return not self.uiCheckBox.isChecked()
def done(self, result):
"""
This dialog is closed.
:param result: ignored
"""
settings = self.parentWidget().settings()
settings["hide_getting_started_dialog"] = self.uiCheckBox.isChecked()
self.parentWidget().setSettings(settings)
super().done(result)
def _urlClickedSlot(self, url):
"""
Opens a clicked URL using user's default browser.
:param url: URL to open
"""
if QtGui.QDesktopServices.openUrl(url) is False:
QtWidgets.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))

View File

@@ -19,9 +19,6 @@
Dialog to configure and update node settings using widget pages.
"""
from gns3.http_client import HTTPClient
from gns3.progress import Progress
from ..qt import QtCore, QtGui, QtWidgets
from ..ui.node_properties_dialog_ui import Ui_NodePropertiesDialog
@@ -67,6 +64,10 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
for node_item in self._node_items:
if not node_item.node().initialized():
continue
# If something of one of the displayed nodes we reload everything
node_item.node().updated_signal.connect(self.resetSettings)
group_name = " {} group".format(str(node_item.node()))
parent = group_name
if parent not in self._parent_items:

View File

@@ -38,8 +38,21 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
# We adapt the max size to the screen resolution
# We need to manually do that otherwise on small screen the windows
# could be bigger than the screen instead of displaying scrollbars
height = QtWidgets.QDesktopWidget().screenGeometry().height() - 100
width = QtWidgets.QDesktopWidget().screenGeometry().width() - 100
self.setMaximumSize(QtCore.QSize(width, height))
if width > 900 and self.width() < 900:
self.resize(900, self.height())
if height > 768 and self.height() < 768:
self.resize(self.width(), 768)
self.uiTreeWidget.currentItemChanged.connect(self._showPreferencesPageSlot)
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
self._applyButton.clicked.connect(self._applyPreferences)
@@ -54,6 +67,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# Something has change?
self._modified = False
def _loadPreferencePages(self):
"""
Loads all preference pages (built-ins and from modules).
@@ -170,15 +185,14 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
if self._modified:
reply = QtWidgets.QMessageBox.warning(self,
"Preferences",
"You have unsaved preferences.\n\nContinue without saving?",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
"Preferences",
"You have unsaved preferences.\n\nContinue without saving?",
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
QtWidgets.QDialog.reject(self)
def accept(self):
"""
Saves the preferences and closes this dialog.

View File

@@ -19,7 +19,7 @@ import sys
import os
import psutil
from gns3.qt import QtCore, QtWidgets
from gns3.qt import QtCore, QtWidgets, QtGui
from gns3.servers import Servers
from ..gns3_vm import GNS3VM
from ..dialogs.preferences_dialog import PreferencesDialog
@@ -27,6 +27,7 @@ from ..ui.setup_wizard_ui import Ui_SetupWizard
from ..utils.progress_dialog import ProgressDialog
from ..utils.wait_for_vm_worker import WaitForVMWorker
from ..utils.wait_for_connection_worker import WaitForConnectionWorker
from ..version import __version__
class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
@@ -46,9 +47,11 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self._server = Servers.instance().localServer()
self.uiGNS3VMDownloadLinkUrlLabel.setText('')
self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
self.uiVMwareBannerButton.clicked.connect(self._VMwareBannerButtonClickedSlot)
settings = parent.settings()
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
@@ -58,16 +61,30 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
self.uiVmwareRadioButton.setChecked(False)
self.uiVirtualBoxRadioButton.setChecked(False)
if sys.platform.startswith("darwin"):
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_fusion_banner.jpg"))
else:
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_workstation_banner.jpg"))
def _VMwareBannerButtonClickedSlot(self):
if sys.platform.startswith("darwin"):
url = "http://send.onenetworkdirect.net/z/616461/CD225091/"
else:
url = "http://send.onenetworkdirect.net/z/616460/CD225091/"
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
def _listVMwareVMsSlot(self):
"""
Slot to refresh the VMware VMs list.
"""
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__)
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
self.uiVirtualBoxRadioButton.setChecked(False)
from gns3.modules import VMware
settings = VMware.instance().settings()
if not os.path.exists(settings["vmrun_path"]):
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API is probably not installed")
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://www.vmware.com/support/developer/vix-api/")
return
self._refreshVMListSlot()
@@ -76,6 +93,8 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
Slot to refresh the VirtualBox VMs list.
"""
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
self.uiVmwareRadioButton.setChecked(False)
from gns3.modules import VirtualBox
settings = VirtualBox.instance().settings()
@@ -160,7 +179,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
# start the GNS3 VM
servers.initVMServer()
worker = WaitForVMWorker()
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self)
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self, delay=5)
progress_dialog.show()
if progress_dialog.exec_():
previous_local_server_ip = servers.localServer().host()
@@ -175,6 +194,10 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
dialog.show()
dialog.exec_()
else:
if not self.uiVmwareRadioButton.isChecked() and not self.uiVirtualBoxRadioButton.isChecked():
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select VMware or VirtualBox")
else:
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select a VM. If no VM is listed, check if the GNS3 VM is correctly imported and press refresh.")
return False
elif self.currentPage() == self.uiAddVMsWizardPage:
@@ -216,9 +239,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
Refresh the list of VM available in VMware or VirtualBox.
"""
if not Servers.instance().localServerIsRunning():
QtWidgets.QMessageBox.critical(self, "Local server", "{}".format("Local server is not running"))
return
server = Servers.instance().localServer()
if self.uiVmwareRadioButton.isChecked():
server.get("/vmware/vms", self._getVMsFromServerCallback)

View File

@@ -21,10 +21,10 @@ Dialog to change node symbols.
import os
from ..qt import QtSvg, QtCore, QtGui, QtWidgets
from ..items.svg_node_item import SvgNodeItem
from ..items.pixmap_node_item import PixmapNodeItem
from ..qt import QtCore, QtGui, QtWidgets
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from ..ui.symbol_selection_dialog_ui import Ui_SymbolSelectionDialog
from ..servers import Servers
import logging
log = logging.getLogger(__name__)
@@ -49,56 +49,71 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
self.uiCustomSymbolRadioButton.toggled.connect(self._customSymbolToggledSlot)
self.uiBuiltInSymbolRadioButton.toggled.connect(self._builtInSymbolToggledSlot)
self.uiSearchLineEdit.textChanged.connect(self._searchTextChangedSlot)
self.uiBuiltinSymbolOnlyCheckBox.toggled.connect(self._builtinSymbolOnlyToggledSlot)
self._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
self._symbols_path = Servers.instance().localServerSettings()["symbols_path"]
selected_symbol = symbol
if not self._items:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
else:
first_item = items[0]
if isinstance(first_item, SvgNodeItem):
custom_symbol = first_item.renderer().objectName()
if not custom_symbol:
symbol_name = first_item.node().defaultSymbol()
else:
symbol_name = custom_symbol
selected_symbol = symbol_name
elif isinstance(first_item, PixmapNodeItem):
selected_symbol = first_item.pixmapSymbolPath()
custom_symbol = True
self.uiBuiltInSymbolRadioButton.setChecked(True)
self.uiSymbolListWidget.setFocus()
self.uiSymbolListWidget.setIconSize(QtCore.QSize(64, 64))
symbol_resources = QtCore.QResource(":/symbols")
for symbol in symbol_resources.children():
if symbol.endswith(".svg"):
self._symbol_items = []
symbols = symbol_resources.children()
try:
for file in os.listdir(self._symbols_path):
symbols.append(file)
except OSError:
pass
symbols.sort()
for symbol in symbols:
if symbol.endswith(".svg") or symbol.endswith(".png"):
name = os.path.splitext(symbol)[0]
item = QtWidgets.QListWidgetItem(self.uiSymbolListWidget)
self._symbol_items.append(item)
item.setText(name)
resource_path = ":/symbols/" + symbol
svg_renderer = QtSvg.QSvgRenderer(resource_path)
if resource_path == selected_symbol:
# this is a built-in symbol
custom_symbol = False
self.uiSymbolListWidget.setCurrentItem(item)
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
# Set the ARGB to 0 to prevent rendering artifacts
image.fill(0x00000000)
if os.path.exists(os.path.join(self._symbols_path, symbol)):
svg_renderer = QImageSvgRenderer(os.path.join(self._symbols_path, symbol))
else:
resource_path = ":/symbols/" + symbol
svg_renderer = QImageSvgRenderer(resource_path)
svg_renderer.render(QtGui.QPainter(image))
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
item.setIcon(icon)
if custom_symbol:
# this is a custom symbol
self.uiCustomSymbolRadioButton.setChecked(True)
self.uiSymbolLineEdit.setText(selected_symbol)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(selected_symbol))
self.uiBuiltInGroupBox.setEnabled(False)
self.uiBuiltInGroupBox.hide()
self.adjustSize()
def _builtinSymbolOnlyToggledSlot(self, checked):
self._filter()
def _searchTextChangedSlot(self, text):
self._filter()
def _filter(self):
"""
Hide element not matching the search
"""
text = self.uiSearchLineEdit.text()
for item in self._symbol_items:
if self.uiBuiltinSymbolOnlyCheckBox.isChecked() and not QtCore.QResource(":/symbols/{}.svg".format(item.text())).isValid():
item.setHidden(True)
else:
if len(text.strip()) == 0 or text.strip().lower() in item.text().lower():
item.setHidden(False)
else:
item.setHidden(True)
def _customSymbolToggledSlot(self, checked):
"""
Slot for when the custom symbol radio button is toggled.
@@ -132,56 +147,42 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
Applies the selected symbol to the items.
"""
if self.uiSymbolListWidget.isEnabled():
current = self.uiSymbolListWidget.currentItem()
if current:
name = current.text()
path = ":/symbols/{}.svg".format(name)
renderer = QtSvg.QSvgRenderer(path)
renderer.setObjectName(path)
for item in self._items:
if isinstance(item, SvgNodeItem):
item.setSharedRenderer(renderer)
else:
pixmap = QtGui.QPixmap(path)
if not pixmap.isNull():
item.setPixmap(pixmap)
item.setPixmapSymbolPath(path)
else:
QtWidgets.QMessageBox.critical(self, "Built-in SVG symbol", "Built-in SVG symbol cannot be applied on Pixmap node item")
return False
else:
symbol_path = self.uiSymbolLineEdit.text()
pixmap = QtGui.QPixmap(symbol_path)
if not pixmap.isNull():
for item in self._items:
if isinstance(item, PixmapNodeItem):
item.setPixmap(pixmap)
item.setPixmapSymbolPath(symbol_path)
else:
renderer = QtSvg.QSvgRenderer(symbol_path)
renderer.setObjectName(symbol_path)
if renderer.isValid():
item.setSharedRenderer(renderer)
else:
QtWidgets.QMessageBox.critical(self, "Custom pixmap symbol", "Custom pixmap symbol which is not SVG format cannot be applied on SVG node item")
return False
symbol_path = self.getSymbol()
pixmap = QtGui.QPixmap(symbol_path)
if not pixmap.isNull():
for item in self._items:
renderer = QImageSvgRenderer(symbol_path)
renderer.setObjectName(symbol_path)
if renderer.isValid():
item.setSharedRenderer(renderer)
else:
QtWidgets.QMessageBox.critical(self, "Custom pixmap symbol", "Invalid image")
return False
return True
def getSymbol(self):
if self.uiSymbolListWidget.isEnabled():
current = self.uiSymbolListWidget.currentItem()
name = current.text()
normal_symbol = ":/symbols/{}.svg".format(name)
if current:
name = current.text()
if QtCore.QResource(":/symbols/{}.svg".format(name)).isValid():
return ":/symbols/{}.svg".format(name)
else:
symbol_path = os.path.join(self._symbols_path, "{}.svg".format(name))
if not os.path.exists(symbol_path):
symbol_path = os.path.join(self._symbols_path, "{}.png".format(name))
return symbol_path
else:
normal_symbol = self.uiSymbolLineEdit.text()
return normal_symbol
return self.uiSymbolLineEdit.text()
return None
def _symbolBrowserSlot(self):
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*.*)"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._symbols_dir, file_formats)
if not path:
return

View File

@@ -52,6 +52,10 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
self.uiPlainTextEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
if len(self._items) == 1:
self.uiApplyColorToAllItemsCheckBox.setChecked(True)
self.uiApplyColorToAllItemsCheckBox.hide()
self.uiApplyRotationToAllItemsCheckBox.setChecked(True)
self.uiApplyRotationToAllItemsCheckBox.hide()
self.uiApplyTextToAllItemsCheckBox.setChecked(True)
self.uiApplyTextToAllItemsCheckBox.hide()
@@ -90,9 +94,11 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
"""
for item in self._items:
item.setDefaultTextColor(self._color)
item.setFont(self.uiPlainTextEdit.font())
item.setRotation(self.uiRotationSpinBox.value())
if self.uiApplyColorToAllItemsCheckBox.isChecked():
item.setDefaultTextColor(self._color)
if self.uiApplyRotationToAllItemsCheckBox.isChecked():
item.setRotation(self.uiRotationSpinBox.value())
if item.editable() and self.uiApplyTextToAllItemsCheckBox.isChecked():
item.setPlainText(self.uiPlainTextEdit.toPlainText())

View File

@@ -112,8 +112,8 @@ class VMWithImagesWizard(VMWizard):
User select a different image in the combo box
"""
item = combo_box.itemData(index)
if item and item["filename"]:
line_edit.setText(item["filename"])
if item and item["path"]:
line_edit.setText(item["path"])
else:
line_edit.setText("")
@@ -130,7 +130,7 @@ class VMWithImagesWizard(VMWizard):
browser.hide()
line_edit.hide()
if combo_box.count() > 0:
line_edit.setText(combo_box.itemData(combo_box.currentIndex())["filename"])
line_edit.setText(combo_box.itemData(combo_box.currentIndex())["path"])
else:
combo_box.hide()
line_edit.setText("")
@@ -160,8 +160,35 @@ class VMWithImagesWizard(VMWizard):
QtWidgets.QMessageBox.critical(self, "Images", "Error while getting the VMs: {}".format(result["message"]))
return
for combo_box in self._images_combo_boxes:
combo_box.clear()
for vm in result:
combo_box.addItem(vm["filename"], vm)
# Wizard is closed
if self.currentPage() is None:
return
if len(result) == 0:
for radio_button in self._radio_existing_images_buttons:
if radio_button.isChecked() and self._widgetOnCurrentPage(radio_button):
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
if button != radio_button:
button.setChecked(True)
button.hide()
else:
for radio_button in self._radio_existing_images_buttons:
if self._widgetOnCurrentPage(radio_button):
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
if button == radio_button:
button.setChecked(True)
button.show()
for combo_box in self._images_combo_boxes:
if self._widgetOnCurrentPage(combo_box):
combo_box.clear()
for vm in result:
combo_box.addItem(vm["path"], vm)
def _widgetOnCurrentPage(self, widget):
"""
:returns Boolean True if widget is current active Wizard page
"""
return self.currentPage().findChild(widget.__class__, widget.objectName()) is not None

View File

@@ -35,6 +35,8 @@ class VMWizard(QtWidgets.QWizard):
super().__init__(parent)
self.setupUi(self)
self.setModal(True)
self._devices = devices
self._use_local_server = use_local_server
@@ -51,7 +53,6 @@ class VMWizard(QtWidgets.QWizard):
if hasattr(self, "uiLoadBalanceCheckBox"):
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
# By default we use the local server
self._server = Servers.instance().localServer()
self.uiLocalRadioButton.setChecked(True)
@@ -61,7 +62,6 @@ class VMWizard(QtWidgets.QWizard):
# skip the server page if we use the local server
self.setStartId(1)
def _vmToggledSlot(self, checked):
"""
Slot for when the VM radio button is toggled.
@@ -107,22 +107,21 @@ class VMWizard(QtWidgets.QWizard):
if self.page(page_id) == self.uiServerWizardPage:
self.uiRemoteServersComboBox.clear()
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem(server.url(), server)
if len(Servers.instance().remoteServers().values()) == 0:
self.uiRemoteRadioButton.setEnabled(False)
else:
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem(server.url(), server)
if hasattr(self, "uiVMRadioButton") and not GNS3VM.instance().isRunning():
self.uiVMRadioButton.setEnabled(False)
if hasattr(self, "uiVMRadioButton") and GNS3VM.instance().isRunning():
self.uiVMRadioButton.setChecked(True)
elif self._use_local_server and self.uiLocalRadioButton.isChecked():
self.uiLocalRadioButton.setChecked(True)
else:
self.uiRemoteRadioButton.setChecked(True)
else:
if self.uiLocalRadioButton.isChecked():
servers = Servers.instance()
if servers.localServerAutoStart() and not servers.localServerIsRunning():
QtWidgets.QMessageBox.critical(self, "Wizard", "Local server is not running")
def validateCurrentPage(self):
"""

View File

@@ -41,6 +41,8 @@ class GNS3VM:
def __init__(self):
self._is_running = False
# The current running vboxmanage and vmrun process
self._running_process = None
def settings(self):
"""
@@ -60,8 +62,38 @@ class GNS3VM:
Servers.instance().setVMsettings(settings)
@staticmethod
def execute_vmrun(subcommand, args, timeout=60):
def killRunningProcess(self):
"""
Kill the VBoxManage or vmrun process if running
"""
if self._running_process is not None:
self._running_process.kill()
self._running_process.wait()
self._running_process = None
def _process_check_output(self, command, timeout=None):
# Original code from Python's subprocess.check_output
# https://github.com/python/cpython/blob/3.4/Lib/subprocess.py
with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
self._running_process = process
try:
output, unused_err = process.communicate(None, timeout=timeout)
except subprocess.TimeoutExpired:
process.kill()
output, unused_err = process.communicate()
self._running_process = None
raise subprocess.TimeoutExpired(process.args, timeout, output=output)
except:
self.killRunningProcess()
raise
retcode = process.poll()
if retcode:
self._running_process = None
raise subprocess.CalledProcessError(retcode, process.args, output=output)
self._running_process = None
return output.decode("utf-8", errors="ignore").strip()
def execute_vmrun(self, subcommand, args, timeout=60):
from gns3.modules.vmware import VMware
vmware_settings = VMware.instance().settings()
@@ -73,11 +105,10 @@ class GNS3VM:
command = [vmrun_path, "-T", host_type, subcommand]
command.extend(args)
log.debug("Executing vmrun with command: {}".format(command))
output = subprocess.check_output(command, timeout=timeout)
return output.decode("utf-8", errors="ignore").strip()
@staticmethod
def execute_vboxmanage(subcommand, args, timeout=60):
return self._process_check_output(command, timeout=timeout)
def execute_vboxmanage(self, subcommand, args, timeout=60):
from gns3.modules.virtualbox import VirtualBox
virtualbox_settings = VirtualBox.instance().settings()
@@ -85,8 +116,7 @@ class GNS3VM:
command = [vboxmanage_path, "--nologo", subcommand]
command.extend(args)
log.debug("Executing VBoxManage with command: {}".format(command))
output = subprocess.check_output(command, timeout=timeout)
return output.decode("utf-8", errors="ignore").strip()
return self._process_check_output(command, timeout=timeout)
@staticmethod
def parse_vmx_file(path):
@@ -254,6 +284,9 @@ class GNS3VM:
if self._is_running and (vm_settings["auto_stop"] or force):
try:
if vm_settings["virtualization"] == "VMware":
if vm_settings["vmx_path"] is None:
log.error("No vm path configured, can't stop the VM")
return
self.execute_vmrun("stop", [vm_settings["vmx_path"], "soft"])
elif vm_settings["virtualization"] == "VirtualBox":
self.execute_vboxmanage("controlvm", [vm_settings["vmname"], "acpipowerbutton"], timeout=3)

View File

@@ -22,13 +22,11 @@ Graphical view on the scene where items are drawn.
import logging
import os
import pickle
import functools
from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets
from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets, qpartial
from .servers import Servers
from .items.node_item import NodeItem
from .items.svg_node_item import SvgNodeItem
from .items.pixmap_node_item import PixmapNodeItem
from .dialogs.node_properties_dialog import NodePropertiesDialog
from .link import Link
from .node import Node
@@ -42,8 +40,11 @@ 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 .dialogs.console_command_dialog import ConsoleCommandDialog
from .local_config import LocalConfig
from .progress import Progress
from .utils.server_select import server_select
from .utils.normalize_filename import normalize_filename
# link items
from .items.link_item import LinkItem
@@ -293,36 +294,36 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.deleteLinkSlot(link_id)
return
# ugly multi-link management
# FIXME: taken from old GNS3 and has a bug!
multi = 0
d1 = 0
d2 = 1
link_items = source_item.links()
for link_item in link_items:
if link_item.destinationItem().node().id() == destination_item.node().id():
d1 += 1
if link_item.sourceItem().node().id() == destination_item.node().id():
d2 += 1
if len(link_items) > 0:
if d2 - d1 == 2:
source_port, destination_port = destination_port, source_port
source_item, destination_item = destination_item, source_item
multi = d1 + 1
elif d1 >= d2:
source_port, destination_port = destination_port, source_port
source_item, destination_item = destination_item, source_item
multi = d2
else:
multi = d1
# MAX 7 links on the scene between 2 nodes
if multi > 3:
multi = 0
# Multi-link management
#
# multi is the offset of the link
# +------+ multi = -1 Link 2 +-------+
# | +-----------------------------+ |
# | R1 | | R2 |
# | | multi = 0 Link 1 | |
# | +-----------------------------+ |
# | | multi = 1 Link 3 | |
# +------+-----------------------------+-------+
if source_item == destination_item:
multi = 0
else:
multi = 0
link_items = source_item.links()
for link_item in link_items:
if link_item.destinationItem().node().id() == destination_item.node().id():
multi += 1
if link_item.sourceItem().node().id() == destination_item.node().id():
multi += 1
# MAX 7 links on the scene between 2 nodes
if multi > 7:
multi = 0
# Pair item represent the bottom links
elif multi % 2 == 0:
multi = multi // 2
else:
multi = -multi // 2
if link.sourcePort().linkType() == "Serial" or (source_port.isStub() and link.destinationPort().linkType() == "Serial"):
link_item = SerialLinkItem(source_item, source_port, destination_item, destination_port, link, multilink=multi)
@@ -396,6 +397,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
elif source_port.linkType() != destination_port.linkType():
QtWidgets.QMessageBox.critical(self, "Connection", "Cannot connect this port!")
return
elif source_port.defaultNio() != destination_port.defaultNio():
QtWidgets.QMessageBox.critical(self, "Connection", "These nodes cannot be connected together ({} != {})".format(source_port.defaultNio().__name__,
destination_port.defaultNio().__name__))
return
if source_item.node().server().protocol() != destination_item.node().server().protocol():
QtWidgets.QMessageBox.critical(self, "Connection", "Sorry, you cannot connect a device running on an insecure server to a device running on a secure server.")
@@ -548,7 +553,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
factor = self.transform().scale(scale_factor, scale_factor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
if (factor < 0.10 or factor > 10):
if factor < 0.10 or factor > 10:
return
self.scale(scale_factor, scale_factor)
@@ -626,8 +631,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
item.parentItem().setSelected(True)
self.changeHostnameActionSlot()
return
else:
super().mouseDoubleClickEvent(event)
super().mouseDoubleClickEvent(event)
def configureSlot(self, items=None):
"""
@@ -635,14 +639,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
if not items:
items = self.scene().selectedItems()
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and item.node().initialized():
items.append(item)
with Progress.instance().context(min_duration=0):
node_properties = NodePropertiesDialog(items, self._main_window)
node_properties.setModal(True)
node_properties.show()
node_properties.exec_()
for item in items:
item.setSelected(False)
def dragMoveEvent(self, event):
"""
@@ -696,7 +701,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
return
path = event.mimeData().urls()[0].toLocalFile()
if os.path.isfile(path) and self._main_window.checkForUnsavedChanges():
self._main_window.loadProject(path)
self._main_window.loadPath(path)
event.acceptProposedAction()
else:
event.ignore()
@@ -755,6 +760,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
console_action.triggered.connect(self.consoleActionSlot)
menu.addAction(console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "console"), items)):
console_edit_action = QtWidgets.QAction("Custom console", menu)
console_edit_action.setIcon(QtGui.QIcon(':/icons/console_edit.svg'))
console_edit_action.triggered.connect(self.customConsoleActionSlot)
menu.addAction(console_edit_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
aux_console_action = QtWidgets.QAction("Auxiliary console", menu)
aux_console_action.setIcon(QtGui.QIcon(':/icons/aux-console.svg'))
@@ -839,6 +850,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
style_action.triggered.connect(self.styleActionSlot)
menu.addAction(style_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "commandLine"), items)):
# Action: Get command line
show_in_file_manager_action = QtWidgets.QAction("Command line", menu)
show_in_file_manager_action.setIcon(QtGui.QIcon(':/icons/console.svg'))
show_in_file_manager_action.triggered.connect(self.getCommandLineSlot)
menu.addAction(show_in_file_manager_action)
# item must have no parent
if True in list(map(lambda item: item.parentItem() is None, items)):
@@ -968,7 +986,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
break
if os.path.exists(vm_dir):
log.debug("Open %s in file manage" )
log.debug("Open %s in file manage")
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(vm_dir)) is False:
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(vm_dir))
break
@@ -994,38 +1012,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
# returns True to ignore this node.
return True
if hasattr(node, "serialConsole") and node.serialConsole():
try:
from .serial_console import serialConsole
serialConsole(node.name(), node.serialPipe())
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
return False
else:
name = node.name()
if aux:
console_port = node.auxConsole()
if console_port is None:
QtWidgets.QMessageBox.critical(self, "Console", "AUX console port not allocated for {}".format(name))
return False
else:
console_port = node.console()
console_type = "telnet"
if "console_type" in node.settings():
console_type = node.settings()["console_type"]
try:
from .telnet_console import nodeTelnetConsole
from .vnc_console import vncConsole
if console_type == "telnet":
nodeTelnetConsole(name, node.server(), console_port)
elif console_type == "vnc":
vncConsole(node.server().host(), console_port)
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
return False
try:
node.openConsole(aux=aux)
except (OSError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
return False
return True
def consoleFromItems(self, items):
@@ -1045,7 +1036,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
counter = 0
for name in sorted(nodes.keys()):
node = nodes[name]
callback = functools.partial(self.consoleToNode, node)
callback = qpartial(self.consoleToNode, node)
self._main_window.run_later(counter, callback)
counter += delay
@@ -1057,6 +1048,25 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.consoleFromItems(self.scene().selectedItems())
def customConsoleActionSlot(self):
"""
Allow user to use a custom console for this VM
"""
current_cmd = None
console_type = "telnet"
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
current_cmd = item.node().consoleCommand()
console_type = item.node().consoleType()
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
if ok:
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
node = item.node()
node.openConsole(command=cmd)
def auxConsoleFromItems(self, items):
"""
Aux console from scene items.
@@ -1074,7 +1084,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
counter = 0
for name in sorted(nodes.keys()):
node = nodes[name]
callback = functools.partial(self.consoleToNode, node, aux=True)
callback = qpartial(self.consoleToNode, node, aux=True)
self._main_window.run_later(counter, callback)
counter += delay
@@ -1111,23 +1121,50 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._import_config_dir = self._main_window.project().filesDir()
item = items[0]
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Import config",
self._import_config_dir,
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
self._import_config_dir = os.path.dirname(path)
item.node().importConfig(path)
if hasattr(item.node(), "importPrivateConfig"):
# this node can have one startup-config and one private-config
default_startup_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + "_startup-config.cfg"
if os.path.exists(default_startup_config_path):
default_import_path = default_startup_config_path
else:
default_import_path = self._import_config_dir
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Import startup-config",
default_import_path,
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
self._import_config_dir = os.path.dirname(path)
item.node().importConfig(path)
default_private_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + "_private-config.cfg"
if os.path.exists(default_private_config_path):
default_import_path = default_private_config_path
else:
default_import_path = self._import_config_dir
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Import private-config",
self._import_config_dir,
default_import_path,
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
item.node().importPrivateConfig(path)
else:
# this node has just one config
default_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + ".cfg"
if os.path.exists(default_config_path):
default_import_path = default_config_path
else:
default_import_path = self._import_config_dir
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import config",
default_import_path,
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
self._import_config_dir = os.path.dirname(path)
item.node().importConfig(path)
def exportConfigActionSlot(self):
"""
@@ -1155,12 +1192,17 @@ class GraphicsView(QtWidgets.QGraphicsView):
item = items[0]
if hasattr(item.node(), "importPrivateConfig"):
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export startup-config", self._export_config_dir)
# this node can have one startup-config and one private-config
default_startup_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + "_startup-config.cfg"
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export startup-config", default_startup_config_path)
self._export_config_dir = os.path.dirname(config_path)
private_config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export private-config", self._export_config_dir)
default_private_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + "_private-config.cfg"
private_config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export private-config", default_private_config_path)
item.node().exportConfig(config_path, private_config_path)
else:
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export config", self._export_config_dir)
# this node has just one config
default_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + ".cfg"
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export config", default_config_path)
self._export_config_dir = os.path.dirname(config_path)
item.node().exportConfig(config_path)
@@ -1201,6 +1243,31 @@ class GraphicsView(QtWidgets.QGraphicsView):
else:
QtWidgets.QMessageBox.warning(self, "Capture", "No port available for packet capture on {}".format(node.name()))
def getCommandLineSlot(self):
"""
Slot to receive events from the get command line action in the
contextual menu.
"""
items = self.scene().selectedItems()
if len(items) != 1:
QtWidgets.QMessageBox.critical(self, "Command line", "Please select only one router")
return
item = items[0]
if isinstance(item, NodeItem) and hasattr(item.node(), "commandLine"):
router = item.node()
if router.commandLine() is None:
QtWidgets.QMessageBox.warning(self, "Command line", "Get command line is not supported for this type of node.")
elif router.commandLine() == '':
QtWidgets.QMessageBox.warning(self, "Command line", "Please start the node in order to get the command line.")
else:
dialog = QtWidgets.QInputDialog(self)
dialog.setOptions(QtWidgets.QInputDialog.NoButtons)
dialog.setLabelText("Command used to start the VM:")
dialog.setTextValue(router.commandLine())
dialog.show()
dialog.exec_()
def idlepcActionSlot(self):
"""
Slot to receive events from the idlepc action in the
@@ -1336,8 +1403,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in self.scene().selectedItems():
if item.parentItem() is None:
if horizontal_pos is None:
horizontal_pos = item.y()
item.setPos(item.x(), horizontal_pos)
horizontal_pos = item.y() + item.boundingRect().height() / 2
item.setPos(item.x(), horizontal_pos - item.boundingRect().height() / 2)
def verticalAlignmentSlot(self):
"""
@@ -1349,8 +1416,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
for item in self.scene().selectedItems():
if item.parentItem() is None:
if vertical_position is None:
vertical_position = item.x()
item.setPos(vertical_position, item.y())
vertical_position = item.x() + item.boundingRect().width() / 2
item.setPos(vertical_position - item.boundingRect().width() / 2, item.y())
def raiseLayerActionSlot(self):
"""
@@ -1405,6 +1472,25 @@ class GraphicsView(QtWidgets.QGraphicsView):
elif item.parentItem() is None:
item.delete()
@staticmethod
def allocateServer(node_data, module_instance):
"""
Allocates a server.
:returns: allocated server (HTTPClient instance)
"""
from .main_window import MainWindow
mainwindow = MainWindow.instance()
allow_local_server = True
if "builtin" in node_data:
allow_local_server = module_instance.settings()["use_local_server"]
server = server_select(mainwindow, allow_local_server=allow_local_server)
if server is None:
raise ModuleError("Please select a server")
return server
def createNode(self, node_data, pos):
"""
Creates a new node on the scene.
@@ -1428,7 +1514,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
raise ModuleError("Could not find any module for {}".format(node_class))
if "server" not in node_data:
server = node_module.allocateServer(node_class)
server = self.allocateServer(node_data, instance)
elif node_data["server"] == "local":
server = Servers.instance().localServer()
elif node_data["server"] == "vm":
@@ -1452,12 +1538,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
node.warning_signal.connect(self._main_window.uiConsoleTextEdit.writeWarning)
node.server_error_signal.connect(self._main_window.uiConsoleTextEdit.writeServerError)
if QtSvg.QSvgRenderer(node_data["symbol"]).isValid():
node_item = SvgNodeItem(node, node_data["symbol"])
else:
node_item = PixmapNodeItem(node, node_data["symbol"])
node_item = SvgNodeItem(node, node_data["symbol"])
node_module.setupNode(node, node_data["name"])
except ModuleError as e:
# If no server is available a ValueError is raised
except (ModuleError, ValueError) as e:
QtWidgets.QMessageBox.critical(self, "Node creation", "{}".format(e))
return

View File

@@ -18,15 +18,15 @@
import json
import http
import copy
import ipaddress
import uuid
import urllib.request
import pathlib
import base64
from functools import partial
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork
from .qt import QtCore, QtNetwork, qpartial
from .network_client import getNetworkUrl
from .utils import parse_version
@@ -54,7 +54,9 @@ class HTTPClient(QtCore.QObject):
# Callback class used for displaying progress
_progress_callback = None
connected_signal = QtCore.Signal()
connection_connected_signal = QtCore.Signal()
connection_closed_signal = QtCore.Signal()
system_usage_updated_signal = QtCore.Signal()
connection_error_signal = QtCore.Signal(str)
def __init__(self, settings, network_manager):
@@ -64,7 +66,10 @@ class HTTPClient(QtCore.QObject):
self._scheme = settings.get("protocol", "http")
self._host = settings["host"]
self._http_host = settings["host"]
if "http_host" in settings:
self._http_host = settings["http_host"]
else:
self._http_host = settings["host"]
self._port = int(settings["port"])
self._http_port = int(settings["port"])
self._user = settings.get("user", None)
@@ -76,6 +81,7 @@ class HTTPClient(QtCore.QObject):
self._ram_limit = settings.get("ram_limit", 0)
self._allocated_ram = 0
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", None)
self._usage = None
self._network_manager = network_manager
@@ -86,25 +92,6 @@ class HTTPClient(QtCore.QObject):
self._id = HTTPClient._instance_count
HTTPClient._instance_count += 1
def getTunnel(self, port):
"""
Get a tunnel to the remote port.
For HTTP standard client it's the same port. For SSH it will create a new tunnel.
:param port: Remote port
:returns: Tuple host, port to connect
"""
return self._host, port
def releaseTunnel(self, port):
"""
Release a tunnel to the remote port.
For HTTP standard client it's do nothing
:param port: Allocated remote port
"""
pass
def settings(self):
"""
Return a dictionnary with server settings
@@ -283,6 +270,7 @@ class HTTPClient(QtCore.QObject):
"""
log.info("Connection to %s closed", self.url())
self._connected = False
self.connection_closed_signal.emit()
def isLocalServerRunning(self):
"""
@@ -295,13 +283,9 @@ class HTTPClient(QtCore.QObject):
if json_data is None or status != 200:
return False
else:
version = json_data.get("version")
local_server = json_data.get("local", False)
if version != __version__:
log.debug("Client version {} differs with server version {}".format(__version__, version))
return False
if not local_server:
log.debug("Running server is not a GNS3 local server (not started with --local)")
version = json_data.get("version", None)
if version is None:
log.debug("Server is not a GNS3 server")
return False
return True
@@ -333,12 +317,18 @@ class HTTPClient(QtCore.QObject):
return response.status, json_data
else:
return response.status, None
except http.client.InvalidURL as e:
log.warn("Invalid local server url: {}".format(e))
return 0, None
except urllib.error.URLError:
# Connection refused. It's a normal behavior if server is not started
return 0, None
except urllib.error.HTTPError as e:
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
return e.code, None
except (OSError, http.client.BadStatusLine, ValueError) as e:
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
return 0, None
return 0, None
def get(self, path, callback, **kwargs):
"""
@@ -399,13 +389,11 @@ class HTTPClient(QtCore.QObject):
return QtNetwork.QNetworkRequest(url)
# FIXME: connect is a method in parent class (QObject)
def connect(self, query, callback):
def _connect(self, query):
"""
Initialize the connection
:param query: The query to execute when all network stack is ready
:param callback: User callback when connection is finish
"""
self.executeHTTPQuery("GET", "/version", query, {})
@@ -426,11 +414,11 @@ class HTTPClient(QtCore.QObject):
"""
if self._connected:
return self.executeHTTPQuery(method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
return self.executeHTTPQuery(method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
else:
log.info("Connection to {}".format(self.url()))
query = partial(self._callbackConnect, method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
self.connect(query, callback)
query = qpartial(self._callbackConnect, method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
self._connect(query)
def _connectionError(self, callback, msg=""):
"""
@@ -440,10 +428,17 @@ class HTTPClient(QtCore.QObject):
:param msg: An optional additional message for the callback
"""
if len(msg) > 0:
msg = "Can't connect to server {}: {}".format(self.url(), msg)
if self.isLocal():
server = "local server {}".format(self.url())
else:
msg = "Can't connect to server {}".format(self.url())
server = "remote server {}".format(self.url())
if len(msg) > 0:
msg = "Cannot connect to {}: {}".format(server, msg)
else:
if self.isLocal():
msg = "Cannot connect to {}. Please check if GNS3 is allowed in your antivirus and firewall.".format(server)
else:
msg = "Cannot connect to {}".format(server)
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
@@ -497,6 +492,7 @@ class HTTPClient(QtCore.QObject):
return
self._connected = True
self.connection_connected_signal.emit()
kwargs["context"] = original_context
self.executeHTTPQuery(method, path, callback, body, **kwargs)
self._version = params["version"]
@@ -568,7 +564,10 @@ class HTTPClient(QtCore.QObject):
host = self._http_host
log.debug("{method} {protocol}://{host}:{port}/v1{path} {body}".format(method=method, protocol=self._scheme, host=host, port=self._http_port, path=path, body=body))
url = QtCore.QUrl("{protocol}://{host}:{port}/v1{path}".format(protocol=self._scheme, host=host, port=self._http_port, path=path))
if self._user:
url = QtCore.QUrl("{protocol}://{user}@{host}:{port}/v1{path}".format(protocol=self._scheme, user=self._user, host=host, port=self._http_port, path=path))
else:
url = QtCore.QUrl("{protocol}://{host}:{port}/v1{path}".format(protocol=self._scheme, host=host, port=self._http_port, path=path))
request = self._request(url)
request = self.addAuth(request)
@@ -580,53 +579,58 @@ class HTTPClient(QtCore.QObject):
response = self._network_manager.sendCustomRequest(request, method.encode(), body)
import copy
context = copy.copy(context)
query_id = str(uuid.uuid4())
context["query_id"] = query_id
if showProgress:
self.notify_progress_start_query(context["query_id"], progressText, response)
response.uploadProgress.connect(partial(self.notify_progress_upload, query_id))
response.downloadProgress.connect(partial(self.notify_progress_download, query_id))
context["query_id"] = str(uuid.uuid4())
response.finished.connect(qpartial(self._processResponse, response, callback, context, body, ignoreErrors))
response.finished.connect(partial(self._processResponse, response, callback, context, body, ignoreErrors))
if downloadProgressCallback is not None:
response.downloadProgress.connect(partial(self._processDownloadProgress, response, downloadProgressCallback, context))
response.downloadProgress.connect(qpartial(self._processDownloadProgress, response, downloadProgressCallback, context))
if showProgress:
response.uploadProgress.connect(qpartial(self.notify_progress_upload, context["query_id"]))
response.downloadProgress.connect(qpartial(self.notify_progress_download, context["query_id"]))
# Should be the last operation otherwise we have race condition in Qt
# where query start before finishing connect to everything
self.notify_progress_start_query(context["query_id"], progressText, response)
return response
def _processDownloadProgress(self, response, callback, context):
def _processDownloadProgress(self, response, callback, context, bytesReceived, bytesTotal):
"""
Process a packet receive on the notification feed.
The feed can contains partial JSON. If we found a
The feed can contains qpartial JSON. If we found a
part of a JSON we keep it for the next packet
"""
if response.error() == QtNetwork.QNetworkReply.NoError:
error_code = response.error()
if response.error() != QtNetwork.QNetworkReply.NoError:
return
if error_code >= 300:
return
# HTTP error
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
if status >= 300:
return
content = bytes(response.readAll())
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
if content_type == "application/json":
content = content.decode("utf-8")
if context["query_id"] in self._buffer:
content = self._buffer[context["query_id"]] + content
try:
while True:
content = content.lstrip(" \r\n\t")
answer, index = json.JSONDecoder().raw_decode(content)
callback(answer, server=self, context=context)
content = content[index:]
except ValueError: # Partial JSON
self._buffer[context["query_id"]] = content
else:
callback(content, server=self, context=context)
content = bytes(response.readAll())
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
if content_type == "application/json":
content = content.decode("utf-8")
if context["query_id"] in self._buffer:
content = self._buffer[context["query_id"]] + content
try:
while True:
content = content.lstrip(" \r\n\t")
answer, index = json.JSONDecoder().raw_decode(content)
callback(answer, server=self, context=context)
content = content[index:]
except ValueError: # Partial JSON
self._buffer[context["query_id"]] = content
else:
callback(content, server=self, context=context)
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
request_canceled = partial(self._requestCanceled, response, context)
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
request_canceled = qpartial(self._requestCanceled, response, context)
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
def _requestCanceled(self, response, context):
@@ -675,7 +679,11 @@ class HTTPClient(QtCore.QObject):
callback({"message": error_message}, error=True, server=self, context=context)
else:
log.debug(body)
callback(json.loads(body), error=True, server=self, context=context)
try:
callback(json.loads(body), error=True, server=self, context=context)
except ValueError:
# It happens when an antivirus catch the communication and send is error page without changing the Content Type
callback({"message": error_message}, error=True, server=self, context=context)
else:
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
log.debug("Decoding response from {} response {}".format(response.url().toString(), status))
@@ -765,5 +773,14 @@ class HTTPClient(QtCore.QObject):
server["accept_insecure_certificate"] = self._accept_insecure_certificate
return server
def isCloud(self):
return False
def systemUsage(self):
"""
Get information about current system usage
:returns: None or dict
"""
return self._usage
def setSystemUsage(self, usage):
self._usage = usage
self.system_usage_updated_signal.emit()

View File

@@ -17,6 +17,7 @@
import os
import pathlib
import glob
from gns3.servers import Servers
from gns3.qt import QtWidgets
@@ -55,6 +56,11 @@ class ImageManager:
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
destination_path = os.path.join(destination_directory, os.path.basename(path))
try:
os.makedirs(destination_directory, exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(parent, 'Image', 'Could not create destination directory {}: {}'.format(destination_directory, str(e)))
return path
worker = FileCopyWorker(path, destination_path)
progress_dialog = ProgressDialog(worker, 'Image', 'Copying {}'.format(os.path.basename(path)), 'Cancel', busy=True, parent=parent)
progress_dialog.show()
@@ -62,6 +68,7 @@ class ImageManager:
errors = progress_dialog.errors()
if errors:
QtWidgets.QMessageBox.critical(parent, 'Image', '{}'.format(''.join(errors)))
return path
else:
path = destination_path
return path
@@ -85,8 +92,8 @@ class ImageManager:
else:
raise Exception('Invalid image vm_type')
filename = os.path.basename(path)
server.post('{}/{}'.format(upload_endpoint, filename), None, body=pathlib.Path(path))
filename = self._getRelativeImagePath(path, vm_type).replace("\\", "/")
server.post('{}/{}'.format(upload_endpoint, filename), None, body=pathlib.Path(path), progressText="Uploading {}".format(filename))
return filename
def addMissingImage(self, filename, server, vm_type):
@@ -106,6 +113,13 @@ class ImageManager:
path = os.path.join(self.getDirectoryForType(vm_type), filename)
if os.path.exists(path):
if self._askForUploadMissingImage(filename, server):
if filename.endswith(".vmdk"):
# A vmdk file could be split in multiple vmdk file
search = glob.escape(path).replace(".vmdk", "-*.vmdk")
for file in glob.glob(search):
self._uploadImageToRemoteServer(file, server, vm_type)
self._uploadImageToRemoteServer(path, server, vm_type)
del self._asked_for_this_image[server.id()][filename]
@@ -121,6 +135,25 @@ class ImageManager:
return True
return False
def _getRelativeImagePath(self, path, vm_type):
"""
Get a path relative to images directory path
or just filename if the path is not located inside
image directory
:param path: file path
:param vm_type: Type of vm
:return: file path
"""
if not path:
return ""
img_directory = self.getDirectoryForType(vm_type)
path = os.path.abspath(path)
if os.path.commonprefix([img_directory, path]) == img_directory:
return os.path.relpath(path, img_directory)
return os.path.basename(path)
def getDirectory(self):
"""
Returns the images directory path.
@@ -134,6 +167,8 @@ class ImageManager:
"""
Return the path of local directory of the images
of a specific vm_type
:param vm_type: Type of vm
"""
if vm_type == 'DYNAMIPS':
return os.path.join(self.getDirectory(), 'IOS')

View File

@@ -18,14 +18,13 @@
import sys
import shutil
import signal
import json
import os
from datetime import datetime
try:
from gns3.qt import QtCore, QtGui, QtWidgets, DEFAULT_BINDING
from gns3.qt import QtGui, QtWidgets
except ImportError:
raise SystemExit("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
@@ -37,7 +36,9 @@ from gns3.version import __version__
from gns3.local_config import LocalConfig
from gns3.ui.iouvm_converter_wizard_ui import Ui_IOUVMConverterWizard
class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
@@ -47,7 +48,7 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
# set the window icon
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico"))# this info is necessary for QSettings
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico")) # this info is necessary for QSettings
config = self._loadConfig()
self.uiPushButtonBrowse.clicked.connect(self._browseTopologiesSlot)
@@ -126,7 +127,7 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
topo = json.load(f)
if "topology" in topo and "servers" in topo["topology"]:
for server in topo["topology"]["servers"]:
if server["local"] == False:
if server["local"] is False:
server["vm"] = True
with open(path, 'w+') as f:
topo = json.dump(topo, f)
@@ -148,7 +149,8 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
else:
filename = "gns3_gui.conf"
directory = LocalConfig.configDirectory()
return os.path.join(directory, filename)
return os.path.join(directory, filename)
def main():
app = QtWidgets.QApplication(sys.argv)

View File

@@ -172,6 +172,20 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
cls._draw_port_labels = state
def resetPortLabels(self):
"""
Resets the port label positions.
"""
source_port_label = self._source_port.label()
destination_port_label = self._destination_port.label()
if source_port_label is not None:
source_port_label.delete()
self._source_port.setLabel(None)
if destination_port_label is not None:
destination_port_label.delete()
self._destination_port.setLabel(None)
def populateLinkContextualMenu(self, menu):
"""
Adds device actions to the link contextual menu.
@@ -318,13 +332,13 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
selection, ok = QtWidgets.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", ports, 0, False)
if ok:
if selection.endswith(self._source_port.name()):
self._source_port.startPacketCaptureReader()
self._source_port.startPacketCaptureReader(self._source_item.node().name())
else:
self._destination_port.startPacketCaptureReader()
self._destination_port.startPacketCaptureReader(self._destination_item.node().name())
elif self._source_port.capturing():
self._source_port.startPacketCaptureReader()
self._source_port.startPacketCaptureReader(self._source_item.node().name())
elif self._destination_port.capturing():
self._destination_port.startPacketCaptureReader()
self._destination_port.startPacketCaptureReader(self._destination_item.node().name())
except OSError as e:
QtWidgets.QMessageBox.critical(self._main_window, "Packet capture", "Cannot start Wireshark: {}".format(e))

View File

@@ -51,8 +51,8 @@ class NodeItem():
effect.setColor(QtGui.QColor("black"))
effect.setStrength(0.8)
#effect = QtWidgets.QGraphicsDropShadowEffect()
#effect.setColor(QtGui.QColor("darkGray"))
#effect.setBlurRadius(0)
# effect.setColor(QtGui.QColor("darkGray"))
# effect.setBlurRadius(0)
#effect.setOffset(3, 3)
self.setGraphicsEffect(effect)
self.graphicsEffect().setEnabled(False)
@@ -186,6 +186,8 @@ class NodeItem():
when a the node has been updated.
"""
if self is None:
return
if self._node_label:
if self._node_label.toPlainText() != self._node.name():
self._node_label.setPlainText(self._node.name())
@@ -212,6 +214,8 @@ class NodeItem():
when the node has been deleted.
"""
if self is None:
return
self._node.removeAllocatedName()
if self in self.scene().items():
self.scene().removeItem(self)
@@ -226,7 +230,8 @@ class NodeItem():
:param message: error message
"""
self._last_error = "{message}".format(message=message)
if self:
self._last_error = "{message}".format(message=message)
def errorSlot(self, node_id, message):
"""
@@ -237,7 +242,8 @@ class NodeItem():
:param message: error message
"""
self._last_error = "{message}".format(message=message)
if self:
self._last_error = "{message}".format(message=message)
def setCustomToolTip(self):
"""

View File

@@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Graphical representation of a pixmap node on the QGraphicsScene.
"""
from ..qt import QtGui, QtWidgets
from .node_item import NodeItem
import logging
log = logging.getLogger(__name__)
class PixmapNodeItem(NodeItem, QtWidgets.QGraphicsPixmapItem):
"""
Pixmap node for the scene.
:param node: Node instance
:param pixmap_symbol: symbol for the node representation on the scene
"""
def __init__(self, node, pixmap_symbol_path):
QtWidgets.QGraphicsPixmapItem.__init__(self)
NodeItem.__init__(self, node)
self._pixmap_symbol_path = pixmap_symbol_path
pixmap = QtGui.QPixmap(pixmap_symbol_path)
self.setPixmap(pixmap)
def setPixmapSymbolPath(self, path):
"""
Sets the pixmap path
:param path: path to the Pixmap file.
"""
self._pixmap_symbol_path = path
def pixmapSymbolPath(self):
"""
Returns the pixmap path
:returns: path to the Pixmap file.
"""
return self._pixmap_symbol_path

View File

@@ -20,6 +20,7 @@ Graphical representation of a SVG node on the QGraphicsScene.
"""
from ..qt import QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .node_item import NodeItem
import logging
@@ -42,9 +43,9 @@ class SvgNodeItem(NodeItem, QtSvg.QGraphicsSvgItem):
# create renderer using symbols path/resource
if symbol:
renderer = QtSvg.QSvgRenderer(symbol)
renderer = QImageSvgRenderer(symbol)
if symbol != node.defaultSymbol():
renderer.setObjectName(symbol)
else:
renderer = QtSvg.QSvgRenderer(node.defaultSymbol())
renderer = QImageSvgRenderer(node.defaultSymbol())
self.setSharedRenderer(renderer)

View File

@@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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 rsa
import sys
import os
import base64
PUB_KEY = b"""-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAiE4Zgzge3Cg6EUfct7vnzcmXkIvsy6g/QkfEeKSz3Cd+L7kxVZGE
weOXySrSSrRBoF1i2JhL2KkqZTY31972deviL+fv+TgE5RueyERFey3fw7+oN/RW
i8UIUvRqHjwocCuJq5yUiOv+AdGKG3TNeYXvx4Xvnrr4AJnJRThDfqd0nr8QAXRn
/Ifx4MKivL8RDyqHoVlHvHeyJmtaZIzsYthsK3FU2XED6d6xwbga3t2cb4+DfJa3
rBtWnoIXHiRdZZUtl34dGiiyxKL2yco+Dpd5pUvw6F7+n77SnSwN+F0ZzrrgUMHA
vBHBnF4WB6mjRFxbO+B/H1OxnXcjwxgYWLCbkrhQogqyfdkmacppWLOH9OyzGUkY
r7qITLCWSAHuIqXmQF4VAqCPYwEK7o6ndebFk1jaAAPGIw52AA1YOSXJ6jpKiO7f
5gXT3xRfv4kW1Fp6le0hp0Laz6VGbOv44vauxk516v5MI+CUL3u5TOmGWM53u1OG
qq6SfL+5Cu0/4L+SUaJ7nzN+PgWx6BEd0LRzEVQcmRPA4zHbhJ7ebBbYOul9RFyW
8D7yy7mUQZwVQDcuaB6l2pu0BfZppb+Uf81h0nRQIrHt7BRBiyaGojQIHsw8CrqP
3fsnHUvqtNLipC26FSTW4wlPIEktsWU8TABgjbuS45+zFTI141/J77ECAwEAAQ==
-----END RSA PUBLIC KEY-----"""
import logging
log = logging.getLogger(__name__)
def checkLicence():
"""
Return true if the user as correctly installed the licence file
"""
appname = "GNS3"
filename = "licence.txt"
if sys.platform.startswith("win"):
# On windows, the user specific configuration file location is %APPDATA%/GNS3/gns3_gui.conf
appdata = os.path.expandvars("%APPDATA%")
licence_file = os.path.join(appdata, appname, filename)
else:
# On UNIX-like platforms, the user specific configuration file location is /etc/xdg/GNS3/gns3_gui.conf
home = os.path.expanduser("~")
licence_file = os.path.join(home, ".config", appname, filename)
return check_licence_file(licence_file)
def check_licence_file(licence_file):
if os.path.exists(licence_file):
with open(licence_file) as f:
email = f.readline().strip()
key = f.readline().strip()
pubkey = rsa.PublicKey.load_pkcs1(PUB_KEY)
try:
rsa.verify(email.encode("utf-8"), base64.b64decode(key), pubkey)
log.info("Found a valid licence file. Thanks for your support")
return True
except rsa.pkcs1.VerificationError:
log.error("Invalid licence file.")
return False
return False

View File

@@ -22,6 +22,7 @@ Manages and stores everything needed for a connection between 2 devices.
from .qt import QtCore
from .nios.nio_udp import NIOUDP
from .nios.nio_vmnet import NIOVMNET
import logging
log = logging.getLogger(__name__)
@@ -72,30 +73,33 @@ class Link(QtCore.QObject):
self._stub = True
else:
self._stub = False
# we must request UDP information if the NIO is a NIO UDP and before
# it can be created.
if not self._stub:
# connect signals used when a NIO has been created by a node
# and this NIO need to be attached to a port connected to this link
source_node.nio_signal.connect(self.newNIOSlot)
destination_node.nio_signal.connect(self.newNIOSlot)
# currently, we support only NIO_UDP for normal connections (non-stub).
if not source_port.defaultNio() == NIOUDP:
# currently, we support only NIO_UDP and NIO_VMNET for normal connections (non-stub).
if source_port.defaultNio() == NIOUDP:
assert destination_port.defaultNio() == NIOUDP
self._source_udp = None
self._destination_udp = None
# connect signals used to receive a UDP port and host allocated by a node
source_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
destination_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
# request the UDP info for each node
source_node.allocateUDPPort(self._source_port.id())
destination_node.allocateUDPPort(self._destination_port.id())
elif source_port.defaultNio() == NIOVMNET:
assert destination_port.defaultNio() == NIOVMNET
source_node.allocate_vmnet_nio_signal.connect(self.VMnetInterfaceAllocatedSlot)
source_node.allocateVMnetInterface(self._source_port.id())
else:
raise NotImplementedError()
self._source_udp = None
self._destination_udp = None
# connect signals used to receive a UDP port and host allocated by a node
source_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
destination_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
# request the UDP info for each node
source_node.allocateUDPPort(self._source_port.id())
destination_node.allocateUDPPort(self._destination_port.id())
else:
# handle stub connections (to a cloud for instance).
if not source_port.isStub() and destination_port.isStub():
@@ -203,7 +207,6 @@ class Link(QtCore.QObject):
:param port_id: port identifier
:param lport: local UDP port
"""
# check that the node is connected to this link as a source
if node_id == self._source_node.id() and port_id == self._source_port.id():
laddr = self._source_node.server().host()
@@ -227,7 +230,6 @@ class Link(QtCore.QObject):
laddr))
if self._source_udp and self._destination_udp:
# we got UDP info from both source and destination nodes
# meaning we can proceed with the creation of UDP NIOs
lport, laddr = self._source_udp
@@ -247,6 +249,30 @@ class Link(QtCore.QObject):
self._destination_node.nio_cancel_signal.connect(self.cancelNIOSlot)
self._destination_node.addNIO(self._destination_port, self._destination_nio)
def VMnetInterfaceAllocatedSlot(self, node_id, port_id, vmnet):
"""
Slot to receive events from Node instances
when a VMnet interface has been allocated in order to create a NIO VMNET.
:param node_id: node identifier
:param port_id: port identifier
:param vmnet: vmnet interface name
"""
# check that the node is connected to this link as a source
# only the source is used to request the server for a vmnet interface
# and then allocate a NIO VMNET to both the source and destination
if node_id == self._source_node.id() and port_id == self._source_port.id():
self._source_node.allocate_vmnet_nio_signal.disconnect(self.VMnetInterfaceAllocatedSlot)
self._source_nio = NIOVMNET(vmnet)
self._destination_nio = NIOVMNET(vmnet)
# add the VMnet NIOs to the nodes
self._source_node.nio_cancel_signal.connect(self.cancelNIOSlot)
self._source_node.addNIO(self._source_port, self._source_nio)
self._destination_node.nio_cancel_signal.connect(self.cancelNIOSlot)
self._destination_node.addNIO(self._destination_port, self._destination_nio)
def newNIOSlot(self, node_id, port_id):
"""
Slot to receive events from Node instances
@@ -382,5 +408,4 @@ class Link(QtCore.QObject):
"source_node_id": self._source_node.id(),
"source_port_id": self._source_port.id(),
"destination_node_id": self._destination_node.id(),
"destination_port_id": self._destination_port.id(),
}
"destination_port_id": self._destination_port.id()}

View File

@@ -21,35 +21,16 @@ import json
import shutil
import copy
import psutil
from .qt import QtCore
from .version import __version__
from .utils import parse_version
import logging
log = logging.getLogger(__name__)
class PeriodicCheckConfig(QtCore.QThread):
"""
Timer for checking if the configuration file change
on disk.
"""
def __init__(self, parent):
super().__init__(parent)
self._parent = parent
def run(self):
self._timer = QtCore.QTimer()
self._timer.timeout.connect(self._parent._checkConfigChanged)
self._timer.setInterval(1000) #  milliseconds
self._timer.start()
self.exec_()
class LocalConfig(QtCore.QObject):
"""
@@ -110,9 +91,6 @@ class LocalConfig(QtCore.QObject):
self._migrateOldConfig()
self._writeConfig()
self._check_thread = PeriodicCheckConfig(self)
self._check_thread.start()
@staticmethod
def configDirectory():
"""
@@ -168,6 +146,15 @@ class LocalConfig(QtCore.QObject):
main_window["hide_getting_started_dialog"] = self._settings["GUI"].get("hide_getting_started_dialog", False)
self._settings["MainWindow"] = main_window
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.1dev2"):
if sys.platform.startswith("darwin"):
from .settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, DEFAULT_TELNET_CONSOLE_COMMAND
if "MainWindow" in self._settings:
if self._settings["MainWindow"]["telnet_console_command"] not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
self._settings["MainWindow"]["telnet_console_command"] = DEFAULT_TELNET_CONSOLE_COMMAND
def _readConfig(self, config_path):
"""
Read the configuration file.
@@ -205,7 +192,8 @@ class LocalConfig(QtCore.QObject):
except (ValueError, OSError) as e:
log.error("Could not write the config file {}: {}".format(self._config_file, e))
def _checkConfigChanged(self):
def checkConfigChanged(self):
try:
if self._last_config_changed and self._last_config_changed < os.stat(self._config_file).st_mtime:
log.info("Client config has changed, reloading it...")
@@ -263,25 +251,31 @@ class LocalConfig(QtCore.QObject):
"""
settings = self.settings().get(section, dict())
changed = False
def _copySettings(local, default):
"""
Copy only existing settings, ignore the other.
Add default values if require.
"""
nonlocal changed
# use default values for missing settings
for name, value in default.items():
if name not in local:
local[name] = value
changed = True
elif isinstance(value, dict):
local[name] = _copySettings(local[name], default[name])
return local
settings = _copySettings(settings, default_settings)
self._settings[section] = settings
if changed:
log.info("Section %s has missing default values. Adding keys %s Saving configuration", section, ','.join(set(default_settings.keys()) - set(settings.keys())))
self._writeConfig()
return copy.deepcopy(settings)
def saveSectionSettings(self, section, settings):
@@ -296,12 +290,20 @@ class LocalConfig(QtCore.QObject):
self._settings[section] = {}
if self._settings[section] != settings:
self._settings[section].update(settings)
self._settings[section].update(copy.deepcopy(settings))
log.info("Section %s has changed. Saving configuration", section)
self._writeConfig()
else:
log.debug("Section %s has not changed. Skip saving configuration", section)
def experimental(self):
"""
:returns: Boolean. True if experimental features allowed
"""
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["experimental_features"]
@staticmethod
def instance(config_file=None):
"""
@@ -313,3 +315,41 @@ class LocalConfig(QtCore.QObject):
if not hasattr(LocalConfig, "_instance") or LocalConfig._instance is None:
LocalConfig._instance = LocalConfig(config_file=config_file)
return LocalConfig._instance
@staticmethod
def isMainGui():
"""
:returns: Return true if we are the main gui (first gui to start)
"""
my_pid = os.getpid()
pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_gui.pid")
if os.path.exists(pid_path):
try:
with open(pid_path) as f:
pid = int(f.read())
if pid != my_pid:
try:
process = psutil.Process(pid=pid)
ps_name = process.name()
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
pass
else:
if "gns3" in ps_name or "python" in ps_name:
# Process run under the same user id
if sys.platform.startswith("win") or process.uids()[0] == os.getuid():
return False
else:
return True
except (OSError, ValueError) as e:
log.critical("Can't read pid file %s: %s", pid_path, str(e))
return False
try:
with open(pid_path, 'w+') as f:
f.write(str(my_pid))
except OSError as e:
log.critical("Can't write pid file %s: %s", pid_path, str(e))
return False
return True

View File

@@ -18,7 +18,6 @@
import os
import sys
import configparser
from gns3.qt import QtCore
import logging
@@ -62,7 +61,7 @@ class LocalServerConfig:
try:
self._config.read(self._config_file, encoding="utf-8")
except (OSError, configparser.Error) as e:
except (OSError, configparser.Error, UnicodeEncodeError, UnicodeDecodeError) as e:
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
def writeConfig(self):

View File

@@ -25,7 +25,7 @@ try:
if gns3.update_manager.UpdateManager().installDownloadedUpdates():
print("Update installed restart the application")
python = sys.executable
os.execl(python, python, * sys.argv)
os.execl(python, *sys.argv)
except Exception as e:
print("Fail update installation: {}".format(str(e)))
@@ -45,6 +45,7 @@ import time
import locale
import argparse
import signal
import re
try:
from gns3.qt import QtCore, QtGui, QtWidgets, DEFAULT_BINDING
@@ -55,11 +56,12 @@ from gns3.main_window import MainWindow
from gns3.logger import init_logger
from gns3.crash_report import CrashReport
from gns3.local_config import LocalConfig
from gns3.application import Application
import logging
log = logging.getLogger(__name__)
from gns3.version import __version__
@@ -144,6 +146,7 @@ def main():
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")
if options.project:
options.project = os.path.abspath(options.project)
os.chdir(frozen_dir)
def exceptionHook(exception, value, tb):
@@ -153,7 +156,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 https://community.gns3.com/community/software/bug\n")
print("\nPLEASE REPORT ON https://www.gns3.com\n")
print("".join(lines))
try:
curdate = time.strftime("%d %b %Y %H:%M:%S")
@@ -186,10 +189,7 @@ def main():
raise SystemExit("Python 3.4 or higher is required")
def version(version_string):
return [int(i) for i in version_string.split('.')]
if version(QtCore.QT_VERSION_STR) < version("4.6"):
raise SystemExit("Requirement is Qt version 4.6 or higher, got version {}".format(QtCore.QT_VERSION_STR))
return [int(i) for i in re.split(r'[^0-9]', version_string)]
# 4.8.3 because of QSettings (http://pyqt.sourceforge.net/Docs/PyQt4/pyqt_qsettings.html)
if DEFAULT_BINDING == "PyQt4" and version(QtCore.BINDING_VERSION_STR) < version("4.8.3"):
@@ -198,6 +198,10 @@ def main():
if DEFAULT_BINDING == "PyQt5" and version(QtCore.BINDING_VERSION_STR) < version("5.0.0"):
raise SystemExit("Requirement is PyQt5 version 5.0.0 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
import psutil
if version(psutil.__version__) < version("2.2.1"):
raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))
# check for the correct locale
# (UNIX/Linux only)
locale_check()
@@ -228,13 +232,8 @@ def main():
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
app = QtWidgets.QApplication(sys.argv)
# this info is necessary for QSettings
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
global app
app = Application(sys.argv)
# save client logging info to a file
logfile = os.path.join(LocalConfig.configDirectory(), "gns3_gui.log")
@@ -248,7 +247,14 @@ def main():
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(LocalConfig.configDirectory(), exception_file_path)
mainwindow = MainWindow(options.project)
global mainwindow
mainwindow = MainWindow()
# On OSX we can receive the file to open from a system event
# loadPath is smart and will load only if a path is present
mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(app.open_file_at_startup))
mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(options.project))
app.file_open_signal.connect(lambda path: mainwindow.loadPath(path))
# Manage Ctrl + C or kill command
def sigint_handler(*args):
@@ -259,9 +265,9 @@ def main():
signal.signal(signal.SIGTERM, sigint_handler)
mainwindow.show()
exit_code = app.exec_()
delattr(MainWindow, "_instance")
app.deleteLater()
# We force a full garbage collect before exit
# for unknow reason otherwise Qt Segfault on OSX in some

View File

@@ -42,6 +42,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 .dialogs.export_debug_dialog import ExportDebugDialog
from .dialogs.doctor_dialog import DoctorDialog
from .dialogs.setup_wizard import SetupWizard
from .settings import GENERAL_SETTINGS
from .utils.progress_dialog import ProgressDialog
@@ -60,9 +62,10 @@ from .utils.download_project import DownloadProjectWorker
from .project import Project
from .http_client import HTTPClient
from .progress import Progress
from .licence import checkLicence
from .image_manager import ImageManager
from .update_manager import UpdateManager
from .utils.analytics import AnalyticsClient
from .dialogs.appliance_wizard import ApplianceWizard
from .registry.appliance import ApplianceError
log = logging.getLogger(__name__)
@@ -76,12 +79,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
# signal to tell the view if the user is adding a link or not
adding_link_signal = QtCore.Signal(bool)
adding_link_signal = QtCore.pyqtSignal(bool)
# signal to tell a new project was created
project_new_signal = QtCore.pyqtSignal(str)
def __init__(self, project=None, parent=None):
# signal to tell the windows is ready to load his first project
ready_signal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
@@ -92,25 +98,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project = None
self._createTemporaryProject()
self._project_from_cmdline = project
self._first_file_load = True
self._loadSettings()
self._connections()
self._ignore_unsaved_state = False
self._max_recent_files = 5
self._soft_exit = True
self._project_dialog = None
self._recent_file_actions = []
self._start_time = time.time()
local_config = LocalConfig.instance()
local_config.config_changed_signal.connect(self._localConfigChangedSlot)
self._uiNewsDockWidget = None
if not checkLicence():
try:
from .news_dock_widget import NewsDockWidget
self._uiNewsDockWidget = NewsDockWidget(self)
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.BottomDockWidgetArea), self._uiNewsDockWidget)
except ImportError:
pass
self._local_config_timer = QtCore.QTimer(self)
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
self._local_config_timer.start(1000) # milliseconds
self._analytics_client = AnalyticsClient()
# restore the geometry and state of the main window.
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
@@ -118,6 +120,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# populate the view -> docks menu
self.uiDocksMenu.addAction(self.uiTopologySummaryDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiServerSummaryDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiConsoleDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiNodesDockWidget.toggleViewAction())
@@ -144,6 +147,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# restore the style
self._setStyle(self._settings.get("style"))
self.setWindowTitle("[*] GNS3")
# load initial stuff once the event loop isn't busy
self.run_later(0, self.startupLoading)
@@ -196,6 +202,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# file menu connections
self.uiNewProjectAction.triggered.connect(self._newProjectActionSlot)
self.uiOpenProjectAction.triggered.connect(self.openProjectActionSlot)
self.uiOpenApplianceAction.triggered.connect(self.openApplianceActionSlot)
self.uiSaveProjectAction.triggered.connect(self._saveProjectActionSlot)
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
self.uiDownloadRemoteProject.triggered.connect(self._downloadRemoteProjectActionSlot)
@@ -216,7 +223,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiFitInViewAction.triggered.connect(self._fitInViewActionSlot)
self.uiShowLayersAction.triggered.connect(self._showLayersActionSlot)
self.uiResetPortLabelsAction.triggered.connect(self._resetPortLabelsActionSlot)
self.uiShowNamesAction.triggered.connect(self._showNamesActionSlot)
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
# control menu connections
@@ -243,10 +249,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiOnlineHelpAction.triggered.connect(self._onlineHelpActionSlot)
self.uiCheckForUpdateAction.triggered.connect(self._checkForUpdateActionSlot)
self.uiSetupWizard.triggered.connect(self._setupWizardActionSlot)
self.uiGettingStartedAction.triggered.connect(self._gettingStartedActionSlot)
self.uiLabInstructionsAction.triggered.connect(self._labInstructionsActionSlot)
self.uiAboutQtAction.triggered.connect(self._aboutQtActionSlot)
self.uiAboutAction.triggered.connect(self._aboutActionSlot)
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
self.uiDoctorAction.triggered.connect(self._doctorSlot)
self.uiIOUVMConverterAction.triggered.connect(self._IOUVMConverterActionSlot)
# browsers tool bar connections
@@ -263,6 +270,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# project
self.project_new_signal.connect(self.project_created)
self.ready_signal.connect(self._readySlot)
def project(self):
"""
Return current project
@@ -280,33 +289,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project = project
self._setCurrentFile(project.topologyFile())
def telnetConsoleCommand(self):
"""
Returns the Telnet console command line.
:returns: command (string)
"""
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 vncConsoleCommand(self):
"""
Returns the VNC command line.
:returns: command (string)
"""
return self._settings["vnc_console_command"]
def setUnsavedState(self):
"""
Sets the project in a unsaved state.
@@ -337,9 +319,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiGraphicsView.reset()
# create the destination directory for project files
try:
os.makedirs(new_project_settings["project_files_dir"])
except FileExistsError:
pass
os.makedirs(new_project_settings["project_files_dir"], exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "New project", "Could not create project files directory {}: {}".format(new_project_settings["project_files_dir"], e))
self._createTemporaryProject()
@@ -361,19 +341,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
if self.checkForUnsavedChanges():
project_dialog = NewProjectDialog(self)
project_dialog.show()
create_new_project = project_dialog.exec_()
self._project_dialog = NewProjectDialog(self)
self._project_dialog.show()
create_new_project = self._project_dialog.exec_()
# Close the device dock so it repopulates. Done in case switching
# between cloud and local.
self.uiNodesDockWidget.setVisible(False)
self.uiNodesDockWidget.setWindowTitle("")
if create_new_project:
new_project_settings = project_dialog.getNewProjectSettings()
new_project_settings = self._project_dialog.getNewProjectSettings()
self._createNewProject(new_project_settings)
else:
self._createTemporaryProject()
self._project_dialog = None
def _IOUVMConverterActionSlot(self):
command = shutil.which("gns3-iouvm-converter")
@@ -385,6 +366,22 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
except (OSError, subprocess.SubprocessError) as e:
QtWidgets.QMessageBox.critical(self, "GNS3 IOU VM Converter", "Error when running gns3-iouvm-converter {}".format(e))
def openApplianceActionSlot(self):
"""
Slot called to open an appliance.
"""
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
if len(directory) == 0:
directory = self.projectsDirPath()
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
"Open appliance",
directory,
"All files (*.*);;GNS3 appliance (*.gns3a)",
"GNS3 appliance (*.gns3a)")
if path:
self.loadPath(path)
def openProjectActionSlot(self):
"""
Slot called to open a project.
@@ -396,7 +393,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"All files (*.*);;GNS3 project files (*.gns3);;NET files (*.net)",
"GNS3 project files (*.gns3)")
if path:
self._loadPath(path)
self.loadPath(path)
def openRecentFileSlot(self):
"""
@@ -409,7 +406,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not os.path.isfile(path):
QtWidgets.QMessageBox.critical(self, "Recent file", "{}: no such file".format(path))
return
self._loadPath(path)
self.loadPath(path)
def loadSnapshot(self, path):
"""Loads a snapshot"""
@@ -418,18 +415,38 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
self._project.close()
def _loadPath(self, path):
def loadPath(self, path):
"""Open a file and close the previous project"""
if path and self.checkForUnsavedChanges():
self._open_project_path = path
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
self._project.close()
if path:
if self._first_file_load is True:
self._first_file_load = False
time.sleep(0.5) # give some time to the server to initialize
if self._project_dialog:
self._project_dialog.reject()
self._project_dialog = None
if path.endswith(".gns3a"):
try:
self._appliance_wizard = ApplianceWizard(self, path)
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self, "Appliance", "Error when importing appliance {}: {}".format(path, str(e)))
return
self._appliance_wizard.show()
self._appliance_wizard.exec_()
elif self.checkForUnsavedChanges():
self._open_project_path = path
if self._project.closed():
self._projectClosedContinueLoadPath()
else:
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
self._project.close()
def _projectClosedContinueLoadPath(self):
path = self._open_project_path
if self.loadProject(path):
if self._loadProject(path):
self.project_new_signal.emit(path)
def _saveProjectActionSlot(self):
@@ -519,7 +536,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
# supported image file formats
file_formats = "PNG File (*.png);;JPG File (*.jpeg *.jpg);;BMP File (*.bmp);;XPM File (*.xpm *.xbm);;PPM File (*.ppm);;TIFF File (*.tiff)"
file_formats = "PNG File (*.png);;JPG File (*.jpeg *.jpg);;BMP File (*.bmp);;XPM File (*.xpm *.xbm);;PPM File (*.ppm);;TIFF File (*.tiff);; GIF File (*.gif)"
path, selected_filter = QtWidgets.QFileDialog.getSaveFileName(self, "Screenshot", self._screenshots_dir, file_formats)
if not path:
return
@@ -635,16 +652,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called to reset the port labels on the scene.
"""
# TODO: reset port labels
pass
def _showNamesActionSlot(self):
"""
Slot called to show the node names on the scene.
"""
# TODO: show/hide node names
pass
for item in self.uiGraphicsView.scene().items():
if isinstance(item, LinkItem):
item.resetPortLabels()
item.adjust()
def _showPortNamesActionSlot(self):
"""
@@ -727,9 +738,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
try:
working_dir = os.path.join(self._project.filesDir(), "project-files", "vpcs", "multi-host")
os.makedirs(working_dir)
except FileExistsError:
pass
os.makedirs(working_dir, exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "VPCS", "Could not create the VPCS working directory: {}".format(e))
return
@@ -758,12 +767,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot called when inserting an image on the scene.
"""
if self._project.filesDir() is None:
files_dir = self._project.filesDir()
if files_dir is None:
QtWidgets.QMessageBox.critical(self, "Image", "Please create a node first")
return
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._pictures_dir, file_formats)
if not path:
@@ -775,7 +785,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
QtWidgets.QMessageBox.critical(self, "Image", "Image file format not supported")
return
destination_dir = os.path.join(self._project.filesDir(), "project-files", "images")
destination_dir = os.path.join(files_dir, "project-files", "images")
try:
os.makedirs(destination_dir, exist_ok=True)
except OSError as e:
@@ -819,7 +829,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to launch a browser pointing to the documentation page.
"""
QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://www.gns3.net/documentation/"))
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://gns3.com/support/docs"))
def _checkForUpdateActionSlot(self, silent=False):
"""
@@ -841,25 +851,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
setup_wizard.show()
setup_wizard.exec_()
def _gettingStartedActionSlot(self, auto=False):
"""
Slot to open the news dialog.
"""
try:
# QtWebKit which is used by GettingStartedDialog is not installed
# by default on FreeBSD, Solaris and possibly other systems.
from .dialogs.getting_started_dialog import GettingStartedDialog
except ImportError as e:
print(e)
return
dialog = GettingStartedDialog(self)
if auto is True and dialog.showit() is False:
return
dialog.show()
dialog.exec_()
def _labInstructionsActionSlot(self, silent=False):
"""
Slot to open lab instructions.
@@ -895,7 +886,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog.show()
dialog.exec_()
def _showNodesDockWidget(self, title, category=Node.routers):
def _exportDebugInformationSlot(self):
"""
Slot to display a window for exporting debug information
"""
dialog = ExportDebugDialog(self, self._project)
dialog.show()
dialog.exec_()
def _doctorSlot(self):
"""
Slot to display a window for exporting debug information
"""
dialog = DoctorDialog(self)
dialog.show()
dialog.exec_()
def _showNodesDockWidget(self, title, category):
"""
Makes the NodesDockWidget appear with the appropriate title and the devices
from the specified category listed.
@@ -954,7 +963,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to browse all the devices.
"""
self._showNodesDockWidget("All devices")
self._showNodesDockWidget("All devices", None)
def _addLinkActionSlot(self):
"""
@@ -1005,31 +1014,29 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
progress.setCancelButtonText("Force quit")
log.debug("Close the main Windows")
self._analytics_client.sendScreenView("Main Window", session_start=False)
servers = Servers.instance()
if self._project.closed() and not servers.localServerIsRunning():
if self._project.closed():
log.debug("Project is closed killing server and closing main windows")
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
elif not self._soft_exit or self.checkForUnsavedChanges():
log.debug("Project is not closed asking for project closing")
self._project.project_closed_signal.connect(self._finish_application_closing)
if servers.localServerIsRunning():
self._project.close(local_server_shutdown=True)
else:
self._project.close(local_server_shutdown=False)
if self._project.closed() and not servers.localServerIsRunning():
log.debug("Disconnect all servers")
servers.disconnectAllServers()
event.accept()
self.uiConsoleTextEdit.closeIO()
else:
event.ignore()
self._project.close(local_server_shutdown=True)
event.ignore()
else:
event.ignore()
def _finish_application_closing(self):
def _finish_application_closing(self, close_windows=True):
"""
Handles the event when the main window is closed.
And project closed.
:params closing: True the windows is currently closing do not try to reclose it
"""
log.debug("_finish_application_closing")
@@ -1045,7 +1052,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
time_spent = "{:.0f}".format(time.time() - self._start_time)
log.debug("Time spend in the software is {}".format(time_spent))
self.close()
if close_windows:
self.close()
def checkForUnsavedChanges(self):
"""
@@ -1096,44 +1105,33 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# restore the style
self._setStyle(self._settings.get("style"))
# show the news dock widget
if self._uiNewsDockWidget and not self._uiNewsDockWidget.isVisible():
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.BottomDockWidgetArea), self._uiNewsDockWidget)
# FIXME: do something with getting started dialog
# self._gettingStartedActionSlot(auto=True)
servers = Servers.instance()
# start the GNS3 VM
gns3_vm = GNS3VM.instance()
if gns3_vm.autoStart() and not gns3_vm.isRunning():
servers.initVMServer()
worker = WaitForVMWorker()
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self)
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self, delay=5)
progress_dialog.show()
if progress_dialog.exec_():
gns3_vm.adjustLocalServerIP()
# start and connect to the local server
server = servers.localServer()
if servers.localServerAutoStart():
if server.isLocalServerRunning():
log.info("Connecting to a server already running on this host")
else:
if servers.initLocalServer() and servers.startLocalServer():
worker = WaitForConnectionWorker(server.host(), server.port())
progress_dialog = ProgressDialog(worker,
"Local server",
"Connecting to server {} on port {}...".format(server.host(), server.port()),
"Cancel", busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():
return
else:
if servers.shouldLocalServerAutoStart():
if not servers.localServerAutoStart():
QtWidgets.QMessageBox.critical(self, "Local server", "Could not start the local server process: {}".format(servers.localServerPath()))
return
worker = WaitForConnectionWorker(server.host(), server.port())
progress_dialog = ProgressDialog(worker,
"Local server",
"Connecting to server {} on port {}...".format(server.host(), server.port()),
"Cancel", busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():
return
# show the setup wizard
with Progress.instance().context(min_duration=0):
setup_wizard = SetupWizard(self)
@@ -1141,17 +1139,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
setup_wizard.show()
setup_wizard.exec_()
self._analytics_client.sendScreenView("Main Window")
self._createTemporaryProject()
if self._project_from_cmdline:
time.sleep(0.5) # give some time to the server to initialize
self._loadPath(self._project_from_cmdline)
elif 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.ready_signal.emit()
if self._settings["check_for_update"]:
# automatic check for update every week (604800 seconds)
@@ -1162,6 +1154,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._settings["last_check_for_update"] = current_epoch
self.setSettings(self._settings)
def _readySlot(self):
"""
Called when the application is ready to load a project
"""
if self._settings["auto_launch_project_dialog"] and self._first_file_load:
self._project_dialog = NewProjectDialog(self, showed_from_startup=True)
self._project_dialog.accepted.connect(self._newProjectDialodAcceptedSlot)
self._project_dialog.show()
def _newProjectDialodAcceptedSlot(self):
"""
Called when user accept the new project dialog
"""
if self._project_dialog:
new_project_settings = self._project_dialog.getNewProjectSettings()
self._createNewProject(new_project_settings)
self._project_dialog = None
def _running_nodes(self):
"""
:returns: Return the list of running nodes
@@ -1173,6 +1183,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
running_nodes.append(node.name())
return running_nodes
def _isTopologyOnRemoteServer(self):
"""
:returns: Boolean True if topology run on a remote server
"""
topology = Topology.instance()
running_nodes = []
for node in topology.nodes():
if not node.server().isLocal():
return True
return False
def saveProjectAs(self):
"""
Saves a project to another location/name.
@@ -1190,12 +1211,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
MessageBox(self, "Save project", "Please stop the following nodes before saving the topology to a new location", nodes)
return
if self._isTopologyOnRemoteServer() and not self._project.temporary():
MessageBox(self, "Save project", "You can not use the save as function on a remote project for the moment.")
return
if self._project.temporary():
default_project_name = "untitled"
else:
default_project_name = os.path.basename(self._project.topologyFile())
if default_project_name.endswith(".gns3"):
default_project_name = default_project_name[:-5]
default_project_name = self._project.name()
projects_dir_path = os.path.normpath(os.path.expanduser(self.projectsDirPath()))
file_dialog = QtWidgets.QFileDialog(self)
@@ -1217,9 +1240,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# create the destination directory for project files
try:
os.makedirs(project_dir)
except FileExistsError:
pass
os.makedirs(project_dir, exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Save project", "Could not create project directory {}: {}".format(project_dir, e))
return
@@ -1256,7 +1277,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
os.remove(old_topology_file_path)
except OSError as e:
MessageBox(self, "Save project", "Errors detected while saving the project", str(e), icon=QtWidgets.QMessageBox.Warning)
return self._loadPath(topology_file_path)
return self.loadPath(topology_file_path)
def saveProject(self, path, random_id=False):
"""
@@ -1284,6 +1305,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiStatusBar.showMessage("Project saved to {}".format(path), 2000)
self._project.setTopologyFile(path)
self._setCurrentFile(path)
self._analytics_client.sendScreenView("Main Window")
return True
def _convertOldProject(self, path):
@@ -1298,6 +1322,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
except ImportError as e:
log.error("GNS3 converter is missing: {}".format(str(e)))
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Please install gns3-converter in order to open old ini-style GNS3 projects")
self._createTemporaryProject()
return
try:
@@ -1313,6 +1338,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
project_name = text
project_dir = os.path.join(self.projectsDirPath(), project_name)
else:
self._createTemporaryProject()
return
for snapshot_def in get_snapshots(path):
@@ -1322,19 +1348,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
do_conversion(topology_def, project_name, project_dir, quiet=True)
except ConvertError as e:
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Could not convert {}: {}".format(path, e))
self._createTemporaryProject()
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)
self._createTemporaryProject()
return
QtWidgets.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)
self._loadProject(project_path)
def loadProject(self, path):
def _loadProject(self, path):
"""
Loads a project into GNS3.
@@ -1351,9 +1379,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if extension == ".net":
self._convertOldProject(path)
return
topology.loadFile(path, self._project)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Load", "Could not load project {}: {}".format(os.path.basename(path), e))
# log.error("exception {type}".format(type=type(e)), exc_info=1)
@@ -1403,9 +1429,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not path:
self.setWindowFilePath("Unsaved project")
self.setWindowTitle("Unsaved project[*] - GNS3")
else:
path = os.path.normpath(path)
self.setWindowFilePath(path)
self.setWindowTitle("{path}[*] - GNS3".format(path=os.path.basename(path)))
self._updateRecentFileSettings(path)
self._updateRecentFileActions()
@@ -1444,12 +1472,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
index = 0
size = len(self._settings["recent_files"])
for file_path in self._settings["recent_files"]:
if file_path and os.path.exists(file_path):
action = self._recent_file_actions[index]
action.setText(" {}. {}".format(index + 1, os.path.basename(file_path)))
action.setData(file_path)
action.setVisible(True)
index += 1
try:
if file_path and os.path.exists(file_path):
action = self._recent_file_actions[index]
action.setText(" {}. {}".format(index + 1, os.path.basename(file_path)))
action.setData(file_path)
action.setVisible(True)
index += 1
# We can have this error if user save a file with unicode char
# and change his system locale.
except UnicodeEncodeError:
pass
for index in range(size + 1, self._max_recent_files):
self._recent_file_actions[index].setVisible(False)
@@ -1550,6 +1583,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.setStyleSheet("")
self.uiNewProjectAction.setIcon(QtGui.QIcon(":/icons/new-project.svg"))
self.uiOpenProjectAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
self.uiOpenApplianceAction.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"))
@@ -1586,6 +1620,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
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.uiOpenApplianceAction.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"))
@@ -1645,6 +1680,7 @@ QComboBox QAbstractItemView {background-color: #dedede}
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.uiOpenApplianceAction.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"))

View File

@@ -20,9 +20,7 @@ Built-in module implementation.
"""
from gns3.qt import QtWidgets
from gns3.servers import Servers
from ..module import Module
from ..module_error import ModuleError
from .cloud import Cloud
from .host import Host
@@ -72,61 +70,6 @@ class Builtin(Module):
log.info("Built-in module reset")
self._nodes.clear()
def allocateServer(self, node_class):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (HTTPClient instance)
"""
# check all other modules to find if they
# are using a local server
using_local_server = []
from gns3.modules import MODULES
for module in MODULES:
instance = module.instance()
if instance != self:
module_settings = instance.settings()
if "use_local_server" in module_settings:
using_local_server.append(module_settings["use_local_server"])
# allocate a server for the node
servers = Servers.instance()
local_server = servers.localServer()
remote_servers = servers.remoteServers()
gns3_vm = Servers.instance().vmServer()
if not all(using_local_server) and (gns3_vm or len(remote_servers)):
# a module is not using a local server
server_list = ["Local server ({})".format(local_server.url())]
if gns3_vm:
server_list.append("GNS3 VM ({})".format(gns3_vm.url()))
if len(remote_servers):
if True not in using_local_server and len(remote_servers) == 1:
# no module is using a local server and there is only one
# remote server available, so no need to ask the user.
return next(iter(servers))
for remote_server in remote_servers:
server_list.append("{}".format(remote_server))
# TODO: move this to graphics_view
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
(selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "Server", "Please choose a server", server_list, 0, False)
if ok:
if selection.startswith("Local server"):
return local_server
elif selection.startswith("GNS3 VM"):
return gns3_vm
else:
return remote_servers[selection]
else:
raise ModuleError("Please select a server")
return local_server
def createNode(self, node_class, server, project):
"""
Creates a new node.

View File

@@ -20,7 +20,7 @@ Configuration page for clouds.
"""
import re
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:31 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
cloudConfigPageWidget.resize(653, 478)
@@ -456,4 +457,3 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONullTab), _translate("cloudConfigPageWidget", "NULL"))
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc."))

View File

@@ -24,11 +24,9 @@ import shutil
import hashlib
from gns3.qt import QtWidgets
from gns3.servers import Servers
from gns3.local_config import LocalConfig
from gns3.image_manager import ImageManager
from gns3.local_server_config import LocalServerConfig
from gns3.gns3_vm import GNS3VM
from ..module import Module
from ..module_error import ModuleError
@@ -47,7 +45,6 @@ from .nodes.frame_relay_switch import FrameRelaySwitch
from .nodes.atm_switch import ATMSwitch
from .settings import DYNAMIPS_SETTINGS
from .settings import IOS_ROUTER_SETTINGS
from .settings import PLATFORMS_DEFAULT_RAM
from .settings import DEFAULT_IDLEPC
PLATFORM_TO_CLASS = {
@@ -201,7 +198,7 @@ class Dynamips(Module):
node.server().decreaseAllocatedRAM(node.settings()["ram"])
self._nodes.remove(node)
def iosRouters(self):
def VMs(self):
"""
Returns IOS routers settings.
@@ -210,7 +207,7 @@ class Dynamips(Module):
return self._ios_routers
def setIOSRouters(self, new_ios_routers):
def setVMs(self, new_ios_routers):
"""
Sets IOS images settings.
@@ -220,6 +217,11 @@ class Dynamips(Module):
self._ios_routers = new_ios_routers.copy()
self._saveIOSRouters()
@staticmethod
def vmConfigurationPage():
from .pages.ios_router_configuration_page import IOSRouterConfigurationPage
return IOSRouterConfigurationPage
def settings(self):
"""
Returns the module settings
@@ -279,10 +281,9 @@ class Dynamips(Module):
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
vm_settings[setting_name] = value
base_name = "R"
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
# must be an EtherSwitch router
base_name = "ESW"
default_name_format = IOS_ROUTER_SETTINGS["default_name_format"]
if ios_router["default_name_format"]:
default_name_format = ios_router["default_name_format"]
# Older GNS3 versions may have the following invalid settings in the VM template
if "console" in vm_settings:
@@ -296,7 +297,7 @@ class Dynamips(Module):
image = vm_settings.pop("image", None)
if image is None:
raise ModuleError("No IOS image has been associated with this IOS router")
node.setup(image, ram, additional_settings=vm_settings, base_name=base_name)
node.setup(image, ram, additional_settings=vm_settings, default_name_format=default_name_format)
else:
node.setup()
@@ -312,8 +313,8 @@ class Dynamips(Module):
if os.path.basename(ios_router["image"]) == image_path:
if ios_router["idlepc"] != idlepc:
ios_router["idlepc"] = idlepc
log.info("Idle-PC value {} saved into '{}' template".format(idlepc, ios_router["name"]))
self._saveIOSRouters()
break
def reset(self):
"""
@@ -360,7 +361,7 @@ class Dynamips(Module):
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
ios_routers = self.iosRouters()
ios_routers = self.VMs()
candidate_ios_images = {}
alternative_image = {"image": image,
"ram": None,
@@ -376,7 +377,6 @@ class Dynamips(Module):
"IOS image", "IOS image {} could not be found\nPlease select an alternative from your existing images:".format(image),
list(candidate_ios_images.keys()), 0, False)
if ok:
ios_image = candidate_ios_images[selection] # FIXME
alternative_image["image"] = ios_router["image"]
alternative_image["ram"] = ios_router["ram"]
alternative_image["idlepc"] = ios_router["idlepc"]
@@ -422,24 +422,14 @@ class Dynamips(Module):
in the nodes view and create a node on the scene.
"""
server = "local"
if not self._settings["use_local_server"]:
if GNS3VM.instance().isRunning():
server = "vm"
else:
# pick up a remote server (round-robin method) #FIXME: review this
remote_server = next(iter(Servers.instance()))
if remote_server:
server = remote_server.url()
nodes = []
for node_class in [EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
nodes.append(
{"class": node_class.__name__,
"name": node_class.symbolName(),
"server": server,
"categories": node_class.categories(),
"symbol": node_class.defaultSymbol()}
"symbol": node_class.defaultSymbol(),
"builtin": True}
)
for ios_router in self._ios_routers.values():

View File

@@ -452,6 +452,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
}
if self.uiEtherSwitchCheckBox.isChecked():
settings["default_name_format"] = "ESW"
settings["startup_config"] = get_default_base_config(self._base_etherswitch_startup_config_template)
settings["symbol"] = ":/symbols/multilayer_switch.svg"
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat

View File

@@ -83,7 +83,8 @@ class EthernetSwitch(Device):
port.setPacketCaptureSupported(True)
self._ports.append(port)
self._settings["ports"][port.portNumber()] = {"type": initial_port["type"],
"vlan": initial_port["vlan"]}
"vlan": initial_port["vlan"],
"ethertype": initial_port.get("ethertype", "")}
params = {"name": name,
"device_type": "ethernet_switch"}
@@ -182,7 +183,8 @@ class EthernetSwitch(Device):
params["nio"] = self.getNIOInfo(nio)
port_info = self._settings["ports"][port.portNumber()]
port_settings = {"vlan": port_info["vlan"],
"type": port_info["type"]}
"type": port_info["type"],
"ethertype": port_info["ethertype"]}
params["port_settings"] = port_settings
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
@@ -217,17 +219,21 @@ class EthernetSwitch(Device):
port_info += " Port {} is empty\n".format(port.name())
else:
port_type = self._settings["ports"][port.portNumber()]["type"]
port_ethertype = self._settings["ports"][port.portNumber()]["ethertype"]
port_vlan = str(self._settings["ports"][port.portNumber()]["vlan"])
port_ethertype_info = ""
if port_type == "access":
port_vlan_info = "VLAN ID {}".format(port_vlan)
elif port_type == "dot1q":
port_vlan_info = "native VLAN {}".format(port_vlan)
elif port_type == "qinq":
port_vlan_info = "outer VLAN {}".format(port_vlan)
port_ethertype_info = "({})".format(port_ethertype)
port_info += " Port {name} is in {port_type} mode, with {port_vlan_info},\n".format(name=port.name(),
port_type=port_type,
port_vlan_info=port_vlan_info)
port_info += " Port {name} is in {port_type} {port_ethertype_info} mode, with {port_vlan_info},\n".format(name=port.name(),
port_type=port_type,
port_ethertype_info=port_ethertype_info,
port_vlan_info=port_vlan_info)
port_info += " {port_description}\n".format(port_description=port.description())
return info + port_info
@@ -254,6 +260,8 @@ class EthernetSwitch(Device):
port_info = port.dump()
if port.portNumber() in self._settings["ports"]:
port_info["type"] = self._settings["ports"][port.portNumber()]["type"]
if port_info["type"] == "qinq" and "ethertype" != "0x8100":
port_info["ethertype"] = self._settings["ports"][port.portNumber()]["ethertype"]
port_info["vlan"] = self._settings["ports"][port.portNumber()]["vlan"]
ports.append(port_info)

View File

@@ -218,7 +218,7 @@ class Router(VM):
self._addWICPorts(wic, wic_slot_number)
self._updateWICNumbering()
def setup(self, image, ram, name=None, vm_id=None, dynamips_id=None, additional_settings={}, base_name="R"):
def setup(self, image, ram, name=None, vm_id=None, dynamips_id=None, additional_settings={}, default_name_format="R{0}"):
"""
Setups this router.
@@ -232,7 +232,7 @@ class Router(VM):
# let's create a unique name if none has been chosen
if not name:
name = self.allocateName(base_name)
name = self.allocateName(default_name_format)
if not name:
self.error_signal.emit(self.id(), "could not allocate a name for this router")
@@ -258,18 +258,16 @@ class Router(VM):
# push the startup-config
if not vm_id and "startup_config" in additional_settings:
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del additional_settings["startup_config"]
# push the private-config
if not vm_id and "private_config" in additional_settings:
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del additional_settings["private_config"]
params.update(additional_settings)
@@ -316,10 +314,9 @@ class Router(VM):
params = {}
if "startup_config" in new_settings:
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del new_settings["startup_config"]
if "private_config" in new_settings:
@@ -344,10 +341,8 @@ class Router(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
for name, value in result.items():
@@ -487,7 +482,8 @@ class Router(VM):
self.httpGet("/dynamips/vms/{vm_id}/idlepc_proposals".format(
vm_id=self._vm_id),
callback,
context={"router": self})
context={"router": self},
progressText="Computing Idle-PC values, please wait...")
def computeAutoIdlepc(self, callback):
"""
@@ -498,7 +494,8 @@ class Router(VM):
self.httpGet("/dynamips/vms/{vm_id}/auto_idlepc".format(
vm_id=self._vm_id),
callback,
context={"router": self})
context={"router": self},
progressText="Computing Idle-PC values, please wait...")
def idlepc(self):
"""
@@ -664,25 +661,15 @@ class Router(VM):
:returns: representation of the node (dictionary)
"""
router = {"id": self.id(),
"vm_id": self._vm_id,
"dynamips_id": self._dynamips_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()}
router = super().dump()
router["vm_id"] = self._vm_id
router["dynamips_id"] = self._dynamips_id
# add the properties
for name, value in self._settings.items():
if value is not None and value != "":
router["properties"][name] = value
# add the ports
if self._ports:
ports = router["ports"] = []
for port in self._ports:
ports.append(port.dump())
return router
def load(self, node_info):
@@ -693,6 +680,8 @@ class Router(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = dynamips_id = node_info.get("router_id")
if not vm_id:
@@ -705,7 +694,7 @@ class Router(VM):
vm_settings[name] = value
name = vm_settings.pop("name")
ram = vm_settings.pop("ram", PLATFORMS_DEFAULT_RAM[self._settings["platform"]])
image = vm_settings.pop("image")
image = vm_settings.pop("image", "")
if self.server().isLocal():
# check and update the path to use the image in the images directory
@@ -722,35 +711,8 @@ class Router(VM):
log.info("router {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(image, ram, name, vm_id, dynamips_id, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# update the port with the correct names and IDs
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("router {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def saveConfig(self):
"""
Save the configs
@@ -881,7 +843,11 @@ class Router(VM):
:param directory: source directory path
"""
contents = os.listdir(directory)
try:
contents = os.listdir(directory)
except OSError as e:
self.warning_signal.emit(self.id(), "Configuration could not be loaded from directory {}: {}".format(directory, e))
return
startup_config = normalize_filename(self.name()) + "_startup-config.cfg"
private_config = normalize_filename(self.name()) + "_private-config.cfg"
new_settings = {}

View File

@@ -20,7 +20,7 @@ Configuration page for Dynamips ATM bridges.
"""
import re
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
from ..ui.atm_bridge_configuration_page_ui import Ui_atmBridgeConfigPageWidget

View File

@@ -20,7 +20,7 @@ Configuration page for Dynamips ATM switches.
"""
import re
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
from ..ui.atm_switch_configuration_page_ui import Ui_atmSwitchConfigPageWidget

View File

@@ -19,7 +19,7 @@
Configuration page for Dynamips Ethernet switches.
"""
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
from ..utils.tree_widget_item import TreeWidgetItem
from ..ui.ethernet_switch_configuration_page_ui import Ui_ethernetSwitchConfigPageWidget
@@ -41,6 +41,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
self.uiDeletePushButton.clicked.connect(self._deletePortSlot)
self.uiPortsTreeWidget.itemActivated.connect(self._portSelectedSlot)
self.uiPortsTreeWidget.itemSelectionChanged.connect(self._portSelectionChangedSlot)
self.uiPortTypeComboBox.currentIndexChanged.connect(self._typeSelectionChangedSlot)
# enable sorting
self.uiPortsTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
@@ -57,11 +58,19 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
port = int(item.text(0))
vlan = int(item.text(1))
port_type = item.text(2)
port_ethertype = item.text(3)
self.uiPortSpinBox.setValue(port)
self.uiVlanSpinBox.setValue(vlan)
index = self.uiPortTypeComboBox.findText(port_type)
if index != -1:
self.uiPortTypeComboBox.setCurrentIndex(index)
index = self.uiPortEtherTypeComboBox.findText(port_ethertype)
if index != -1:
self.uiPortEtherTypeComboBox.setCurrentIndex(index)
if port_type == "qinq":
self.uiPortEtherTypeComboBox.setEnabled(True)
else:
self.uiPortEtherTypeComboBox.setEnabled(False)
def _portSelectionChangedSlot(self):
"""
@@ -74,6 +83,17 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
else:
self.uiDeletePushButton.setEnabled(False)
def _typeSelectionChangedSlot(self):
"""
Disable Q-in-Q EtherType for access and dot1q ports.
"""
port_type = self.uiPortTypeComboBox.currentText()
if port_type == "qinq":
self.uiPortEtherTypeComboBox.setEnabled(True)
else:
self.uiPortEtherTypeComboBox.setEnabled(False)
def _addPortSlot(self):
"""
Adds a new port.
@@ -82,12 +102,17 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
port = self.uiPortSpinBox.value()
vlan = self.uiVlanSpinBox.value()
port_type = self.uiPortTypeComboBox.currentText()
if port_type == "qinq":
port_ethertype = self.uiPortEtherTypeComboBox.currentText()
else:
port_ethertype = ""
if port in self._ports:
# update a given entry in the tree widget
item = self.uiPortsTreeWidget.findItems(str(port), QtCore.Qt.MatchFixedString)[0]
item.setText(1, str(vlan))
item.setText(2, port_type)
item.setText(3, port_ethertype)
else:
# add a new entry in the tree widget
@@ -95,10 +120,12 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
item.setText(0, str(port))
item.setText(1, str(vlan))
item.setText(2, port_type)
item.setText(3, port_ethertype)
self.uiPortsTreeWidget.addTopLevelItem(item)
self._ports[port] = {"type": port_type,
"vlan": vlan}
"vlan": vlan,
"ethertype": port_ethertype}
self.uiPortSpinBox.setValue(max(self._ports) + 1)
self.uiPortsTreeWidget.resizeColumnToContents(0)
@@ -147,6 +174,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
item.setText(0, str(port))
item.setText(1, str(info["vlan"]))
item.setText(2, info["type"])
item.setText(3, info["ethertype"])
self.uiPortsTreeWidget.addTopLevelItem(item)
self._ports[port] = info

View File

@@ -19,7 +19,7 @@
Configuration page for Dynamips Frame Relay switches.
"""
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
from ..ui.frame_relay_switch_configuration_page_ui import Ui_frameRelaySwitchConfigPageWidget

View File

@@ -281,6 +281,14 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
self.uiBaseMACLineEdit.hide()
if not node:
# these are template settings
# rename the label from "Name" to "Template name"
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the startup-config
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
@@ -296,6 +304,8 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiStartupConfigLabel.hide()
self.uiStartupConfigLineEdit.hide()
self.uiStartupConfigToolButton.hide()
@@ -519,11 +529,20 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
del settings["image"]
if not node:
# these are template settings
# save the default name format
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
startup_config = self.uiStartupConfigLineEdit.text().strip()
if not startup_config:
settings["startup_config"] = ""
elif startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
if self._configFileValid(startup_config):
settings["startup_config"] = startup_config
else:
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
@@ -532,7 +551,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if not private_config:
settings["private_config"] = ""
elif private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
if self._configFileValid(private_config):
settings["private_config"] = private_config
else:
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
@@ -636,3 +655,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if node:
self._checkForLinkConnectedToWIC(wic_number, settings, node)
settings["wic" + str(wic_number)] = None
def _configFileValid(self, path):
"""
Return true if it's a valid configuration file
"""
if not os.path.isabs(path):
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
return os.access(path, os.R_OK)

View File

@@ -160,7 +160,6 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
for item in self.uiIOSRoutersTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
del self._ios_routers[key]
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
@@ -228,7 +227,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
try:
os.makedirs(cls.getImageDirectory(), exist_ok=True)
except OSError as e:
QtWidgets.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, e))
QtWidgets.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(cls.getImageDirectory(), e))
return
compressed = False
@@ -271,7 +270,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
decompressed_size += zip_info.file_size
else:
decompressed_size = os.path.getsize(path)
except (zipfile.BadZipFile, OSError):
except (zipfile.BadZipFile, OSError, ValueError):
return 0
# get the size in MB
@@ -337,7 +336,8 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["Name:", ios_router["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", ios_router["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ios_router["default_name_format"]])
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ios_router["server"]])
QtWidgets.QTreeWidgetItem(section_item, ["Platform:", ios_router["platform"]])
if ios_router["chassis"]:
@@ -350,8 +350,8 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
if ios_router["private_config"]:
QtWidgets.QTreeWidgetItem(section_item, ["Private-config:", ios_router["private_config"]])
if ios_router["platform"] == "c7200":
QtWidgets.QTreeWidgetItem(section_item, ["Midplane:", ios_router["midplane"]])
QtWidgets.QTreeWidgetItem(section_item, ["NPE:", ios_router["npe"]])
QtWidgets.QTreeWidgetItem(section_item, ["Midplane:", ios_router.get("midplane", "vxr")])
QtWidgets.QTreeWidgetItem(section_item, ["NPE:", ios_router.get("npe", "npe-400")])
# fill out the Memories and disk section
section_item = self._createSectionItem("Memories and disks")
@@ -391,7 +391,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
"""
dynamips_module = Dynamips.instance()
self._ios_routers = copy.deepcopy(dynamips_module.iosRouters())
self._ios_routers = copy.deepcopy(dynamips_module.VMs())
self._items.clear()
for key, ios_router in self._ios_routers.items():
@@ -410,4 +410,4 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
Saves the IOS router preferences.
"""
Dynamips.instance().setIOSRouters(self._ios_routers)
Dynamips.instance().setVMs(self._ios_routers)

View File

@@ -32,6 +32,7 @@ DYNAMIPS_SETTINGS = {
IOS_ROUTER_SETTINGS = {
"name": "",
"default_name_format": "R{0}",
"image": "",
"symbol": ":/symbols/router.svg",
"category": Node.routers,
@@ -73,6 +74,7 @@ PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
"c3745": 256,
"c7200": 512}
# MD5 checksum done on uncompressed IOS images
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
@@ -87,7 +89,8 @@ DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adv
"64f8c427ed48fd21bd02cf1ff254c4eb": "0x60c09aa0", # c3725-adventerprisek9-mz.124-15.T14
"ddbaf74274822b50fa9670e10c75b08f": "0x60aa1da0", # c3745-adventerprisek9-mz.124-25d
"4af2e752220ed1397924150ff7bbe4ce": "0x602701e4", # c3745-adventerprisek9-mz.124-15.T14
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838"} # c7200-adventerprisek9-mz.124-24.T5
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838", # c7200-adventerprisek9-mz.124-24.T5
"dda82f22a39215bc6b27af891e12b8f6": "0x6018c294"} # c7200-adventerprisek9-mz.155-2.XB
# platforms with supported chassis
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_bridge_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmBridgeConfigPageWidget(object):
def setupUi(self, atmBridgeConfigPageWidget):
atmBridgeConfigPageWidget.setObjectName("atmBridgeConfigPageWidget")
atmBridgeConfigPageWidget.resize(432, 358)
@@ -151,4 +152,3 @@ class Ui_atmBridgeConfigPageWidget(object):
self.uiDeletePushButton.setText(_translate("atmBridgeConfigPageWidget", "&Delete"))
self.uiGeneralGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("atmBridgeConfigPageWidget", "Name:"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_switch_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
atmSwitchConfigPageWidget.resize(459, 419)
@@ -185,4 +186,3 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/dynamips_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DynamipsPreferencesPageWidget(object):
def setupUi(self, DynamipsPreferencesPageWidget):
DynamipsPreferencesPageWidget.setObjectName("DynamipsPreferencesPageWidget")
DynamipsPreferencesPageWidget.resize(435, 200)
@@ -108,4 +109,3 @@ class Ui_DynamipsPreferencesPageWidget(object):
self.uiSparseMemorySupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable sparse memory support"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiAdvancedSettingsTabWidget), _translate("DynamipsPreferencesPageWidget", "Advanced settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("DynamipsPreferencesPageWidget", "Restore defaults"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ethernet_hub_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ethernetHubConfigPageWidget(object):
def setupUi(self, ethernetHubConfigPageWidget):
ethernetHubConfigPageWidget.setObjectName("ethernetHubConfigPageWidget")
ethernetHubConfigPageWidget.resize(381, 270)
@@ -57,4 +58,3 @@ class Ui_ethernetHubConfigPageWidget(object):
self.uiSettingsGroupBox.setTitle(_translate("ethernetHubConfigPageWidget", "Settings"))
self.uiNameLabel.setText(_translate("ethernetHubConfigPageWidget", "Name:"))
self.uiPortsLabel.setText(_translate("ethernetHubConfigPageWidget", "Number of ports:"))

View File

@@ -65,6 +65,11 @@
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>EtherType</string>
</property>
</column>
</widget>
</item>
</layout>
@@ -141,6 +146,13 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>QinQ EtherType:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="uiPortTypeComboBox">
<item>
@@ -160,6 +172,33 @@
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="uiPortEtherTypeComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>0x8100</string>
</property>
</item>
<item>
<property name="text">
<string>0x88A8</string>
</property>
</item>
<item>
<property name="text">
<string>0x9100</string>
</property>
</item>
<item>
<property name="text">
<string>0x9200</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ethernet_switch_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.5
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ethernetSwitchConfigPageWidget(object):
def setupUi(self, ethernetSwitchConfigPageWidget):
ethernetSwitchConfigPageWidget.setObjectName("ethernetSwitchConfigPageWidget")
ethernetSwitchConfigPageWidget.resize(397, 315)
@@ -80,12 +81,23 @@ class Ui_ethernetSwitchConfigPageWidget(object):
self.label_2 = QtWidgets.QLabel(self.uiEthernetSwitchSettingsGroupBox)
self.label_2.setObjectName("label_2")
self.gridlayout.addWidget(self.label_2, 2, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.uiEthernetSwitchSettingsGroupBox)
self.label_4.setObjectName("label_4")
self.gridlayout.addWidget(self.label_4, 3, 0, 1, 1)
self.uiPortTypeComboBox = QtWidgets.QComboBox(self.uiEthernetSwitchSettingsGroupBox)
self.uiPortTypeComboBox.setObjectName("uiPortTypeComboBox")
self.uiPortTypeComboBox.addItem("")
self.uiPortTypeComboBox.addItem("")
self.uiPortTypeComboBox.addItem("")
self.gridlayout.addWidget(self.uiPortTypeComboBox, 2, 1, 1, 1)
self.uiPortEtherTypeComboBox = QtWidgets.QComboBox(self.uiEthernetSwitchSettingsGroupBox)
self.uiPortEtherTypeComboBox.setEnabled(False)
self.uiPortEtherTypeComboBox.setObjectName("uiPortEtherTypeComboBox")
self.uiPortEtherTypeComboBox.addItem("")
self.uiPortEtherTypeComboBox.addItem("")
self.uiPortEtherTypeComboBox.addItem("")
self.uiPortEtherTypeComboBox.addItem("")
self.gridlayout.addWidget(self.uiPortEtherTypeComboBox, 3, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiEthernetSwitchSettingsGroupBox, 1, 0, 1, 2)
self.uiAddPushButton = QtWidgets.QPushButton(ethernetSwitchConfigPageWidget)
self.uiAddPushButton.setObjectName("uiAddPushButton")
@@ -116,13 +128,18 @@ class Ui_ethernetSwitchConfigPageWidget(object):
self.uiPortsTreeWidget.headerItem().setText(0, _translate("ethernetSwitchConfigPageWidget", "Port"))
self.uiPortsTreeWidget.headerItem().setText(1, _translate("ethernetSwitchConfigPageWidget", "VLAN"))
self.uiPortsTreeWidget.headerItem().setText(2, _translate("ethernetSwitchConfigPageWidget", "Type"))
self.uiPortsTreeWidget.headerItem().setText(3, _translate("ethernetSwitchConfigPageWidget", "EtherType"))
self.uiEthernetSwitchSettingsGroupBox.setTitle(_translate("ethernetSwitchConfigPageWidget", "Settings"))
self.label.setText(_translate("ethernetSwitchConfigPageWidget", "Port:"))
self.label_3.setText(_translate("ethernetSwitchConfigPageWidget", "VLAN:"))
self.label_2.setText(_translate("ethernetSwitchConfigPageWidget", "Type:"))
self.label_4.setText(_translate("ethernetSwitchConfigPageWidget", "QinQ EtherType:"))
self.uiPortTypeComboBox.setItemText(0, _translate("ethernetSwitchConfigPageWidget", "access"))
self.uiPortTypeComboBox.setItemText(1, _translate("ethernetSwitchConfigPageWidget", "dot1q"))
self.uiPortTypeComboBox.setItemText(2, _translate("ethernetSwitchConfigPageWidget", "qinq"))
self.uiPortEtherTypeComboBox.setItemText(0, _translate("ethernetSwitchConfigPageWidget", "0x8100"))
self.uiPortEtherTypeComboBox.setItemText(1, _translate("ethernetSwitchConfigPageWidget", "0x88A8"))
self.uiPortEtherTypeComboBox.setItemText(2, _translate("ethernetSwitchConfigPageWidget", "0x9100"))
self.uiPortEtherTypeComboBox.setItemText(3, _translate("ethernetSwitchConfigPageWidget", "0x9200"))
self.uiAddPushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Delete"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/frame_relay_switch_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:32 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_frameRelaySwitchConfigPageWidget(object):
def setupUi(self, frameRelaySwitchConfigPageWidget):
frameRelaySwitchConfigPageWidget.setObjectName("frameRelaySwitchConfigPageWidget")
frameRelaySwitchConfigPageWidget.resize(499, 405)
@@ -148,4 +149,3 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
self.uiDestinationDLCILabel.setText(_translate("frameRelaySwitchConfigPageWidget", "DLCI:"))
self.uiAddPushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Delete"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>476</width>
<height>510</height>
<width>499</width>
<height>507</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,58 +24,13 @@
<string>General</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiPlatformLabel">
<property name="text">
<string>Platform:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="uiPlatformTextLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiChassisLabel">
<property name="text">
<string>Chassis:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="uiChassisTextLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="uiIOSImageLabel">
<property name="text">
<string>IOS image path:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="uiIOSImageLineEdit"/>
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiIOSImageToolButton">
<widget class="QToolButton" name="uiPrivateConfigToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
@@ -86,10 +41,47 @@
</item>
</layout>
</item>
<item row="9" column="0">
<widget class="QLabel" name="uiStartupConfigLabel">
<item row="17" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>151</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="14" column="0">
<widget class="QLabel" name="uiAuxPortLabel">
<property name="text">
<string>Initial startup-config:</string>
<string>Aux port:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="uiChassisTextLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<property name="text">
<string>Console port:</string>
</property>
</widget>
</item>
@@ -110,20 +102,20 @@
</item>
</layout>
</item>
<item row="10" column="0">
<widget class="QLabel" name="uiPrivateConfigLabel">
<item row="3" column="0">
<widget class="QLabel" name="uiPlatformLabel">
<property name="text">
<string>Initial private-config:</string>
<string>Platform:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
<widget class="QLineEdit" name="uiIOSImageLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="uiPrivateConfigToolButton">
<widget class="QToolButton" name="uiIOSImageToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
@@ -134,91 +126,38 @@
</item>
</layout>
</item>
<item row="13" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Console port:</string>
<string>Name:</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="uiAuxPortLabel">
<item row="10" column="0">
<widget class="QLabel" name="uiPrivateConfigLabel">
<property name="text">
<string>Aux port:</string>
<string>Initial private-config:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QSpinBox" name="uiAuxPortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="uiMidplaneLabel">
<item row="9" column="0">
<widget class="QLabel" name="uiStartupConfigLabel">
<property name="text">
<string>Midplane:</string>
<string>Initial startup-config:</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QComboBox" name="uiMidplaneComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="uiNPELabel">
<item row="3" column="1">
<widget class="QLabel" name="uiPlatformTextLabel">
<property name="text">
<string>NPE:</string>
<string/>
</property>
</widget>
</item>
<item row="17" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>151</height>
</size>
</property>
</spacer>
</item>
<item row="16" column="1">
<widget class="QComboBox" name="uiNPEComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<item row="6" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Symbol:</string>
<string>Category:</string>
</property>
</widget>
</item>
@@ -239,16 +178,87 @@
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<item row="4" column="0">
<widget class="QLabel" name="uiChassisLabel">
<property name="text">
<string>Category:</string>
<string>Chassis:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QComboBox" name="uiNPEComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QComboBox" name="uiMidplaneComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="uiMidplaneLabel">
<property name="text">
<string>Midplane:</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="uiIOSImageLabel">
<property name="text">
<string>IOS image path:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="16" column="0">
<widget class="QLabel" name="uiNPELabel">
<property name="text">
<string>NPE:</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QSpinBox" name="uiAuxPortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiMemoriesPageWidget">

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created: Thu Feb 4 21:08:13 2016
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -11,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_iosRouterConfigPageWidget(object):
def setupUi(self, iosRouterConfigPageWidget):
iosRouterConfigPageWidget.setObjectName("iosRouterConfigPageWidget")
iosRouterConfigPageWidget.resize(476, 510)
iosRouterConfigPageWidget.resize(499, 507)
self.vboxlayout = QtWidgets.QVBoxLayout(iosRouterConfigPageWidget)
self.vboxlayout.setObjectName("vboxlayout")
self.uiTabWidget = QtWidgets.QTabWidget(iosRouterConfigPageWidget)
@@ -20,55 +21,6 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiGeneralPageWidget.setObjectName("uiGeneralPageWidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiGeneralPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout_2.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout_2.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiPlatformLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPlatformLabel.setObjectName("uiPlatformLabel")
self.gridLayout_2.addWidget(self.uiPlatformLabel, 3, 0, 1, 1)
self.uiPlatformTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPlatformTextLabel.setText("")
self.uiPlatformTextLabel.setObjectName("uiPlatformTextLabel")
self.gridLayout_2.addWidget(self.uiPlatformTextLabel, 3, 1, 1, 1)
self.uiChassisLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiChassisLabel.setObjectName("uiChassisLabel")
self.gridLayout_2.addWidget(self.uiChassisLabel, 4, 0, 1, 1)
self.uiChassisTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiChassisTextLabel.setText("")
self.uiChassisTextLabel.setObjectName("uiChassisTextLabel")
self.gridLayout_2.addWidget(self.uiChassisTextLabel, 4, 1, 1, 1)
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
self.gridLayout_2.addWidget(self.uiIOSImageLabel, 8, 0, 1, 1)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiIOSImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiIOSImageLineEdit.setObjectName("uiIOSImageLineEdit")
self.horizontalLayout_5.addWidget(self.uiIOSImageLineEdit)
self.uiIOSImageToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
self.uiIOSImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiIOSImageToolButton.setObjectName("uiIOSImageToolButton")
self.horizontalLayout_5.addWidget(self.uiIOSImageToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_5, 8, 1, 1, 1)
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
self.gridLayout_2.addWidget(self.uiStartupConfigLabel, 9, 0, 1, 1)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiStartupConfigLineEdit.setObjectName("uiStartupConfigLineEdit")
self.horizontalLayout_4.addWidget(self.uiStartupConfigLineEdit)
self.uiStartupConfigToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 9, 1, 1, 1)
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
self.gridLayout_2.addWidget(self.uiPrivateConfigLabel, 10, 0, 1, 1)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.uiPrivateConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
@@ -79,49 +31,63 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiPrivateConfigToolButton.setObjectName("uiPrivateConfigToolButton")
self.horizontalLayout_6.addWidget(self.uiPrivateConfigToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_6, 10, 1, 1, 1)
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
self.gridLayout_2.addWidget(self.uiConsolePortLabel, 13, 0, 1, 1)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout_2.addWidget(self.uiConsolePortSpinBox, 13, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 17, 1, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout_2.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiAuxPortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiAuxPortLabel.setObjectName("uiAuxPortLabel")
self.gridLayout_2.addWidget(self.uiAuxPortLabel, 14, 0, 1, 1)
self.uiAuxPortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
self.uiAuxPortSpinBox.setMaximum(65535)
self.uiAuxPortSpinBox.setObjectName("uiAuxPortSpinBox")
self.gridLayout_2.addWidget(self.uiAuxPortSpinBox, 14, 1, 1, 1)
self.uiMidplaneLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiMidplaneLabel.setObjectName("uiMidplaneLabel")
self.gridLayout_2.addWidget(self.uiMidplaneLabel, 15, 0, 1, 1)
self.uiMidplaneComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiMidplaneComboBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiMidplaneComboBox.sizePolicy().hasHeightForWidth())
self.uiMidplaneComboBox.setSizePolicy(sizePolicy)
self.uiMidplaneComboBox.setObjectName("uiMidplaneComboBox")
self.gridLayout_2.addWidget(self.uiMidplaneComboBox, 15, 1, 1, 1)
self.uiNPELabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiNPELabel.setObjectName("uiNPELabel")
self.gridLayout_2.addWidget(self.uiNPELabel, 16, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 17, 1, 1, 1)
self.uiNPEComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiNPEComboBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNPEComboBox.sizePolicy().hasHeightForWidth())
self.uiNPEComboBox.setSizePolicy(sizePolicy)
self.uiNPEComboBox.setObjectName("uiNPEComboBox")
self.gridLayout_2.addWidget(self.uiNPEComboBox, 16, 1, 1, 1)
self.uiChassisTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiChassisTextLabel.setText("")
self.uiChassisTextLabel.setObjectName("uiChassisTextLabel")
self.gridLayout_2.addWidget(self.uiChassisTextLabel, 4, 1, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout_2.addWidget(self.uiSymbolLabel, 5, 0, 1, 1)
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
self.gridLayout_2.addWidget(self.uiConsolePortLabel, 13, 0, 1, 1)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiStartupConfigLineEdit.setObjectName("uiStartupConfigLineEdit")
self.horizontalLayout_4.addWidget(self.uiStartupConfigLineEdit)
self.uiStartupConfigToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 9, 1, 1, 1)
self.uiPlatformLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPlatformLabel.setObjectName("uiPlatformLabel")
self.gridLayout_2.addWidget(self.uiPlatformLabel, 3, 0, 1, 1)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiIOSImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiIOSImageLineEdit.setObjectName("uiIOSImageLineEdit")
self.horizontalLayout_5.addWidget(self.uiIOSImageLineEdit)
self.uiIOSImageToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
self.uiIOSImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiIOSImageToolButton.setObjectName("uiIOSImageToolButton")
self.horizontalLayout_5.addWidget(self.uiIOSImageToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_5, 8, 1, 1, 1)
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout_2.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
self.gridLayout_2.addWidget(self.uiPrivateConfigLabel, 10, 0, 1, 1)
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
self.gridLayout_2.addWidget(self.uiStartupConfigLabel, 9, 0, 1, 1)
self.uiPlatformTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiPlatformTextLabel.setText("")
self.uiPlatformTextLabel.setObjectName("uiPlatformTextLabel")
self.gridLayout_2.addWidget(self.uiPlatformTextLabel, 3, 1, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout_2.addWidget(self.uiCategoryLabel, 6, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
@@ -132,12 +98,53 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout_2.addLayout(self.horizontalLayout_7, 5, 1, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout_2.addWidget(self.uiCategoryLabel, 6, 0, 1, 1)
self.uiChassisLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiChassisLabel.setObjectName("uiChassisLabel")
self.gridLayout_2.addWidget(self.uiChassisLabel, 4, 0, 1, 1)
self.uiNPEComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiNPEComboBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNPEComboBox.sizePolicy().hasHeightForWidth())
self.uiNPEComboBox.setSizePolicy(sizePolicy)
self.uiNPEComboBox.setObjectName("uiNPEComboBox")
self.gridLayout_2.addWidget(self.uiNPEComboBox, 16, 1, 1, 1)
self.uiMidplaneComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiMidplaneComboBox.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiMidplaneComboBox.sizePolicy().hasHeightForWidth())
self.uiMidplaneComboBox.setSizePolicy(sizePolicy)
self.uiMidplaneComboBox.setObjectName("uiMidplaneComboBox")
self.gridLayout_2.addWidget(self.uiMidplaneComboBox, 15, 1, 1, 1)
self.uiMidplaneLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiMidplaneLabel.setObjectName("uiMidplaneLabel")
self.gridLayout_2.addWidget(self.uiMidplaneLabel, 15, 0, 1, 1)
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
self.gridLayout_2.addWidget(self.uiIOSImageLabel, 8, 0, 1, 1)
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout_2.addWidget(self.uiCategoryComboBox, 6, 1, 1, 1)
self.uiNPELabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiNPELabel.setObjectName("uiNPELabel")
self.gridLayout_2.addWidget(self.uiNPELabel, 16, 0, 1, 1)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout_2.addWidget(self.uiConsolePortSpinBox, 13, 1, 1, 1)
self.uiAuxPortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
self.uiAuxPortSpinBox.setMaximum(65535)
self.uiAuxPortSpinBox.setObjectName("uiAuxPortSpinBox")
self.gridLayout_2.addWidget(self.uiAuxPortSpinBox, 14, 1, 1, 1)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout_2.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout_2.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
self.uiTabWidget.addTab(self.uiGeneralPageWidget, "")
self.uiMemoriesPageWidget = QtWidgets.QWidget()
self.uiMemoriesPageWidget.setObjectName("uiMemoriesPageWidget")
@@ -545,22 +552,23 @@ class Ui_iosRouterConfigPageWidget(object):
def retranslateUi(self, iosRouterConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
iosRouterConfigPageWidget.setWindowTitle(_translate("iosRouterConfigPageWidget", "Dynamips IOS Router configuration"))
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:"))
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:"))
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:"))
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:"))
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:"))
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:"))
self.uiPrivateConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:"))
self.uiAuxPortLabel.setText(_translate("iosRouterConfigPageWidget", "Aux port:"))
self.uiMidplaneLabel.setText(_translate("iosRouterConfigPageWidget", "Midplane:"))
self.uiNPELabel.setText(_translate("iosRouterConfigPageWidget", "NPE:"))
self.uiSymbolLabel.setText(_translate("iosRouterConfigPageWidget", "Symbol:"))
self.uiSymbolToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:"))
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:"))
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:"))
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:"))
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:"))
self.uiCategoryLabel.setText(_translate("iosRouterConfigPageWidget", "Category:"))
self.uiSymbolToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:"))
self.uiMidplaneLabel.setText(_translate("iosRouterConfigPageWidget", "Midplane:"))
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:"))
self.uiNPELabel.setText(_translate("iosRouterConfigPageWidget", "NPE:"))
self.uiDefaultNameFormatLabel.setText(_translate("iosRouterConfigPageWidget", "Default name format:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralPageWidget), _translate("iosRouterConfigPageWidget", "General"))
self.uiRamLabel.setText(_translate("iosRouterConfigPageWidget", "RAM size:"))
self.uiRamSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:33 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_IOSRouterPreferencesPageWidget(object):
def setupUi(self, IOSRouterPreferencesPageWidget):
IOSRouterPreferencesPageWidget.setObjectName("IOSRouterPreferencesPageWidget")
IOSRouterPreferencesPageWidget.resize(505, 350)
@@ -77,4 +78,3 @@ class Ui_IOSRouterPreferencesPageWidget(object):
self.uiDecompressIOSPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Decompress"))
self.uiEditIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Edit"))
self.uiDeleteIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Delete"))

View File

@@ -29,11 +29,11 @@
<property name="title">
<string>Server type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="text">
<string>Remote</string>
<string>Run the IOS on a remote computer</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -43,30 +43,17 @@
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="text">
<string>GNS3 VM</string>
<string>Run the IOS on the GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Local</string>
<string>Run the IOS on your local computer</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
#
# Created: Wed Jul 15 12:22:33 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,20 +19,18 @@ class Ui_IOSRouterWizard(object):
self.verticalLayout.setObjectName("verticalLayout")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setChecked(True)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
self.verticalLayout_3.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.horizontalLayout.addWidget(self.uiVMRadioButton)
self.verticalLayout_3.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.verticalLayout_3.addWidget(self.uiLocalRadioButton)
self.verticalLayout.addWidget(self.uiServerTypeGroupBox)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
@@ -71,8 +68,8 @@ class Ui_IOSRouterWizard(object):
self.uiIOSNewImageRadioButton.setChecked(False)
self.uiIOSNewImageRadioButton.setObjectName("uiIOSNewImageRadioButton")
self.horizontalLayout_4.addWidget(self.uiIOSNewImageRadioButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_4.addItem(spacerItem1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_4.addItem(spacerItem)
self.verticalLayout_2.addLayout(self.horizontalLayout_4)
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiIOSImageWizardPage)
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
@@ -295,9 +292,9 @@ class Ui_IOSRouterWizard(object):
self.uiServerWizardPage.setTitle(_translate("IOSRouterWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose a server type to run your new IOS router."))
self.uiServerTypeGroupBox.setTitle(_translate("IOSRouterWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("IOSRouterWizard", "Remote"))
self.uiVMRadioButton.setText(_translate("IOSRouterWizard", "GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("IOSRouterWizard", "Local"))
self.uiRemoteRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on a remote computer"))
self.uiVMRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on the GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on your local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("IOSRouterWizard", "Remote servers"))
self.uiLoadBalanceCheckBox.setText(_translate("IOSRouterWizard", "Load balance across all available remote servers"))
self.uiRemoteServersLabel.setText(_translate("IOSRouterWizard", "Run on server:"))

View File

@@ -70,4 +70,6 @@ class DecompressIOSWorker(QtCore.QObject):
Cancel this worker.
"""
if not self:
return
self._is_running = False

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
class TreeWidgetItem(QtWidgets.QTreeWidgetItem):

View File

@@ -23,7 +23,7 @@ import sys
import os
import shutil
from gns3.qt import QtCore, QtWidgets
from gns3.qt import QtWidgets
from gns3.local_server_config import LocalServerConfig
from gns3.local_config import LocalConfig
@@ -141,7 +141,7 @@ class IOU(Module):
node.server().decreaseAllocatedRAM(node.settings()["ram"])
self._nodes.remove(node)
def iouDevices(self):
def VMs(self):
"""
Returns IOU devices settings.
@@ -150,7 +150,7 @@ class IOU(Module):
return self._iou_devices
def setIOUDevices(self, new_iou_devices):
def setVMs(self, new_iou_devices):
"""
Sets IOS devices settings.
@@ -160,6 +160,11 @@ class IOU(Module):
self._iou_devices = new_iou_devices.copy()
self._saveIOUDevices()
@staticmethod
def vmConfigurationPage():
from .pages.iou_device_configuration_page import iouDeviceConfigurationPage
return iouDeviceConfigurationPage
def settings(self):
"""
Returns the module settings
@@ -236,6 +241,10 @@ class IOU(Module):
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
vm_settings[setting_name] = value
default_name_format = IOU_DEVICE_SETTINGS["default_name_format"]
if self._iou_devices[iouimage]["default_name_format"]:
default_name_format = self._iou_devices[iouimage]["default_name_format"]
if vm_settings["use_default_iou_values"]:
del vm_settings["ram"]
del vm_settings["nvram"]
@@ -245,7 +254,7 @@ class IOU(Module):
del vm_settings["console"]
iou_path = vm_settings.pop("path")
node.setup(iou_path, additional_settings=vm_settings)
node.setup(iou_path, additional_settings=vm_settings, default_name_format=default_name_format)
def reset(self):
"""
@@ -291,7 +300,7 @@ class IOU(Module):
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
iou_devices = self.iouDevices()
iou_devices = self.VMs()
candidate_iou_images = {}
alternative_image = image

View File

@@ -23,7 +23,6 @@ import os
import re
from gns3.vm import VM
from gns3.node import Node
from gns3.servers import Servers
from gns3.packet_capture import PacketCapture
from gns3.ports.ethernet_port import EthernetPort
from gns3.ports.serial_port import SerialPort
@@ -104,7 +103,7 @@ class IOUDevice(VM):
self._ports.remove(port)
log.info("port {} has been removed".format(port.name()))
def setup(self, iou_path, name=None, vm_id=None, additional_settings={}, base_name="IOU"):
def setup(self, iou_path, name=None, vm_id=None, additional_settings={}, default_name_format="IOU{0}"):
"""
Setups this IOU device.
@@ -115,7 +114,7 @@ class IOUDevice(VM):
# let's create a unique name if none has been chosen
if not name:
name = self.allocateName(base_name)
name = self.allocateName(default_name_format)
if not name:
self.error_signal.emit(self.id(), "could not allocate a name for this IOU device")
@@ -130,24 +129,22 @@ class IOUDevice(VM):
# push the startup-config
if "startup_config" in additional_settings:
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del additional_settings["startup_config"]
# push the startup-config
if "private_config" in additional_settings:
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del additional_settings["private_config"]
params = self._addIourcContentToParams(params)
params.update(additional_settings)
self.httpPost("/iou/vms", self._setupCallback, body=params)
self.httpPost("/iou/vms", self._setupCallback, body=params, progressText="Creating {}".format(name))
def _setupCallback(self, result, error=False, **kwargs):
"""
@@ -188,7 +185,7 @@ class IOUDevice(VM):
params = self._addIourcContentToParams(params)
log.debug("{} is starting".format(self.name()))
self.httpPost("/{prefix}/vms/{vm_id}/start".format(prefix=self.URL_PREFIX, vm_id=self._vm_id), self._startCallback, body=params)
self.httpPost("/{prefix}/vms/{vm_id}/start".format(prefix=self.URL_PREFIX, vm_id=self._vm_id), self._startCallback, body=params, progressText="{} is starting".format(self.name()))
def _addIourcContentToParams(self, params):
"""
@@ -219,17 +216,15 @@ class IOUDevice(VM):
params = {}
if "startup_config" in new_settings:
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del new_settings["startup_config"]
if "private_config" in new_settings:
if new_settings["private_config"] and os.path.isfile(new_settings["private_config"]):
base_config_content = self._readBaseConfig(new_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del new_settings["private_config"]
for name, value in new_settings.items():
@@ -247,10 +242,8 @@ class IOUDevice(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -401,24 +394,14 @@ class IOUDevice(VM):
:returns: representation of the node (dictionary)
"""
iou = {"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()}
iou = super().dump()
iou["vm_id"] = self._vm_id
# add the properties
for name, value in self._settings.items():
if value is not None and value != "":
iou["properties"][name] = value
# add the ports
if self._ports:
ports = iou["ports"] = []
for port in self._ports:
ports.append(port.dump())
return iou
def load(self, node_info):
@@ -429,6 +412,8 @@ class IOUDevice(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("iou_id")
if not vm_id:
@@ -460,30 +445,6 @@ class IOUDevice(VM):
self.loaded_signal.connect(self._updatePortSettings)
self.setup(path, name, vm_id, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("IOU device {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def saveConfig(self):
"""
Save the configs

View File

@@ -171,6 +171,14 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
self.uiGeneralgroupBox.hide()
if not node:
# these are template settings
# rename the label from "Name" to "Template name"
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the startup-config and private-config
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
self.uiPrivateConfigLineEdit.setText(settings["private_config"])
@@ -184,6 +192,8 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiStartupConfigLabel.hide()
self.uiStartupConfigLineEdit.hide()
self.uiStartupConfigToolButton.hide()
@@ -244,12 +254,21 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
del settings["console"]
if not node:
# these are template settings
# save the default name format
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
# save the startup-config
startup_config = self.uiStartupConfigLineEdit.text().strip()
if not startup_config:
settings["startup_config"] = ""
elif startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
if self._configFileValid(startup_config):
settings["startup_config"] = startup_config
else:
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
@@ -259,7 +278,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
if not private_config:
settings["private_config"] = ""
elif private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
if self._configFileValid(private_config):
settings["private_config"] = private_config
else:
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
@@ -296,3 +315,11 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
settings["ethernet_adapters"] = ethernet_adapters
settings["serial_adapters"] = serial_adapters
def _configFileValid(self, path):
"""
Return true if it's a valid configuration file
"""
if not os.path.isabs(path):
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
return os.access(path, os.R_OK)

View File

@@ -70,7 +70,8 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["Name:", iou_device["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", iou_device["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", iou_device["default_name_format"]])
QtWidgets.QTreeWidgetItem(section_item, ["Server:", iou_device["server"]])
QtWidgets.QTreeWidgetItem(section_item, ["Image:", iou_device["image"]])
if iou_device["startup_config"]:
@@ -179,7 +180,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
"""
iou_module = IOU.instance()
self._iou_devices = copy.deepcopy(iou_module.iouDevices())
self._iou_devices = copy.deepcopy(iou_module.VMs())
self._items.clear()
for key, iou_device in self._iou_devices.items():
@@ -199,7 +200,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
"""
# self._iouImageSaveSlot()
IOU.instance().setIOUDevices(self._iou_devices)
IOU.instance().setVMs(self._iou_devices)
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():

View File

@@ -36,6 +36,7 @@ if not sys.platform.startswith("linux"):
IOU_DEVICE_SETTINGS = {
"name": "",
"default_name_format": "IOU{0}",
"path": "",
"symbol": ":/symbols/multilayer_switch.svg",
"category": Node.routers,

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>461</width>
<height>520</height>
<width>569</width>
<height>564</height>
</rect>
</property>
<property name="windowTitle">
@@ -43,14 +43,14 @@
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
@@ -67,24 +67,24 @@
</item>
</layout>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="3" column="0">
<item row="5" column="0">
<widget class="QLabel" name="uiIOUImageLabel">
<property name="text">
<string>IOU image path:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="uiIOUImageLineEdit"/>
@@ -101,14 +101,14 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="uiStartupConfigLabel">
<property name="text">
<string>Startup-config:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLineEdit" name="uiStartupConfigLineEdit"/>
@@ -125,14 +125,14 @@
</item>
</layout>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QLabel" name="uiPrivateConfigLabel">
<property name="text">
<string>Private-config:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
@@ -149,20 +149,34 @@
</item>
</layout>
</item>
<item row="6" column="0">
<item row="8" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<property name="text">
<string>Console port:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="8" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
#
# Created: Wed Jul 15 12:22:33 2015
# by: PyQt5 UI code generator 5.4
# Created: Thu Feb 4 21:08:13 2016
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_iouDeviceConfigPageWidget(object):
def setupUi(self, iouDeviceConfigPageWidget):
iouDeviceConfigPageWidget.setObjectName("iouDeviceConfigPageWidget")
iouDeviceConfigPageWidget.resize(461, 520)
iouDeviceConfigPageWidget.resize(569, 564)
self.verticalLayout = QtWidgets.QVBoxLayout(iouDeviceConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(iouDeviceConfigPageWidget)
@@ -34,7 +34,7 @@ class Ui_iouDeviceConfigPageWidget(object):
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout.addWidget(self.uiSymbolLabel, 1, 0, 1, 1)
self.gridLayout.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
@@ -44,16 +44,16 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout.addLayout(self.horizontalLayout_7, 1, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout_7, 2, 1, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout.addWidget(self.uiCategoryLabel, 2, 0, 1, 1)
self.gridLayout.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralgroupBox)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout.addWidget(self.uiCategoryComboBox, 2, 1, 1, 1)
self.gridLayout.addWidget(self.uiCategoryComboBox, 3, 1, 1, 1)
self.uiIOUImageLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiIOUImageLabel.setObjectName("uiIOUImageLabel")
self.gridLayout.addWidget(self.uiIOUImageLabel, 3, 0, 1, 1)
self.gridLayout.addWidget(self.uiIOUImageLabel, 5, 0, 1, 1)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiIOUImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
@@ -63,10 +63,10 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiIOUImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiIOUImageToolButton.setObjectName("uiIOUImageToolButton")
self.horizontalLayout_5.addWidget(self.uiIOUImageToolButton)
self.gridLayout.addLayout(self.horizontalLayout_5, 3, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout_5, 5, 1, 1, 1)
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
self.gridLayout.addWidget(self.uiStartupConfigLabel, 4, 0, 1, 1)
self.gridLayout.addWidget(self.uiStartupConfigLabel, 6, 0, 1, 1)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
@@ -76,10 +76,10 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
self.gridLayout.addLayout(self.horizontalLayout_4, 4, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout_4, 6, 1, 1, 1)
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
self.gridLayout.addWidget(self.uiPrivateConfigLabel, 5, 0, 1, 1)
self.gridLayout.addWidget(self.uiPrivateConfigLabel, 7, 0, 1, 1)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.uiPrivateConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
@@ -89,14 +89,21 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiPrivateConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiPrivateConfigToolButton.setObjectName("uiPrivateConfigToolButton")
self.horizontalLayout_6.addWidget(self.uiPrivateConfigToolButton)
self.gridLayout.addLayout(self.horizontalLayout_6, 5, 1, 1, 1)
self.gridLayout.addLayout(self.horizontalLayout_6, 7, 1, 1, 1)
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
self.gridLayout.addWidget(self.uiConsolePortLabel, 6, 0, 1, 1)
self.gridLayout.addWidget(self.uiConsolePortLabel, 8, 0, 1, 1)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralgroupBox)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout.addWidget(self.uiConsolePortSpinBox, 6, 1, 1, 1)
self.gridLayout.addWidget(self.uiConsolePortSpinBox, 8, 1, 1, 1)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
self.uiDefaultNameFormatLineEdit.setText("")
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
self.verticalLayout_2.addWidget(self.uiGeneralgroupBox)
self.uiOtherSettingsGroupBox = QtWidgets.QGroupBox(self.tab)
self.uiOtherSettingsGroupBox.setObjectName("uiOtherSettingsGroupBox")
@@ -206,6 +213,7 @@ class Ui_iouDeviceConfigPageWidget(object):
self.uiPrivateConfigLabel.setText(_translate("iouDeviceConfigPageWidget", "Private-config:"))
self.uiPrivateConfigToolButton.setText(_translate("iouDeviceConfigPageWidget", "&Browse..."))
self.uiConsolePortLabel.setText(_translate("iouDeviceConfigPageWidget", "Console port:"))
self.uiDefaultNameFormatLabel.setText(_translate("iouDeviceConfigPageWidget", "Default name format:"))
self.uiOtherSettingsGroupBox.setTitle(_translate("iouDeviceConfigPageWidget", "Other settings"))
self.uiL1KeepalivesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Enable layer 1 keepalive messages (testing only)"))
self.uiDefaultValuesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Use default IOU values for memories"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:33 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_IOUDevicePreferencesPageWidget(object):
def setupUi(self, IOUDevicePreferencesPageWidget):
IOUDevicePreferencesPageWidget.setObjectName("IOUDevicePreferencesPageWidget")
IOUDevicePreferencesPageWidget.resize(505, 350)
@@ -74,4 +75,3 @@ class Ui_IOUDevicePreferencesPageWidget(object):
self.uiDeleteIOUDevicePushButton.setText(_translate("IOUDevicePreferencesPageWidget", "&Delete"))
self.uiIOUDeviceInfoTreeWidget.headerItem().setText(0, _translate("IOUDevicePreferencesPageWidget", "1"))
self.uiIOUDeviceInfoTreeWidget.headerItem().setText(1, _translate("IOUDevicePreferencesPageWidget", "2"))

View File

@@ -29,11 +29,11 @@
<property name="title">
<string>Server type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="text">
<string>Remote</string>
<string>Run the IOU on a remote computers</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -43,30 +43,17 @@
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="text">
<string>GNS3 VM</string>
<string>Run the IOU on the GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Local</string>
<string>Run the IOU on your local computer</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_wizard.ui'
#
# Created: Wed Jul 15 12:22:33 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,20 +19,18 @@ class Ui_IOUDeviceWizard(object):
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setChecked(True)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.horizontalLayout.addWidget(self.uiVMRadioButton)
self.verticalLayout.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.verticalLayout.addWidget(self.uiLocalRadioButton)
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
@@ -87,8 +84,8 @@ class Ui_IOUDeviceWizard(object):
self.uiNewImageRadioButton.setChecked(False)
self.uiNewImageRadioButton.setObjectName("uiNewImageRadioButton")
self.horizontalLayout_2.addWidget(self.uiNewImageRadioButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
self.formLayout_8 = QtWidgets.QFormLayout()
self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
@@ -128,9 +125,9 @@ class Ui_IOUDeviceWizard(object):
self.uiServerWizardPage.setTitle(_translate("IOUDeviceWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("IOUDeviceWizard", "Please choose a server type to run your new IOU device."))
self.uiServerTypeGroupBox.setTitle(_translate("IOUDeviceWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("IOUDeviceWizard", "Remote"))
self.uiVMRadioButton.setText(_translate("IOUDeviceWizard", "GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("IOUDeviceWizard", "Local"))
self.uiRemoteRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on a remote computers"))
self.uiVMRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on the GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on your local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("IOUDeviceWizard", "Remote servers"))
self.uiLoadBalanceCheckBox.setText(_translate("IOUDeviceWizard", "Load balance across all available remote servers"))
self.uiRemoteServersLabel.setText(_translate("IOUDeviceWizard", "Run on server:"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:34 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_IOUPreferencesPageWidget(object):
def setupUi(self, IOUPreferencesPageWidget):
IOUPreferencesPageWidget.setObjectName("IOUPreferencesPageWidget")
IOUPreferencesPageWidget.resize(400, 300)
@@ -105,4 +106,3 @@ class Ui_IOUPreferencesPageWidget(object):
self.uiIOURCPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("IOUPreferencesPageWidget", "General settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("IOUPreferencesPageWidget", "Restore defaults"))

View File

@@ -22,7 +22,6 @@ Base class (interface) for modules.
from ..qt import QtCore
from ..local_config import LocalConfig
import logging
log = logging.getLogger(__name__)
@@ -40,7 +39,6 @@ class Module(QtCore.QObject):
super().__init__()
LocalConfig.instance().config_changed_signal.connect(self.configChangedSlot)
def configChangedSlot(self):
"""
Call when the configuration file has changed

View File

@@ -107,7 +107,7 @@ class Qemu(Module):
self._settings["vms"] = list(self._qemu_vms.values())
self._saveSettings()
def qemuVMs(self):
def VMs(self):
"""
Returns QEMU VMs settings.
@@ -116,11 +116,11 @@ class Qemu(Module):
return self._qemu_vms
def setQemuVMs(self, new_qemu_vms):
def setVMs(self, new_qemu_vms):
"""
Sets QEMU VM settings.
:param new_iou_images: IOS images settings (dictionary)
:param new_qemu_vms: Qemu images settings (dictionary)
"""
self._qemu_vms = new_qemu_vms.copy()
@@ -216,6 +216,13 @@ class Qemu(Module):
else:
vm = selected_vms[0]
linked_base = self._qemu_vms[vm]["linked_base"]
if not linked_base:
for other_node in self._nodes:
if other_node.settings()["name"] == self._qemu_vms[vm]["name"] and \
(self._qemu_vms[vm]["server"] == "local" and other_node.server().isLocal() or self._qemu_vms[vm]["server"] == other_node.server().host):
raise ModuleError("Sorry a Qemu VM without the linked base setting enabled can only be used once in your topology")
vm_settings = {}
for setting_name, value in self._qemu_vms[vm].items():
if setting_name in node.settings() and value != "" and value is not None:
@@ -226,12 +233,22 @@ class Qemu(Module):
port_name_format = self._qemu_vms[vm]["port_name_format"]
port_segment_size = self._qemu_vms[vm]["port_segment_size"]
first_port_name = self._qemu_vms[vm]["first_port_name"]
default_name_format = QEMU_VM_SETTINGS["default_name_format"]
if self._qemu_vms[vm]["default_name_format"]:
default_name_format = self._qemu_vms[vm]["default_name_format"]
if linked_base:
default_name_format = default_name_format.replace('{name}', name)
name = None
node.setup(qemu_path,
name=name,
port_name_format=port_name_format,
port_segment_size=port_segment_size,
first_port_name=first_port_name,
linked_clone=linked_base,
additional_settings=vm_settings,
base_name=name)
default_name_format=default_name_format)
def reset(self):
"""
@@ -241,15 +258,19 @@ class Qemu(Module):
log.info("QEMU module reset")
self._nodes.clear()
def getQemuBinariesFromServer(self, server, callback):
def getQemuBinariesFromServer(self, server, callback, archs=None):
"""
Gets the QEMU binaries list from a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
"""
server.get("/qemu/binaries", callback)
request_body = None
if archs is not None:
request_body = {"archs": archs}
server.get("/qemu/binaries", callback, body=request_body)
def getQemuImgBinariesFromServer(self, server, callback):
"""
@@ -261,6 +282,16 @@ class Qemu(Module):
server.get(r"/qemu/img-binaries", callback)
def getQemuCapabilitiesFromServer(self, server, callback):
"""
Gets the capabilities of Qemu at a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
"""
server.get(r"/qemu/capabilities", callback)
def createDiskImage(self, server, callback, options):
"""
Create a disk image on the remote server
@@ -323,6 +354,11 @@ class Qemu(Module):
from .pages.qemu_vm_preferences_page import QemuVMPreferencesPage
return [QemuPreferencesPage, QemuVMPreferencesPage]
@staticmethod
def vmConfigurationPage():
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
return QemuVMConfigurationPage
@staticmethod
def instance():
"""

View File

@@ -164,11 +164,14 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
cluster_size = self.uiQcow2ClusterSizeComboBox.currentText()
if not '<default>' == cluster_size:
options["cluster_size"] = cluster_size
if cluster_size.endswith('k'):
options["cluster_size"] = int(cluster_size[:-1]) * 1024
else:
options["cluster_size"] = int(cluster_size)
refcount_bits = self.uiRefcountEntrySizeComboBox.currentText()
if not '<default>' == refcount_bits:
options["refcount_bits"] = refcount_bits
options["refcount_bits"] = int(refcount_bits)
options["lazy_refcounts"] = 'on' if QtCore.Qt.Checked == self.uiLazyRefcountsCheckBox.checkState() else 'off'

View File

@@ -31,6 +31,7 @@ from ..ui.qemu_vm_wizard_ui import Ui_QemuVMWizard
from ..pages.qemu_vm_configuration_page import QemuVMConfigurationPage
from .qemu_image_wizard import QemuImageWizard
class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
"""
@@ -70,7 +71,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
:param vm_type: type of VM
"""
self.uiASADeprecatedWarningLabel.hide()
if vm_type == "IOSv":
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/iosv_virl.svg"))
self.uiNameLineEdit.setText("vIOS")
@@ -86,7 +86,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
elif vm_type == "ASA 8.4(2)":
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/asa.svg"))
self.uiNameLineEdit.setText("ASA")
self.uiASADeprecatedWarningLabel.show()
elif vm_type == "IDS":
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/ids.svg"))
self.uiNameLineEdit.setText("IDS")
@@ -108,7 +107,10 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
if not self.uiQemuListComboBox.count():
QtWidgets.QMessageBox.critical(self, "QEMU binaries", "Sorry, no QEMU binary has been found. Please make sure QEMU is installed before continuing")
return False
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
if (sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
QtWidgets.QMessageBox.warning(self, "Qemu binaries", "This version of qemu is obsolete and provided only for compatibility with old GNS3 versions.\nPlease use Qemu in the GNS3 VM for full Qemu support.")
return True
def initializePage(self, page_id):
@@ -203,9 +205,11 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
elif self.uiTypeComboBox.currentText() == "ASA 8.4(2)":
settings["adapters"] = 4
settings["initrd"] = self.uiInitrdImageLineEdit.text()
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text()
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt"
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
settings["options"] = "-no-kvm -icount auto -hdachs 980,16,32"
if not sys.platform.startswith("darwin"):
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
settings["process_priority"] = "low"
@@ -224,7 +228,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
if "options" not in settings:
settings["options"] = ""
if server == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.13.0\qemu-system-i386w.exe")) or \
if server == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.11.0\qemu.exe")) or \
(sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
settings["options"] += " -vga none -vnc none"
settings["legacy_networking"] = True

View File

@@ -22,12 +22,11 @@ Configuration page for QEMU VMs.
import os
import re
from functools import partial
from collections import OrderedDict
from gns3.modules.qemu.dialogs.qemu_image_wizard import QemuImageWizard
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.qt import QtGui, QtCore, QtWidgets, qpartial
from gns3.servers import Servers
from gns3.modules.module_error import ModuleError
from gns3.dialogs.node_properties_dialog import ConfigurationError
@@ -53,6 +52,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiBootPriorityComboBox.addItem("HDD", "c")
self.uiBootPriorityComboBox.addItem("CD/DVD-ROM", "d")
self.uiBootPriorityComboBox.addItem("Network", "n")
self.uiBootPriorityComboBox.addItem("HDD or Network", "cn")
self.uiBootPriorityComboBox.addItem("HDD or CD/DVD-ROM", "cd")
self.uiHdaDiskImageToolButton.clicked.connect(self._hdaDiskImageBrowserSlot)
self.uiHdbDiskImageToolButton.clicked.connect(self._hdbDiskImageBrowserSlot)
@@ -268,7 +270,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
if qemu_path and "/" not in qemu_path and "\\" not in qemu_path:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu_path), qemu_path)
index = self.uiQemuListComboBox.findData("{path}".format(path=qemu_path))
if index != -1:
self.uiQemuListComboBox.setCurrentIndex(index)
@@ -310,12 +311,15 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
self._server = Servers.instance().getServerFromString(settings["server"])
callback = partial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
try:
Qemu.instance().getQemuBinariesFromServer(self._server, callback)
except ModuleError as e:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
self.uiQemuListComboBox.clear()
if self._server is None:
QtWidgets.QMessageBox.warning(self, "Qemu", "Server {} is not running, cannot retrieve the QEMU binaries list".format(settings["server"]))
else:
callback = qpartial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
try:
Qemu.instance().getQemuBinariesFromServer(self._server, callback)
except ModuleError as e:
QtWidgets.QMessageBox.critical(self, "Qemu", "Error while getting the QEMU binaries list: {}".format(e))
self.uiQemuListComboBox.clear()
if not group:
# set the device name
@@ -325,6 +329,12 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
else:
self.uiConsolePortLabel.hide()
self.uiConsolePortSpinBox.hide()
if "linked_base" in settings:
self.uiBaseVMCheckBox.setChecked(settings["linked_base"])
else:
self.uiBaseVMCheckBox.hide()
self.uiHdaDiskImageLineEdit.setText(settings["hda_disk_image"])
self.uiHdbDiskImageLineEdit.setText(settings["hdb_disk_image"])
self.uiHdcDiskImageLineEdit.setText(settings["hdc_disk_image"])
@@ -351,6 +361,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiKernelImageToolButton.hide()
if not node:
# these are template settings
# rename the label from "Name" to "Template name"
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the symbol
self.uiSymbolLineEdit.setText(settings["symbol"])
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
@@ -364,6 +382,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiSymbolLabel.hide()
self.uiSymbolLineEdit.hide()
self.uiSymbolToolButton.hide()
@@ -437,6 +457,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
if "console" in settings:
settings["console"] = self.uiConsolePortSpinBox.value()
if "linked_base" in settings:
settings["linked_base"] = self.uiBaseVMCheckBox.isChecked()
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
settings["hdb_disk_image"] = self.uiHdbDiskImageLineEdit.text().strip()
settings["hdc_disk_image"] = self.uiHdcDiskImageLineEdit.text().strip()
@@ -479,6 +502,15 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
del settings["mac_address"]
if not node:
# these are template settings
# save the default name format
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
symbol_path = self.uiSymbolLineEdit.text()
pixmap = QtGui.QPixmap(symbol_path)
if pixmap.isNull():
@@ -488,14 +520,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size

View File

@@ -19,10 +19,8 @@
Configuration page for QEMU VM preferences.
"""
import ntpath
import os
import copy
import sys
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.main_window import MainWindow
@@ -69,11 +67,14 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["VM name:", qemu_vm["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", qemu_vm["name"]])
if qemu_vm["linked_base"]:
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", qemu_vm["default_name_format"]])
QtWidgets.QTreeWidgetItem(section_item, ["Server:", qemu_vm["server"]])
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", qemu_vm["console_type"]])
QtWidgets.QTreeWidgetItem(section_item, ["CPUs:", str(qemu_vm["cpus"])])
QtWidgets.QTreeWidgetItem(section_item, ["Memory:", "{} MB".format(qemu_vm["ram"])])
QtWidgets.QTreeWidgetItem(section_item, ["Linked base VM:", "{}".format(qemu_vm["linked_base"])])
if qemu_vm["qemu_path"]:
QtWidgets.QTreeWidgetItem(section_item, ["QEMU binary:", os.path.basename(qemu_vm["qemu_path"])])
@@ -222,7 +223,7 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
"""
qemu_module = Qemu.instance()
self._qemu_vms = copy.deepcopy(qemu_module.qemuVMs())
self._qemu_vms = copy.deepcopy(qemu_module.VMs())
self._items.clear()
for key, qemu_vm in self._qemu_vms.items():
@@ -241,4 +242,4 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
Saves the QEMU VM preferences.
"""
Qemu.instance().setQemuVMs(self._qemu_vms)
Qemu.instance().setVMs(self._qemu_vms)

View File

@@ -48,8 +48,10 @@ class QemuVM(VM):
self._port_name_format = None
self._port_segment_size = 0
self._first_port_name = None
self._linked_clone = True
self._settings = {"name": "",
"usage": "",
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
@@ -97,7 +99,14 @@ class QemuVM(VM):
if self._first_port_name and adapter_number == 0:
port_name = self._first_port_name
else:
port_name = self._port_name_format.format(interface_number, segment_number)
port_name = self._port_name_format.format(
interface_number,
segment_number,
port0=interface_number,
port1=1 + interface_number,
segment0=segment_number,
segment1=1 + segment_number
)
interface_number += 1
if self._port_segment_size and interface_number % self._port_segment_size == 0:
segment_number += 1
@@ -109,8 +118,8 @@ class QemuVM(VM):
self._ports.append(new_port)
log.debug("Adapter {} with port {} has been added".format(adapter_number, port_name))
def setup(self, qemu_path, name=None, vm_id=None, port_name_format="Ethernet{0}",
port_segment_size=0, first_port_name="", additional_settings={}, base_name=None):
def setup(self, qemu_path, name=None, vm_id=None, port_name_format="Ethernet{0}", port_segment_size=0,
first_port_name="", linked_clone=True, additional_settings={}, default_name_format=None):
"""
Setups this QEMU VM.
@@ -119,16 +128,19 @@ class QemuVM(VM):
"""
# let's create a unique name if none has been chosen
if not name:
name = self.allocateName(base_name + "-")
if not name and linked_clone:
name = self.allocateName(default_name_format)
if not name:
self.error_signal.emit(self.id(), "could not allocate a name for this QEMU VM")
return
self.setName(name)
self._settings["name"] = name
self._linked_clone = linked_clone
params = {"name": name,
"qemu_path": qemu_path}
"qemu_path": qemu_path,
"linked_clone": linked_clone}
if vm_id:
params["vm_id"] = vm_id
@@ -137,7 +149,7 @@ class QemuVM(VM):
self._port_segment_size = port_segment_size
self._first_port_name = first_port_name
params.update(additional_settings)
self.httpPost("/qemu/vms", self._setupCallback, body=params)
self.httpPost("/qemu/vms", self._setupCallback, body=params, progressText="Creating {}".format(name))
def _setupCallback(self, result, error=False, **kwargs):
"""
@@ -161,42 +173,13 @@ class QemuVM(VM):
self.created_signal.emit(self.id())
self._module.addNode(self)
for image_field in ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "initrd", "kernel_image"]:
for image_field in ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "initrd", "kernel_image", "cdrom_image"]:
if image_field in result and result[image_field] is not None and result[image_field] != "":
# The image is missing on remote server
field = "{}_md5sum".format(image_field)
if field not in result or result[field] is None or len(result[field]) == 0:
ImageManager.instance().addMissingImage(result[image_field], self._server, "QEMU")
def delete(self):
"""
Deletes this QEMU VM instance.
"""
log.debug("QEMU VM instance {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._vm_id:
self.httpDelete("/qemu/vms/{vm_id}".format(vm_id=self._vm_id), self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False, **kwargs):
"""
Callback for delete.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
def update(self, new_settings):
"""
Updates the settings for this QEMU VM.
@@ -213,9 +196,6 @@ class QemuVM(VM):
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.httpPut("/qemu/vms/{vm_id}".format(vm_id=self._vm_id), self._updateCallback, body=params)
@@ -227,10 +207,8 @@ class QemuVM(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
if not super()._updateCallback(result, error=error, **kwargs):
return False
updated = False
nb_adapters_changed = False
@@ -316,13 +294,10 @@ class QemuVM(VM):
:returns: representation of the node (dictionary)
"""
qemu_vm = {"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"port_name_format": self._port_name_format,
"server_id": self._server.id()}
qemu_vm = super().dump()
qemu_vm["vm_id"] = self._vm_id
qemu_vm["linked_clone"] = self._linked_clone
qemu_vm["port_name_format"] = self._port_name_format
if self._port_segment_size:
qemu_vm["port_segment_size"] = self._port_segment_size
@@ -334,12 +309,6 @@ class QemuVM(VM):
if value is not None and value != "":
qemu_vm["properties"][name] = value
# add the ports
if self._ports:
ports = qemu_vm["ports"] = []
for port in self._ports:
ports.append(port.dump())
return qemu_vm
def info(self):
@@ -375,6 +344,9 @@ class QemuVM(VM):
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
port_description=port.description())
if "usage" in self._settings and len(self._settings["usage"]) > 0:
info += " Usage: {}\n".format(self._settings["usage"])
return info + port_info
def load(self, node_info):
@@ -385,10 +357,12 @@ class QemuVM(VM):
:param node_info: representation of the node (dictionary)
"""
super().load(node_info)
# for backward compatibility
vm_id = node_info.get("qemu_id")
if not vm_id:
vm_id = node_info.get("vm_id")
linked_clone = node_info.get("linked_clone", True)
port_name_format = node_info.get("port_name_format", "Ethernet{0}")
port_segment_size = node_info.get("port_segment_size", 0)
first_port_name = node_info.get("first_port_name", "")
@@ -402,35 +376,7 @@ class QemuVM(VM):
qemu_path = vm_settings.pop("qemu_path")
log.info("QEMU VM {} is loading".format(name))
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(qemu_path, name, vm_id, port_name_format, port_segment_size, first_port_name, vm_settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.loaded_signal.disconnect(self._updatePortSettings)
# assign the correct names and IDs to the ports
if "ports" in self._node_info:
ports = self._node_info["ports"]
for topology_port in ports:
for port in self._ports:
adapter_number = topology_port.get("adapter_number", topology_port["port_number"])
if adapter_number == port.adapterNumber():
port.setName(topology_port["name"])
port.setId(topology_port["id"])
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("QEMU VM {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
self.setup(qemu_path, name, vm_id, port_name_format, port_segment_size, first_port_name, linked_clone, vm_settings)
def name(self):
"""

View File

@@ -28,6 +28,8 @@ QEMU_SETTINGS = {
QEMU_VM_SETTINGS = {
"name": "",
"default_name_format": "{name}-{0}",
"usage": "",
"symbol": ":/symbols/qemu_guest.svg",
"category": Node.end_devices,
"port_name_format": "Ethernet{0}",
@@ -59,5 +61,6 @@ QEMU_VM_SETTINGS = {
"kernel_image": "",
"initrd": "",
"kernel_command_line": "",
"linked_base": True,
"server": "local"
}

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
#
# Created: Wed Jul 15 12:22:34 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuImageWizard(object):
def setupUi(self, QemuImageWizard):
QemuImageWizard.setObjectName("QemuImageWizard")
QemuImageWizard.resize(535, 369)
@@ -318,11 +319,11 @@ class Ui_QemuImageWizard(object):
self.uiFormatQcow2Radio.setToolTip(_translate("QemuImageWizard", "Qcow2 is the current Qemu format, supporting many special features."))
self.uiFormatQcowRadio.setToolTip(_translate("QemuImageWizard", "Qcow is a legacy Qemu format that is also supported by VirtualBox."))
self.uiFormatVhdRadio.setToolTip(_translate("QemuImageWizard", "VHD is the format used by Microsoft VirtualPC, and is also supported by Qemu and VirtualBox.\n"
"On Windows 7 and above, it can be mounted on the host PC."))
"On Windows 7 and above, it can be mounted on the host PC."))
self.uiFormatVdiRadio.setToolTip(_translate("QemuImageWizard", "VDI is the native format of VirtualBox"))
self.uiFormatVmdkRadio.setToolTip(_translate("QemuImageWizard", "VMDK is the native format for VMware and is also supported by Qemu and VirtualBox."))
self.uiFormatRawRadio.setToolTip(_translate("QemuImageWizard", "Raw image files represent the actual data on the image, with zero special features.\n"
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
self.uiFormatRawRadio.setText(_translate("QemuImageWizard", "Raw"))
self.uiQcow2OptionsWizardPage.setTitle(_translate("QemuImageWizard", "Qcow2 options"))
self.uiSizeOptionsGroupBox.setTitle(_translate("QemuImageWizard", "Size options"))
@@ -330,12 +331,12 @@ class Ui_QemuImageWizard(object):
self.uiQcow2PreallocationOffRadio.setToolTip(_translate("QemuImageWizard", "The file only takes as much space from the host as needed. The VM will still see the full capacity you specify."))
self.uiQcow2PreallocationOffRadio.setText(_translate("QemuImageWizard", "off"))
self.uiQcow2PreallocationMetadataRadio.setToolTip(_translate("QemuImageWizard", "Same as \"off\", but preallocates enough space to hold any potenial metadata for the HDD.\n"
"This improves performance when the image file needs to grow."))
"This improves performance when the image file needs to grow."))
self.uiQcow2PreallocationMetadataRadio.setText(_translate("QemuImageWizard", "metadata"))
self.uiQcow2PreallocationFallocRadio.setToolTip(_translate("QemuImageWizard", "Same as \"full\", but uses C\'s posix_fallocate() if available on the host, instead of zero filling the file."))
self.uiQcow2PreallocationFallocRadio.setText(_translate("QemuImageWizard", "falloc"))
self.uiQcow2PreallocationFullRadio.setToolTip(_translate("QemuImageWizard", "The file will start off at the full size you specify.\n"
"Free space will be zero filled."))
"Free space will be zero filled."))
self.uiQcow2PreallocationFullRadio.setText(_translate("QemuImageWizard", "full"))
self.uiClusterSizeLabel.setText(_translate("QemuImageWizard", "Cluster size:"))
self.uiQcow2ClusterSizeComboBox.setItemText(0, _translate("QemuImageWizard", "<default>"))
@@ -395,4 +396,3 @@ class Ui_QemuImageWizard(object):
self.uiLocationBrowseToolButton.setText(_translate("QemuImageWizard", "Browse"))
self.uiSizeLabel.setText(_translate("QemuImageWizard", "Disk size:"))
self.uiSizeSpinBox.setSuffix(_translate("QemuImageWizard", " MiB"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:34 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuPreferencesPageWidget(object):
def setupUi(self, QemuPreferencesPageWidget):
QemuPreferencesPageWidget.setObjectName("QemuPreferencesPageWidget")
QemuPreferencesPageWidget.resize(366, 336)
@@ -53,4 +54,3 @@ class Ui_QemuPreferencesPageWidget(object):
self.uiKVMAccelerationCheckBox.setText(_translate("QemuPreferencesPageWidget", "Enable KVM acceleration"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiServerSettingsTabWidget), _translate("QemuPreferencesPageWidget", "General settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("QemuPreferencesPageWidget", "Restore defaults"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>611</width>
<height>524</height>
<width>574</width>
<height>523</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,57 +24,6 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="2" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QComboBox" name="uiQemuListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="11" column="2">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>94</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="uiQemuListLabel">
<property name="text">
<string>Qemu binary:</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="uiConsolePortLabel">
<property name="text">
<string>Console port:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiRamLabel">
<property name="text">
@@ -82,23 +31,7 @@
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="uiRamSpinBox">
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>32</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="2" column="2">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
@@ -115,34 +48,36 @@
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>VM name:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<item row="3" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="uiConsoleTypeLabel">
<property name="text">
<string>Console type:</string>
<item row="4" column="1">
<widget class="QSpinBox" name="uiRamSpinBox">
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>32</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="8" column="2">
<item row="2" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="8" column="1">
<widget class="QComboBox" name="uiConsoleTypeComboBox">
<item>
<property name="text">
@@ -156,8 +91,12 @@
</item>
</widget>
</item>
<item row="7" column="2">
<widget class="QComboBox" name="uiBootPriorityComboBox"/>
<item row="8" column="0">
<widget class="QLabel" name="uiConsoleTypeLabel">
<property name="text">
<string>Console type:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="uiBootPriorityLabel">
@@ -166,14 +105,28 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiCPULabel">
<item row="6" column="0">
<widget class="QLabel" name="uiQemuListLabel">
<property name="text">
<string>vCPUs:</string>
<string>Qemu binary:</string>
</property>
</widget>
</item>
<item row="5" column="2">
<item row="9" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<property name="text">
<string>Console port:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="uiCPUSpinBox">
<property name="minimum">
<number>1</number>
@@ -183,6 +136,63 @@
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="uiBootPriorityComboBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="uiQemuListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="10" column="1">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>263</width>
<height>94</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiCPULabel">
<property name="text">
<string>vCPUs:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiHddTab">
@@ -523,6 +533,9 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>
@@ -745,10 +758,20 @@
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="1">
<widget class="QLineEdit" name="uiQemuOptionsLineEdit"/>
</item>
<item row="1" column="0" colspan="3">
<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>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="uiACPIShutdownCheckBox">
<property name="text">
<string>Enable ACPI shutdown (experimental)</string>
@@ -759,6 +782,7 @@
<zorder>uiQemuOptionsLineEdit</zorder>
<zorder>uiQemuOptionsLabel</zorder>
<zorder>uiACPIShutdownCheckBox</zorder>
<zorder>uiBaseVMCheckBox</zorder>
</widget>
</item>
<item>

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: Wed Aug 5 15:49:25 2015
# Created: Thu Feb 4 21:54:18 2016
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(611, 524)
QemuVMConfigPageWidget.resize(574, 523)
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
@@ -21,38 +21,9 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiGeneralSettingsTab.setObjectName("uiGeneralSettingsTab")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiGeneralSettingsTab)
self.gridLayout_4.setObjectName("gridLayout_4")
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout_4.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 2, 1, 1)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout_4.addWidget(self.uiConsolePortSpinBox, 9, 2, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_4.addItem(spacerItem, 11, 2, 1, 1)
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 2)
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
self.gridLayout_4.addWidget(self.uiConsolePortLabel, 9, 0, 1, 2)
self.uiRamLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiRamLabel.setObjectName("uiRamLabel")
self.gridLayout_4.addWidget(self.uiRamLabel, 4, 0, 1, 1)
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiRamSpinBox.setMinimum(32)
self.uiRamSpinBox.setMaximum(65535)
self.uiRamSpinBox.setProperty("value", 256)
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 2, 1, 1)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
@@ -62,41 +33,76 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
self.gridLayout_4.addLayout(self.horizontalLayout_7, 2, 2, 1, 1)
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout_4.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout_4.addWidget(self.uiNameLineEdit, 0, 2, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout_4.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
self.gridLayout_4.addLayout(self.horizontalLayout_7, 2, 1, 1, 1)
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
self.gridLayout_4.addWidget(self.uiCategoryComboBox, 3, 2, 1, 1)
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
self.gridLayout_4.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
self.gridLayout_4.addWidget(self.uiCategoryComboBox, 3, 1, 1, 1)
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiRamSpinBox.setMinimum(32)
self.uiRamSpinBox.setMaximum(65535)
self.uiRamSpinBox.setProperty("value", 256)
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 1, 1, 1)
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
self.gridLayout_4.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout_4.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiConsoleTypeComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiConsoleTypeComboBox.setObjectName("uiConsoleTypeComboBox")
self.uiConsoleTypeComboBox.addItem("")
self.uiConsoleTypeComboBox.addItem("")
self.gridLayout_4.addWidget(self.uiConsoleTypeComboBox, 8, 2, 1, 1)
self.uiBootPriorityComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiBootPriorityComboBox.setObjectName("uiBootPriorityComboBox")
self.gridLayout_4.addWidget(self.uiBootPriorityComboBox, 7, 2, 1, 1)
self.gridLayout_4.addWidget(self.uiConsoleTypeComboBox, 8, 1, 1, 1)
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
self.gridLayout_4.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
self.uiBootPriorityLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiBootPriorityLabel.setObjectName("uiBootPriorityLabel")
self.gridLayout_4.addWidget(self.uiBootPriorityLabel, 7, 0, 1, 1)
self.uiCPULabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiCPULabel.setObjectName("uiCPULabel")
self.gridLayout_4.addWidget(self.uiCPULabel, 5, 0, 1, 1)
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 1)
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
self.gridLayout_4.addWidget(self.uiConsolePortLabel, 9, 0, 1, 1)
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
self.gridLayout_4.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
self.uiCPUSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiCPUSpinBox.setMinimum(1)
self.uiCPUSpinBox.setMaximum(255)
self.uiCPUSpinBox.setObjectName("uiCPUSpinBox")
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 2, 1, 1)
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 1, 1, 1)
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
self.uiConsolePortSpinBox.setMaximum(65535)
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
self.gridLayout_4.addWidget(self.uiConsolePortSpinBox, 9, 1, 1, 1)
self.uiBootPriorityComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
self.uiBootPriorityComboBox.setObjectName("uiBootPriorityComboBox")
self.gridLayout_4.addWidget(self.uiBootPriorityComboBox, 7, 1, 1, 1)
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout_4.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 1, 1, 1)
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_4.addItem(spacerItem, 10, 1, 1, 1)
self.uiCPULabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiCPULabel.setObjectName("uiCPULabel")
self.gridLayout_4.addWidget(self.uiCPULabel, 5, 0, 1, 1)
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
self.gridLayout_4.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
self.gridLayout_4.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
self.uiQemutabWidget.addTab(self.uiGeneralSettingsTab, "")
self.uiHddTab = QtWidgets.QWidget()
self.uiHddTab.setObjectName("uiHddTab")
@@ -378,10 +384,14 @@ class Ui_QemuVMConfigPageWidget(object):
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
self.uiQemuOptionsLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiQemuOptionsLineEdit.setObjectName("uiQemuOptionsLineEdit")
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 2, 1, 1)
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 1, 1, 1)
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiBaseVMCheckBox.setEnabled(True)
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 1, 0, 1, 2)
self.uiACPIShutdownCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiACPIShutdownCheckBox.setObjectName("uiACPIShutdownCheckBox")
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 1, 0, 1, 3)
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 2, 0, 1, 2)
self.verticalLayout_2.addWidget(self.groupBox)
spacerItem4 = QtWidgets.QSpacerItem(20, 90, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
@@ -396,19 +406,20 @@ class Ui_QemuVMConfigPageWidget(object):
def retranslateUi(self, QemuVMConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
QemuVMConfigPageWidget.setWindowTitle(_translate("QemuVMConfigPageWidget", "QEMU VM configuration"))
self.uiSymbolLabel.setText(_translate("QemuVMConfigPageWidget", "Symbol:"))
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
self.uiConsolePortLabel.setText(_translate("QemuVMConfigPageWidget", "Console port:"))
self.uiRamLabel.setText(_translate("QemuVMConfigPageWidget", "RAM:"))
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
self.uiSymbolToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
self.uiNameLabel.setText(_translate("QemuVMConfigPageWidget", "VM name:"))
self.uiCategoryLabel.setText(_translate("QemuVMConfigPageWidget", "Category:"))
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
self.uiSymbolLabel.setText(_translate("QemuVMConfigPageWidget", "Symbol:"))
self.uiConsoleTypeComboBox.setItemText(0, _translate("QemuVMConfigPageWidget", "telnet"))
self.uiConsoleTypeComboBox.setItemText(1, _translate("QemuVMConfigPageWidget", "vnc"))
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
self.uiBootPriorityLabel.setText(_translate("QemuVMConfigPageWidget", "Boot priority:"))
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
self.uiConsolePortLabel.setText(_translate("QemuVMConfigPageWidget", "Console port:"))
self.uiCategoryLabel.setText(_translate("QemuVMConfigPageWidget", "Category:"))
self.uiNameLabel.setText(_translate("QemuVMConfigPageWidget", "Name:"))
self.uiCPULabel.setText(_translate("QemuVMConfigPageWidget", "vCPUs:"))
self.uiDefaultNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Default name format:"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiGeneralSettingsTab), _translate("QemuVMConfigPageWidget", "General settings"))
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Primary Master)"))
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
@@ -440,6 +451,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
@@ -462,6 +474,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiProcessPriorityComboBox.setItemText(5, _translate("QemuVMConfigPageWidget", "Very low"))
self.groupBox.setTitle(_translate("QemuVMConfigPageWidget", "Aditional settings"))
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
self.uiACPIShutdownCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable ACPI shutdown (experimental)"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced settings"))

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:34 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMPreferencesPageWidget(object):
def setupUi(self, QemuVMPreferencesPageWidget):
QemuVMPreferencesPageWidget.setObjectName("QemuVMPreferencesPageWidget")
QemuVMPreferencesPageWidget.resize(505, 350)
@@ -74,4 +75,3 @@ class Ui_QemuVMPreferencesPageWidget(object):
self.uiDeleteQemuVMPushButton.setText(_translate("QemuVMPreferencesPageWidget", "&Delete"))
self.uiQemuVMInfoTreeWidget.headerItem().setText(0, _translate("QemuVMPreferencesPageWidget", "1"))
self.uiQemuVMInfoTreeWidget.headerItem().setText(1, _translate("QemuVMPreferencesPageWidget", "2"))

View File

@@ -29,11 +29,11 @@
<property name="title">
<string>Server type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="text">
<string>Remote</string>
<string>Run the Qemu VM on a remote computer</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -43,30 +43,17 @@
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="text">
<string>GNS3 VM</string>
<string>Run the Qemu VM on the GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Local</string>
<string>Run the Qemu VM on your local computer</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
@@ -133,20 +120,6 @@ font: 18pt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiASADeprecatedWarningLabel">
<property name="styleSheet">
<string notr="true">color: red;
font: 18pt;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: The recommended way to run ASA is to use ASAv with VMware.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
#
# Created: Wed Jul 15 12:22:34 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,20 +19,18 @@ class Ui_QemuVMWizard(object):
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setChecked(True)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
self.verticalLayout_5.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.horizontalLayout.addWidget(self.uiVMRadioButton)
self.verticalLayout_5.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.verticalLayout_5.addWidget(self.uiLocalRadioButton)
self.verticalLayout_3.addWidget(self.uiServerTypeGroupBox)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
@@ -67,12 +64,6 @@ class Ui_QemuVMWizard(object):
self.uiOSDeprecatedWarningLabel.setWordWrap(True)
self.uiOSDeprecatedWarningLabel.setObjectName("uiOSDeprecatedWarningLabel")
self.verticalLayout.addWidget(self.uiOSDeprecatedWarningLabel)
self.uiASADeprecatedWarningLabel = QtWidgets.QLabel(self.uiTypeWizardPage)
self.uiASADeprecatedWarningLabel.setStyleSheet("color: red;\n"
"font: 18pt;")
self.uiASADeprecatedWarningLabel.setWordWrap(True)
self.uiASADeprecatedWarningLabel.setObjectName("uiASADeprecatedWarningLabel")
self.verticalLayout.addWidget(self.uiASADeprecatedWarningLabel)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.uiTypeLabel = QtWidgets.QLabel(self.uiTypeWizardPage)
@@ -138,8 +129,8 @@ class Ui_QemuVMWizard(object):
self.uiNewImageRadioButton_2.setChecked(False)
self.uiNewImageRadioButton_2.setObjectName("uiNewImageRadioButton_2")
self.horizontalLayout_3.addWidget(self.uiNewImageRadioButton_2)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem1)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
self.verticalLayout_2.addLayout(self.horizontalLayout_3)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
@@ -184,8 +175,8 @@ class Ui_QemuVMWizard(object):
self.uiNewImageRadioButton_4.setChecked(False)
self.uiNewImageRadioButton_4.setObjectName("uiNewImageRadioButton_4")
self.horizontalLayout_7.addWidget(self.uiNewImageRadioButton_4)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem2)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem1)
self.verticalLayout_4.addLayout(self.horizontalLayout_7)
self.formLayout_2 = QtWidgets.QFormLayout()
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
@@ -223,8 +214,8 @@ class Ui_QemuVMWizard(object):
self.horizontalLayout_14.addWidget(self.uiKernelImageToolButton)
self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_14)
self.verticalLayout_4.addLayout(self.formLayout_2)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_4.addItem(spacerItem3)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_4.addItem(spacerItem2)
self.horizontalLayout_10.addWidget(self.uiLinuxBootGroupBox)
QemuVMWizard.addPage(self.uiASAWizardPage)
self.uiDiskImageHdbWizardPage = QtWidgets.QWizardPage()
@@ -241,8 +232,8 @@ class Ui_QemuVMWizard(object):
self.uiNewImageRadioButton_5.setChecked(False)
self.uiNewImageRadioButton_5.setObjectName("uiNewImageRadioButton_5")
self.horizontalLayout_9.addWidget(self.uiNewImageRadioButton_5)
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_9.addItem(spacerItem4)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_9.addItem(spacerItem3)
self.verticalLayout_6.addLayout(self.horizontalLayout_9)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
@@ -278,16 +269,15 @@ class Ui_QemuVMWizard(object):
self.uiServerWizardPage.setTitle(_translate("QemuVMWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a server type to run your new QEMU VM."))
self.uiServerTypeGroupBox.setTitle(_translate("QemuVMWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("QemuVMWizard", "Remote"))
self.uiVMRadioButton.setText(_translate("QemuVMWizard", "GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("QemuVMWizard", "Local"))
self.uiRemoteRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on a remote computer"))
self.uiVMRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on the GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on your local computer"))
self.uiRemoteServersGroupBox.setTitle(_translate("QemuVMWizard", "Remote servers"))
self.uiLoadBalanceCheckBox.setText(_translate("QemuVMWizard", "Load balance across all available remote servers"))
self.uiRemoteServersLabel.setText(_translate("QemuVMWizard", "Run on server:"))
self.uiTypeWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM type"))
self.uiTypeWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a type of QEMU VM to help with pre-configuration."))
self.uiOSDeprecatedWarningLabel.setText(_translate("QemuVMWizard", "<html><head/><body><p><span style=\" font-weight:600;\">WARNING</span>: The recommended way to run QEMU on Windows and OSX is to use the GNS3 VM</p></body></html>"))
self.uiASADeprecatedWarningLabel.setText(_translate("QemuVMWizard", "<html><head/><body><p><span style=\" font-weight:600;\">Note</span>: The recommended way to run ASA is to use ASAv with VMware.</p></body></html>"))
self.uiTypeLabel.setText(_translate("QemuVMWizard", "Type:"))
self.uiNameWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM name"))
self.uiNameWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a descriptive name for your new QEMU virtual machine."))

View File

@@ -127,6 +127,9 @@ class VirtualBox(Module):
continue
vm_settings = VBOX_VM_SETTINGS.copy()
vm_settings.update(vm)
# For backward compatibility we use vmname
if not vm_settings["name"]:
vm_settings["name"] = vmname
# for backward compatibility before version 1.4
if "symbol" not in vm_settings:
vm_settings["symbol"] = vm_settings.get("default_symbol", vm_settings["symbol"])
@@ -141,7 +144,7 @@ class VirtualBox(Module):
self._settings["vms"] = list(self._virtualbox_vms.values())
self._saveSettings()
def virtualBoxVMs(self):
def VMs(self):
"""
Returns VirtualBox VMs settings.
@@ -150,7 +153,12 @@ class VirtualBox(Module):
return self._virtualbox_vms
def setVirtualBoxVMs(self, new_virtualbox_vms):
@staticmethod
def vmConfigurationPage():
from .pages.virtualbox_vm_configuration_page import VirtualBoxVMConfigurationPage
return VirtualBoxVMConfigurationPage
def setVMs(self, new_virtualbox_vms):
"""
Sets VirtualBox VM settings.
@@ -255,25 +263,36 @@ class VirtualBox(Module):
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)")
raise ModuleError("Sorry a VirtualBox VM without the linked base setting enabled can only be used once in your topology")
elif node.project().temporary():
raise ModuleError("Sorry, VirtualBox linked clones are not supported in temporary projects")
vm_settings = {}
for setting_name, value in self._virtualbox_vms[vm].items():
if setting_name in node.settings() and value != "" and value is not None:
if setting_name != "name" and setting_name in node.settings() and value != "" and value is not None:
vm_settings[setting_name] = value
name = self._virtualbox_vms[vm]["name"]
vmname = self._virtualbox_vms[vm]["vmname"]
port_name_format = self._virtualbox_vms[vm]["port_name_format"]
port_segment_size = self._virtualbox_vms[vm]["port_segment_size"]
first_port_name = self._virtualbox_vms[vm]["first_port_name"]
default_name_format = VBOX_VM_SETTINGS["default_name_format"]
if self._virtualbox_vms[vm]["default_name_format"]:
default_name_format = self._virtualbox_vms[vm]["default_name_format"]
if linked_base:
default_name_format = default_name_format.replace('{name}', name)
name = None
node.setup(vmname,
name=name,
port_name_format=port_name_format,
port_segment_size=port_segment_size,
first_port_name=first_port_name,
linked_clone=linked_base,
additional_settings=vm_settings)
additional_settings=vm_settings,
default_name_format=default_name_format)
def reset(self):
"""
@@ -315,7 +334,7 @@ class VirtualBox(Module):
for vbox_vm in self._virtualbox_vms.values():
nodes.append(
{"class": VirtualBoxVM.__name__,
"name": vbox_vm["vmname"],
"name": vbox_vm["name"],
"server": vbox_vm["server"],
"symbol": vbox_vm["symbol"],
"categories": [vbox_vm["category"]]}

View File

@@ -19,8 +19,6 @@
Wizard for VirtualBox VMs.
"""
import sys
from gns3.qt import QtGui, QtWidgets
from gns3.servers import Servers
from gns3.dialogs.vm_wizard import VMWizard
@@ -107,6 +105,7 @@ class VirtualBoxVMWizard(VMWizard, Ui_VirtualBoxVMWizard):
vminfo = self.uiVMListComboBox.itemData(index)
settings = {
"name": vmname,
"vmname": vmname,
"server": server,
"ram": vminfo["ram"],

View File

@@ -106,6 +106,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
self.uiVMListComboBox.hide()
if not node:
# these are template settings
# rename the label from "Name" to "Template name"
self.uiNameLabel.setText("Template name:")
# load the default name format
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
# load the symbol
self.uiSymbolLineEdit.setText(settings["symbol"])
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
@@ -119,6 +127,8 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiSymbolLabel.hide()
self.uiSymbolLineEdit.hide()
self.uiSymbolToolButton.hide()
@@ -176,6 +186,15 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
del settings["enable_remote_console"]
if not node:
# these are template settings
# save the default name format
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
if '{0}' not in default_name_format and '{id}' not in default_name_format:
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
else:
settings["default_name_format"] = default_name_format
symbol_path = self.uiSymbolLineEdit.text()
pixmap = QtGui.QPixmap(symbol_path)
if pixmap.isNull():
@@ -185,14 +204,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size

View File

@@ -66,7 +66,10 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(section_item, ["VM name:", vbox_vm["vmname"]])
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", vbox_vm["name"]])
QtWidgets.QTreeWidgetItem(section_item, ["VirtualBox name:", vbox_vm["vmname"]])
if vbox_vm["linked_base"]:
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", vbox_vm["default_name_format"]])
QtWidgets.QTreeWidgetItem(section_item, ["RAM:", str(vbox_vm["ram"])])
QtWidgets.QTreeWidgetItem(section_item, ["Server:", vbox_vm["server"]])
QtWidgets.QTreeWidgetItem(section_item, ["Remote console enabled:", "{}".format(vbox_vm["enable_remote_console"])])
@@ -121,7 +124,7 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
self._virtualbox_vms[key].update(new_vm_settings)
item = QtWidgets.QTreeWidgetItem(self.uiVirtualBoxVMsTreeWidget)
item.setText(0, self._virtualbox_vms[key]["vmname"])
item.setText(0, self._virtualbox_vms[key]["name"])
item.setIcon(0, QtGui.QIcon(self._virtualbox_vms[key]["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
@@ -141,17 +144,8 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
if dialog.exec_():
# update the icon
item.setIcon(0, QtGui.QIcon(vbox_vm["symbol"]))
if vbox_vm["vmname"] != item.text(0):
new_key = "{server}:{vmname}".format(server=vbox_vm["server"], name=vbox_vm["vmname"])
if new_key in self._virtualbox_vms:
QtWidgets.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)
if vbox_vm["name"] != item.text(0):
item.setText(0, vbox_vm["name"])
self._refreshInfo(vbox_vm)
def _vboxVMDeleteSlot(self):
@@ -171,12 +165,12 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
"""
vbox_module = VirtualBox.instance()
self._virtualbox_vms = copy.deepcopy(vbox_module.virtualBoxVMs())
self._virtualbox_vms = copy.deepcopy(vbox_module.VMs())
self._items.clear()
for key, vbox_vm in self._virtualbox_vms.items():
item = QtWidgets.QTreeWidgetItem(self.uiVirtualBoxVMsTreeWidget)
item.setText(0, vbox_vm["vmname"])
item.setText(0, vbox_vm["name"])
item.setIcon(0, QtGui.QIcon(vbox_vm["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
@@ -191,4 +185,4 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
"""
# self._vboxVMSaveSlot()
VirtualBox.instance().setVirtualBoxVMs(self._virtualbox_vms)
VirtualBox.instance().setVMs(self._virtualbox_vms)

View File

@@ -27,7 +27,9 @@ VBOX_SETTINGS = {
}
VBOX_VM_SETTINGS = {
"name": "",
"vmname": "",
"default_name_format": "{name}-{0}",
"symbol": ":/symbols/vbox_guest.svg",
"category": Node.end_devices,
"port_name_format": "Ethernet{0}",

View File

@@ -2,14 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_preferences_page.ui'
#
# Created: Wed Jul 15 12:22:35 2015
# by: PyQt5 UI code generator 5.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_VirtualBoxPreferencesPageWidget(object):
def setupUi(self, VirtualBoxPreferencesPageWidget):
VirtualBoxPreferencesPageWidget.setObjectName("VirtualBoxPreferencesPageWidget")
VirtualBoxPreferencesPageWidget.resize(450, 250)
@@ -69,4 +70,3 @@ class Ui_VirtualBoxPreferencesPageWidget(object):
self.uiVboxManagePathToolButton.setText(_translate("VirtualBoxPreferencesPageWidget", "&Browse..."))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("VirtualBoxPreferencesPageWidget", "General settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("VirtualBoxPreferencesPageWidget", "Restore defaults"))

View File

@@ -24,24 +24,27 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
<item row="4" column="1">
<widget class="QComboBox" name="uiVMListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<item row="5" column="0">
<widget class="QLabel" name="uiVMRamLabel">
<property name="text">
<string>Symbol:</string>
<string>RAM:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
@@ -58,41 +61,14 @@
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Category:</string>
<string>Name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiVMListLabel">
<property name="text">
<string>VM name:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="uiVMListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiVMRamLabel">
<property name="text">
<string>RAM:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QSpinBox" name="uiVMRamSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -108,45 +84,21 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<item row="2" column="0">
<widget class="QLabel" name="uiSymbolLabel">
<property name="text">
<string>Console port:</string>
<string>Symbol:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="uiEnableConsoleCheckBox">
<property name="text">
<string>Enable remote console</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="uiACPIShutdownCheckBox">
<property name="text">
<string>Enable ACPI shutdown</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="uiHeadlessModeCheckBox">
<property name="text">
<string>Start VM in headless mode</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<widget class="QCheckBox" name="uiBaseVMCheckBox">
<property name="enabled">
<bool>true</bool>
@@ -156,7 +108,7 @@
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -169,6 +121,64 @@
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="uiCategoryLabel">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="uiEnableConsoleCheckBox">
<property name="text">
<string>Enable remote console</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="uiHeadlessModeCheckBox">
<property name="text">
<string>Start VM in headless mode</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="uiVMListLabel">
<property name="text">
<string>VM name:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="uiConsolePortLabel">
<property name="text">
<string>Console port:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="uiConsolePortSpinBox">
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiDefaultNameFormatLabel">
<property name="text">
<string>Default name format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
@@ -256,6 +266,9 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>

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