Compare commits

...

1090 Commits

Author SHA1 Message Date
Julien Duponchelle
194f3352a1 1.4.3 2016-02-19 22:16:16 +01:00
Julien Duponchelle
b27a7a1c31 Allow idlepc 0x0 in topology
Also improve the json schema error reporting
2016-02-19 15:53:21 +01:00
grossmj
1a5b5327dc Show an explicit error message when status code 0 is returned. Fixes #1034. 2016-02-18 16:52:57 -07:00
grossmj
d30ac79a77 Fixes minor bug when dropping a VirtualBox VM on the scene. Fixes #748. 2016-02-18 16:30:13 -07:00
Julien Duponchelle
a1461d9ea6 Correctly check local server if only local is available in vm wizard 2016-02-18 19:09:24 +01:00
Julien Duponchelle
783248d58b Try make the GNS3 VM server running value more reliable
Ref #1049
2016-02-18 16:30:00 +01:00
Julien Duponchelle
d13d77e39a Make VM configuration dialog modal
Fix #1048
2016-02-18 10:26:27 +01:00
grossmj
bf333e1964 Cannot take GIF screenshots (write is not supported by Qt). 2016-02-17 16:02:32 -07:00
Julien Duponchelle
1833e8683b 1.4.3dev1 2016-02-17 19:36:57 +01:00
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
92b9efea12 Merge remote-tracking branch 'origin/unstable' into unstable 2015-08-07 10:42:43 -06:00
grossmj
d1af92942d Fixes bug when validating VMware wizard page. 2015-08-07 10:42:26 -06:00
Julien Duponchelle
b47b5e2dc0 1.4.0beta1 2015-08-07 18:37:35 +02:00
Julien Duponchelle
3265a94d26 Show an error id you try to use a local server not started 2015-08-07 18:30:43 +02:00
Julien Duponchelle
e9d9dc2748 CLear the list before asking for VM list for Vbox and Vmware 2015-08-07 18:09:38 +02:00
Julien Duponchelle
8b38d4967c Fix bugs report by elhers 2015-08-07 17:35:26 +02:00
Julien Duponchelle
3729070e33 Fix password lost for remote servers
Fix #603
2015-08-07 15:31:07 +02:00
Julien Duponchelle
d415c20446 Unused import 2015-08-07 15:19:51 +02:00
Julien Duponchelle
efb5931be3 Fix schema for virtualbox 1.3.2 topologies
https://community.gns3.com/message/40214
2015-08-07 15:17:44 +02:00
Julien Duponchelle
4867a5510a Refactor all VM wizards
* All the wizards inherit of vm_wizard even VMware and VirtualBox
* Code for managing images is in vm_wizard_with_images (Qemu, Dynamips,
IOU)
* If GNS3VM is not running you can't select it at first step

Fix #604
2015-08-07 14:30:56 +02:00
Julien Duponchelle
932dd79ac1 Fix Apply not enabled when removing a remote server
Fix #602
2015-08-07 11:42:48 +02:00
Julien Duponchelle
dc5957dd0a Fix remote server list display when use_local_server for Qemu 2015-08-07 09:53:03 +02:00
grossmj
bb131b4ff5 Keep the "use local server" settings to select the default radio button in VM wizards. Ref #598. 2015-08-06 23:43:12 -06:00
grossmj
76e3d3523e Fixes #601 (spelling error). 2015-08-06 15:11:48 -06:00
Julien Duponchelle
2bc9dd2802 Store the url of server in gns3_gui for third party apps 2015-08-06 17:23:04 +02:00
Julien Duponchelle
0950c3d80d Correctly handle remote server in qemu wizard
Fix #599
2015-08-06 16:35:03 +02:00
Julien Duponchelle
808ea00787 Fix error when editing a qemu device with a relative path
Fix #594
2015-08-06 14:48:50 +02:00
grossmj
c4400f8a64 Fixes issue in VirtualBox wizard when no local server is enabled in the VirtualBox general settings. 2015-08-05 22:42:16 -06:00
grossmj
4a78cfe00a Catch exception when starting packet capture reader for a remote packet capture. Fixes #597. 2015-08-05 19:07:54 -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
grossmj
4e073b4681 Fixes KeyError: 'vmx_path'. Fixes #595. 2015-08-05 17:38:28 -06:00
grossmj
e9548b2e03 Support for CPUs setting for Qemu VMs. 2015-08-05 17:17:55 -06:00
Julien Duponchelle
05820e973b Fix chicken of VNC command and add a warning about bugs in OSX VNC
Fix #593
2015-08-05 14:57:43 +02:00
grossmj
f779b6fe7d Bump version to 1.4.0dev5. 2015-08-04 15:29:03 -06:00
Julien Duponchelle
210c46f49d Fix issue whith auto update when we release a new build 2015-08-04 20:11:48 +02:00
Julien Duponchelle
e746ddc525 1.4.0alpha4 2015-08-04 19:48:09 +02:00
grossmj
d186d4ce1e Use half the available physical memory for the GNS3 VM in the Setup Wizard. 2015-08-04 11:32:40 -06:00
Jeremy Grossmann
cee80cc579 Merge pull request #585 from GNS3/wizard_step_1
Show the server choice in wizard if you have a VM or a remote server
2015-08-04 11:31:13 -06:00
Jeremy Grossmann
1c0232cf96 Merge pull request #591 from GNS3/drop_resource_type
Drop useless notion of resource_type
2015-08-04 11:20:23 -06:00
Julien Duponchelle
77ff7a8aba Fix tests 2015-08-04 18:03:47 +02:00
Julien Duponchelle
fed48de0d5 Drop useless notion of resource_type 2015-08-04 17:58:47 +02:00
Julien Duponchelle
95c1df229f Fix When you modify config from outside router list is not refresh
Fix #590
2015-08-04 17:51:31 +02:00
Julien Duponchelle
78b2cb8a32 Sync auth settings between 1.3 and 1.4
Fix #589
2015-08-04 15:25:15 +02:00
Julien Duponchelle
e1ba7a0ad6 Fix merge error 2015-08-04 15:24:18 +02:00
Julien Duponchelle
e64597eaf0 Group bad request by path
Fix #587
2015-08-04 14:57:03 +02:00
Julien Duponchelle
f1b7ea13ef Fix behavior of wizard for vmware and virtualbox 2015-08-04 12:46:31 +02:00
Julien Duponchelle
25d6a0a160 Disallow connection to a different major version in all cases
And test all cases

Fix #586
2015-08-04 12:35:58 +02:00
Julien Duponchelle
5be13a645e Merge branch 'master' into unstable 2015-08-03 19:24:30 +02:00
Julien Duponchelle
dfa85e9886 Prepare version 1.3.10 2015-08-03 19:21:48 +02:00
Julien Duponchelle
8a612970e6 1.3.9 2015-08-03 18:56:10 +02:00
Julien Duponchelle
f18a9fe7f5 Show the server choice in wizard if you have a VM or a remote server
It's easier for using instead of searching the screen with use
local server options.

But I think it's not complete because we still have the checkbox
in the preferences.

When I read the code I found this:
cd9bb16f79/gns3/modules/builtin/__init__.py (L72)

And I'm not sure in which case it's call. I think it's only for
VPCS and things like Dynamips switch. Is it right? If it's the case
we can
drop the checkbox from IOU, Qemu, VMware, VirtualBox
2015-08-03 15:53:59 +02:00
Julien Duponchelle
a5d0fe5b8b Fix tests 2015-08-03 12:00:55 +02:00
Julien Duponchelle
3ef70a1213 Fix local server settings erased at each launch
Fix #580
2015-08-03 11:44:55 +02:00
grossmj
3bd2136450 Support for Qemu disk interfaces, cd/dvd-rom image and boot priority. 2015-08-02 23:02:17 -06:00
grossmj
c165146d7c Prevents progress dialog to stay displayed (fixes graphical bug). 2015-08-01 15:19:57 -06:00
grossmj
adfdae3a8e Check that DHCP is enabled on the VirtualBox host-only network associated with the VirtualBox GNS3 VM. Fixes #287. 2015-08-01 14:48:41 -06:00
grossmj
f92aae4e6e Fixes Qt5 incompatibility. 2015-08-01 11:58:32 -06:00
grossmj
bed867cf7b Catch exception when trying to launch Wireshark. 2015-08-01 11:55:26 -06:00
grossmj
81aaeaf25f Adds traceback information when there is an assertion error with topologyFile. 2015-08-01 11:54:06 -06:00
grossmj
3cc62563df Catch exception when trying to launch Wireshark. 2015-08-01 11:49:40 -06:00
grossmj
aaa21be642 Backport: fixes migration of cloud interfaces. 2015-08-01 11:28:12 -06:00
grossmj
aa87cb0064 Fixes migration of cloud interfaces. Fixes #582. 2015-08-01 11:19:12 -06:00
grossmj
1e6e2d32e3 Wait for the server to be fully started in the GNS3 VM. Fixes #581. 2015-07-31 15:42:47 -06:00
Julien Duponchelle
a95805bc04 1.4.0dev4 2015-07-31 15:34:10 +02:00
Julien Duponchelle
a5ccce30d6 Fix issue with file upload and Qt 5.5
For an unknow reason it's stop working with Qt 5.5
the fix is to use QtCore.Qt.QueuedConnection on the slot
in order to avoid blocking the HTTP thread.

Fix #584
2015-07-31 12:21:50 +02:00
grossmj
f6fdd79d0f Fixes version. 2015-07-30 19:07:25 -06:00
grossmj
3f9c1af8e8 Improves and fixes a bug with the Symbol dialog. 2015-07-29 21:58:54 -06:00
grossmj
be9a88029b Improves the symbol dialog. Implements #514. 2015-07-29 18:21:14 -06:00
Julien Duponchelle
de53581826 1.4.0alpha3 2015-07-29 18:16:08 +02:00
Julien Duponchelle
d8db54d8eb Clarify informations in the IOUVM converter wizard 2015-07-29 14:19:53 +02:00
Julien Duponchelle
64aa8e9190 Hide the browse button for remote servers in qemu wizard 2015-07-29 12:00:39 +02:00
Julien Duponchelle
7719fb5fed Ask for app restart during in app upgrade
Ref #578
2015-07-29 11:46:26 +02:00
Julien Duponchelle
e5d8fac004 When you turn off crash report has developer no warning about .git
Fix #580
2015-07-29 11:07:10 +02:00
grossmj
c8ce6e0dc1 Ensure proper naming for Qemu disk images. 2015-07-28 22:15:37 -06:00
grossmj
7523ff50ff Show the disk image create button in the Qemu VM wizard for remote server and the GNS3 VM. 2015-07-28 21:21:47 -06:00
grossmj
74cd7f07a0 Adds psutil dependency. 2015-07-28 19:34:13 -06:00
grossmj
1edfcc2c42 Configure vCPU/RAM from setup wizard. Implements #565. 2015-07-28 19:31:54 -06:00
grossmj
631f283cba Should fix KeyError: 'vmx_path'. Fixes #574 . 2015-07-28 17:34:58 -06:00
grossmj
a33609b144 Restart the local server from the setup wizard if necessary. Fixes #577. 2015-07-28 17:27:03 -06:00
grossmj
572e028917 Start the GNS3 VM first before the local server. Fixes #577. 2015-07-28 17:26:36 -06:00
Julien Duponchelle
31262f9f7f Fix tests PyQT 5.5 compatibility 2015-07-28 17:30:34 +02:00
Julien Duponchelle
e5af0e778f Changelog 1.4.0alpha3 2015-07-28 17:24:04 +02:00
Julien Duponchelle
97a6d23d36 Merge branch 'master' into unstable 2015-07-28 16:55:05 +02:00
Julien Duponchelle
773d680e42 New crash report key 2015-07-28 16:53:28 +02:00
Julien Duponchelle
a43236dfc3 Merge pull request #566 from GNS3/iouvm_converter
IOUVM converter
2015-07-28 15:52:38 +02:00
Julien Duponchelle
12e65994ae Manage errors 2015-07-28 15:52:12 +02:00
Julien Duponchelle
d7b43ef5cf IOUVM converter 2015-07-28 15:52:11 +02:00
Julien Duponchelle
97a7496854 Update configuration file for replacing GNS3VM by IOUVM 2015-07-28 15:51:28 +02:00
Julien Duponchelle
e6e24ef953 Create qemu disk image on remote server
Fix #558
2015-07-28 14:37:14 +02:00
Julien Duponchelle
1903fc2a6f Create qemu image on the server
Fix #569, #562
Ref #558
2015-07-28 14:20:33 +02:00
Jeremy Grossmann
94613f0458 Merge pull request #567 from GNS3/apply
Inform user before exiting preferences dialog with changes
2015-07-27 12:28:42 -07:00
Jeremy
2072fee01a Merge remote-tracking branch 'origin/master'
Conflicts:
	CHANGELOG
	gns3/version.py
2015-07-27 12:03:57 -06:00
Jeremy
b1658f3799 1.3.8 2015-07-27 12:03:07 -06:00
Julien Duponchelle
115654fa1d 1.3.9dev1 2015-07-27 20:02:49 +02:00
Julien Duponchelle
8c3179f0fe 1.3.8 2015-07-27 19:58:51 +02:00
Julien Duponchelle
4313aed9f0 Yet another PyQT 5.5 bug fix 2015-07-27 17:27:14 +02:00
Julien Duponchelle
1cbc3a0063 Fix Error with qemu-create when changing refcount bits
Fix #576
2015-07-27 14:42:24 +02:00
Julien Duponchelle
102e5cba6b More PyQT 5.5 compatibility
Fix #568
2015-07-27 11:48:31 +02:00
Julien Duponchelle
e932780f02 Fix _addRemoteServer() got an unexpected keyword argument 'local'
Fix https://github.com/GNS3/gns3-server/issues/281
2015-07-27 11:43:08 +02:00
Julien Duponchelle
1b0415f81d Do not crash if configuration file is removed
Fix #572
2015-07-27 11:29:28 +02:00
Julien Duponchelle
aa82383d2c Fix PyQT 5.5 compatibility
Fix #568
2015-07-27 11:26:32 +02:00
Jeremy
57d1c2a81a Fixes rare issue when adding a link. Fixes #573. 2015-07-26 16:10:12 -06:00
Jeremy
606ddb7b38 Fixes rare issue when adding a link. Fixes #573. 2015-07-26 16:08:55 -06:00
Jeremy
f3f7829bbe Fixes crash with PyQt 5.5. Fixes #568. 2015-07-25 15:03:22 -06:00
Jeremy
ae9a009f1f Changes how to look for the vmrun.exe location. 2015-07-24 16:50:36 -06:00
Julien Duponchelle
6c6bf7870d Inform user before exiting preferences dialog with changes
This Gray out the apply button by default. If something change
the button apply is enabled.

If you try to exit the windows without saving you got a warning.

The change detection is made by binding to various signal of the
preferences dialog childrens.

This work also when coming from the setup wizard.

Fix #515
2015-07-24 16:30:18 +02:00
Julien Duponchelle
6cff9d6fb5 Write GNS3 upgrade to appdata
Fix #564
2015-07-24 15:37:20 +02:00
Julien Duponchelle
0a71be1205 Fix windows asking for upgrade to the wrong version
Also replace the parse version of setup tools because it's
display a warning when comparing tuples.

Fix #563
2015-07-24 15:03:24 +02:00
Julien Duponchelle
3736c3380c Drop unused code 2015-07-24 10:57:28 +02:00
Julien Duponchelle
1beb25a820 Merge branch 'master' into unstable 2015-07-23 11:27:21 +02:00
Jeremy
202eb95eb3 Merge remote-tracking branch 'origin/unstable' into unstable
Conflicts:
	gns3/version.py
2015-07-22 20:56:48 -06:00
Jeremy
eaa5a326fe Bump version to 1.4.0dev3 2015-07-22 20:55:54 -06:00
Jeremy
409d3de69c Backport: option to drop nvram & disk files for IOS routers in order to save disk space. 2015-07-22 16:02:08 -06:00
Julien Duponchelle
e05ee6bba0 1.4.0alpha2 2015-07-22 20:42:47 +02:00
Jeremy
111ed742ec Change default timeout for VBoxManage and vmrun from 10 to 60 seconds. 2015-07-22 11:41:46 -06:00
grossmj
8fd5743d75 Fixes VPCS "" does not exist message box. 2015-07-22 09:39:39 -06:00
Jeremy
63b79ccf3b Cloud support with the GNS3 VM. 2015-07-21 19:20:07 -06:00
Jeremy
45f4265c03 Display an error message when Qemu binaries cannot be retrieved in the Qemu VM configuration page. 2015-07-21 17:10:43 -06:00
Jeremy
b7b13ea2cb Remove default FLASH when no hda disk for Qemu VMs. Fixes #535. 2015-07-21 16:46:27 -06:00
Jeremy
1f660b180e Fixes indentation mistakes. 2015-07-21 16:04:55 -06:00
Jeremy
3b2ccf75ec Use the registry to find vmrun if the default VMware install path doesn't exist. Fixes #546. 2015-07-21 15:58:42 -06:00
Julien Duponchelle
734fc65c29 Merge branch 'master' into unstable 2015-07-21 18:49:40 +02:00
Julien Duponchelle
5252ed16ca New crash report key 2015-07-21 18:49:09 +02:00
Jeremy
cec6fcf81a Avoid the creation of a NIO when one has been cancelled. 2015-07-20 19:31:35 -06:00
Jeremy
a812796bdc Avoid the creation of a NIO when one has been cancelled. 2015-07-20 19:28:51 -06:00
Julien Duponchelle
9abb4fe692 Fix Crash with chinese characters
Fix #553
2015-07-17 17:26:02 +02:00
Julien Duponchelle
42b86c6b18 Display an error if terminal command is invalid
Fix #550
2015-07-17 17:14:57 +02:00
Julien Duponchelle
8aec2275fd Fix TypeError: _addRemoteServer() got an unexpected keyword argument
'cloud'

Fix #551
2015-07-16 19:03:21 +02:00
Julien Duponchelle
61f03d734b Fix TypeError: _addRemoteServer() got multiple values for keyword argument 'user'
Fix #547
2015-07-16 11:02:14 +02:00
Julien Duponchelle
907e20d0ec Fix AttributeError: 'UUID' object has no attribute 'connected'
Fix #543
2015-07-16 10:44:36 +02:00
Julien Duponchelle
f84c759e8d Fix AttributeError: 'LocalConfig' object has no attribute '_last_config_changed'
Ref #545
2015-07-16 10:38:23 +02:00
grossmj
f6fb4695c1 Fixes missing return in isLocalServerRunning() 2015-07-15 17:47:00 -06:00
grossmj
3a1ccb5ba0 Prevents "Show in File Manager" to be used with generic switches. 2015-07-15 16:18:01 -06:00
grossmj
7b8ab4ac2c Fixes code indentation issue with auto idle-pc feature. 2015-07-15 16:11:39 -06:00
grossmj
53504f1c3d Removes reference to cloud server in VirtualBox VM wizard. 2015-07-15 16:02:36 -06:00
Julien Duponchelle
d5408165f9 If GUI exit due to a signal do not warn user
This avoid issue for user trying to automate GNS3

Fix #542
2015-07-15 18:56:32 +02:00
Julien Duponchelle
9bf8c115c1 Fix typo in variable name 2015-07-15 18:11:56 +02:00
Julien Duponchelle
a06ac4cbb6 Fix some schema validation errors 2015-07-15 14:59:32 +02:00
Julien Duponchelle
6406b7412d Log error about why gns3 converter can't be used
Ref #540
2015-07-15 13:27:08 +02:00
Julien Duponchelle
41aca47f92 Remove unused dependencies 2015-07-15 13:14:47 +02:00
Julien Duponchelle
a170e1cfb5 Cleanup UI after QT4 drop 2015-07-15 12:25:46 +02:00
Julien Duponchelle
b2429a6a1b Drop PyQt4 support and show an error for users
Fix #533, #532
2015-07-15 12:17:54 +02:00
Julien Duponchelle
23e1baf92f First bugs discover by gns3-qa :)
* The dot1q was not supported in JSON schema validation
* border_style is not always mandatory
* border_width propery for ellipse
2015-07-13 14:52:15 +02:00
Julien Duponchelle
278c94b7df Log corrupted topology 2015-07-13 14:52:14 +02:00
Julien Duponchelle
256fc5a222 Log missing IOS images 2015-07-13 14:52:14 +02:00
Julien Duponchelle
a0fa28b3fd Call AutoStart even if the topology is empty. 2015-07-13 14:52:14 +02:00
grossmj
09d8212225 Fixes symbol for VM template gone after restart. Fixes #538. 2015-07-12 16:51:43 -06:00
Julien Duponchelle
e658786e88 Wait for VirtualBox vm start
Fix #530
2015-07-11 14:56:28 +02:00
Julien Duponchelle
4a1e6eba8c Fix VirtualBox GNS3 VM
Fix #530
2015-07-10 22:51:27 +02:00
Julien Duponchelle
ce5e209681 Fix QComboBox broken with PyQT4
Fix #526
2015-07-10 20:29:47 +02:00
Julien Duponchelle
40178b5277 Do not send error to the notification feed
Fix #528
2015-07-10 20:15:07 +02:00
Julien Duponchelle
631c487233 Fix issue with remote server not saved/migrated
Fix #521
2015-07-10 18:48:04 +02:00
Julien Duponchelle
5e47afe3a4 Remove ram as a mandatory dynamips settings
Fix #523
2015-07-10 17:33:32 +02:00
Julien Duponchelle
942bf9094d Force UTF-8 when reading server configuration file
Fix #525
2015-07-10 17:28:20 +02:00
Julien Duponchelle
985200b6d9 Fix setup wizard PyQT4 compatibility
clicked as default argument False with Qt5 but not with Qt4

Fix #527
2015-07-10 17:20:41 +02:00
Julien Duponchelle
35b61bc891 1.4.0dev2 2015-07-10 15:50:19 +02:00
Julien Duponchelle
b149abbb23 1.4.0alpha1 2015-07-09 19:06:17 +02:00
Julien Duponchelle
536387ad8c Turn off Travis notification 2015-07-09 18:43:03 +02:00
grossmj
1628edbb8b Merge remote-tracking branch 'origin/unstable' into unstable 2015-07-09 10:30:43 -06:00
grossmj
39adbbdb27 Use os.path.abspath() for path returned by shutil.which(). 2015-07-09 10:30:14 -06:00
Julien Duponchelle
be49fa9b54 Reupload a clean iourc at each IOU start
Fix #485
2015-07-09 17:41:48 +02:00
Julien Duponchelle
aaed8435d1 Add 1.4.0alpha1 Changelog
Fix #519
2015-07-09 17:41:48 +02:00
Julien Duponchelle
c5bd406351 Fix test download project 2015-07-09 17:41:48 +02:00
Jeremy
4004caadc7 Fixes error in setup wizard if VMware isn't installed. Fixes #517. 2015-07-08 15:50:35 -06:00
Julien Duponchelle
1ac2a782c4 Fix Preferences menu always open the wizard on OSX
Fix #516
2015-07-08 19:17:36 +02:00
Julien Duponchelle
6887928bba Remove unused cloud code from the 1.4
I hope it will avoid strange bugs
2015-07-08 17:40:59 +02:00
Julien Duponchelle
1401537796 Fix File -> Download remote project doesn't seem to work.
Fix #512
2015-07-08 17:13:46 +02:00
Julien Duponchelle
e53f5ca175 Remove unused code 2015-07-08 16:54:55 +02:00
Julien Duponchelle
463f49586c Monitor config file in a dedicated thread.
This avoid warning in the console at startup

Fix #504
2015-07-08 11:49:07 +02:00
Julien Duponchelle
5a87098f95 Fix PyQT4 compatibility
Fix #505
2015-07-08 11:11:34 +02:00
Julien Duponchelle
da44ff05aa Add missing border_style property to json schema
Fix #510
2015-07-08 11:01:42 +02:00
grossmj
3839171d42 Use GNS3 VM if local server is not enabled for VPCS module. Fixes #511. 2015-07-07 23:06:09 -06:00
grossmj
8d9a009f89 Missing return when trying to download a remote project while using a temporary project. 2015-07-07 22:42:22 -06:00
grossmj
2c0a4cf7a7 Use the GNS3 VM by default in the VM wizards if local server is not activated in the module preferences and the VM is running. 2015-07-07 22:22:13 -06:00
grossmj
4b2269b668 Do not start the GNS3 VM if local server is chosen in the Setup wizard. 2015-07-07 21:48:27 -06:00
grossmj
3bfff093f8 Setup Wizard (to be tweaked). Implements #402. 2015-07-07 12:59:16 -06:00
grossmj
6d835a2068 Add timeouts to vmrun and VBoxManage commands. 2015-07-07 12:55:47 -06:00
grossmj
36398c54e0 Fixes exception when untick the VirtualBox Preference "Use the local server". Fixes #509. 2015-07-07 08:01:07 -06:00
grossmj
2a2777b22d Fixes GUI crash on unset vmrun_path variable. Fixes #507. 2015-07-07 07:58:48 -06:00
Jeremy
11dc69334d Merge remote-tracking branch 'origin/unstable' into unstable 2015-07-06 11:34:58 -06:00
Jeremy
c4c17aa115 Do not use default paths for vmrun and vboxmanage if they don't exist. 2015-07-06 11:34:52 -06:00
Julien Duponchelle
4b41c06dc4 Since travis didn't support Qt5 do not send alert 2015-07-06 17:00:53 +02:00
Julien Duponchelle
a9e27cd63f Fix Mac OS X: cx_Freeze app crashes on first start
Fix #495
2015-07-06 14:47:38 +02:00
Julien Duponchelle
0f6b4f2b32 Fix VMware remote server support on OSX 2015-07-06 14:38:25 +02:00
Julien Duponchelle
08877155e2 Dissallow VMware for local server on all Wizard page
Fix #501
2015-07-06 12:17:20 +02:00
Jeremy
7007f3ea44 Make sure a path is set before checking if it exists in preferences. 2015-07-05 20:13:11 -06:00
Jeremy
9419cce747 Warn users they cannot create VMware Fusion VMs on OSX. See #501. 2015-07-05 18:55:41 -06:00
Julien Duponchelle
033c884059 Revert "Add temporary debug for #495"
This reverts commit 99b0b65e89.
2015-07-05 23:38:31 +02:00
Julien Duponchelle
99b0b65e89 Add temporary debug for #495 2015-07-05 23:22:03 +02:00
Julien Duponchelle
67042470f3 Fix server not log on OSX not written in ~/.config/gns3.net
Fix #497
2015-07-05 21:21:51 +02:00
Jeremy
7c5388ee71 More checks on local paths in the preferences. 2015-07-04 12:18:12 -06:00
Julien Duponchelle
96634ece3f Fix getting started setting not migrate from 1.3
Fix #494
2015-07-04 11:24:57 +02:00
Jeremy
ee9ea92a11 Adds -no-kvm to the ASA template and ignore -no-kvm on platforms other than Linux. Should resolve #472. 2015-07-03 23:35:42 -06:00
Jeremy
246e9f7e3f Do not save the GNS3 VM host since it is retrieved every time we start. 2015-07-03 23:27:56 -06:00
Jeremy
fc43f89d9e Explicitly set the acceleration method to tcg for ASA templates. Should resolve #472. 2015-07-03 16:10:07 -06:00
Jeremy
be75dc95a3 Show an error if the console port range overlaps the default VNC port range (5900 to 6000) in the server preferences. 2015-07-03 16:07:19 -06:00
Julien Duponchelle
6bb0f7b902 Fix tests 2015-07-03 22:57:14 +02:00
Julien Duponchelle
6178c56606 Migrate 1.3 configuration file
Fix #492
2015-07-03 22:54:48 +02:00
Jeremy
0a2ca923ee Remove running VBoxManage as another user option (root for instance). 2015-07-03 14:37:17 -06:00
Jeremy
5adc4cb437 Merge remote-tracking branch 'origin/unstable' into unstable 2015-07-03 13:09:16 -06:00
Jeremy
28d4371f4d Fixes #481. 2015-07-03 13:08:47 -06:00
Julien Duponchelle
d343bbe1ac Fix an issue with configuration migration on OSX 2015-07-03 20:49:13 +02:00
Julien Duponchelle
c6a6163aec Add chicken VNC for OSX 2015-07-03 20:48:48 +02:00
Julien Duponchelle
be60a37a29 Check if an insecure HTTPS certificate has not changed
Fix #488
2015-07-03 15:51:45 +02:00
Julien Duponchelle
b3d42866b7 Fix error message when you can't bind to the local ip 2015-07-03 15:20:25 +02:00
Julien Duponchelle
9633fb659a Fix tests when you play wih your local ip 2015-07-03 15:14:30 +02:00
Julien Duponchelle
fab637f5ae Minor fix for error loggings when port conflict 2015-07-03 15:07:07 +02:00
Julien Duponchelle
edcd991659 Allow user to change the location of the config file 2015-07-03 11:22:41 +02:00
Julien Duponchelle
84f41b9c2f Always store exception with the config 2015-07-03 11:15:06 +02:00
grossmj
e7a61f07a2 GNS3 VM not headless by default. 2015-07-02 17:16:34 -06:00
Julien Duponchelle
57616ffd83 Fix crash at Windows launch 2015-07-02 16:38:56 +02:00
Julien Duponchelle
ea002e6634 Fix error at GNS3 VM launch about the missing version
Fix #484, https://github.com/GNS3/gns3-server/issues/250
2015-07-02 16:29:26 +02:00
Julien Duponchelle
24574360a0 Path fixing 2015-07-02 15:35:32 +02:00
Julien Duponchelle
e4dff4916b Fix A gns3.net folder is created in config on MacOS with the logs
Fix #480
2015-07-02 11:18:28 +02:00
grossmj
bd1ff4c954 Restore missing debug level. 2015-07-01 16:16:08 -06:00
Julien Duponchelle
563c762756 Add the frozen path to the binary lookup path.
This avoid issue when the current working directory is different.
And avoid special case in the code for searching the binary.

Fix #479
2015-07-01 15:55:54 +02:00
Julien Duponchelle
6c6bd65969 Fix resources path lookup 2015-07-01 15:05:29 +02:00
Julien Duponchelle
e2dbd5216d Merge branch 'master' into unstable 2015-07-01 14:32:04 +02:00
Julien Duponchelle
959a05643f Fix crash when the vmware command is not available 2015-07-01 14:28:02 +02:00
Julien Duponchelle
3e0f33859a Support self update of the application
Fix #456
2015-07-01 11:54:30 +02:00
Julien Duponchelle
b70615c19c Due to the migration to cx_freeze darwin and windows share
the same path for resources
2015-07-01 11:54:30 +02:00
Julien Duponchelle
f6956baf89 Use the same location for the server config on GUI and server
Fix https://github.com/GNS3/gns3-server/issues/249
2015-07-01 09:55:40 +02:00
Julien Duponchelle
47cd7cf2a2 Catch invalid reply from the remote server
Fix #477
2015-07-01 09:30:25 +02:00
grossmj
a1d6d17685 Option to adjust the local server IP address to be in the same subnet as the GNS3 VM. 2015-06-30 22:58:36 -06:00
grossmj
fe768a650d Automatically determine the VMware host type. 2015-06-30 22:56:19 -06:00
grossmj
d9ba282024 Adjust layout in Qemu VM wizard. 2015-06-30 17:21:03 -06:00
grossmj
1b48cc99e5 Adjust spacing in Qemu image wizard. 2015-06-30 17:17:16 -06:00
Jeremy
8740e20c90 Support to insert SVG images. 2015-06-29 17:55:05 -06:00
Julien Duponchelle
93a2cdf4bf Warning about deprecated ASA on Qemu
Fix #327
2015-06-29 15:23:14 +02:00
Julien Duponchelle
0faa4e62f0 Fix segfault at exit on OSX
The fix as two part
1) We restore the IO at exit, because otherwise a print on console
crash due to the fact the console as already disapear
2) We force a Garbage Collect it seem to clean the pyqt
references and allow QT to properly exit

Fix #475
2015-06-29 12:23:09 +02:00
Julien Duponchelle
78e97e9731 Create Qemu image from the Qemu new VM wizard
Fix #389
2015-06-29 11:11:43 +02:00
Julien Duponchelle
d59be759bd Support relative path for Default IDLE PC
I also move the code to the Dynamips module and add test for it.

Fix #471
2015-06-26 16:00:06 +02:00
Julien Duponchelle
2df78eb436 Display crash event from qemu in the console
Fix #473
2015-06-26 15:11:16 +02:00
Julien Duponchelle
3f132a759f Drop the slider from Qemu Wizard because he was buggy
Fix #470
2015-06-25 16:03:10 +02:00
Julien Duponchelle
b87244bf9a Bug fix and graphical improvement of qemu_image_wizard
The look & feel is more the look & feel of other wizards.
Resize is correctly supported.

Fix #470
2015-06-25 15:09:32 +02:00
Julien Duponchelle
6d826001cc Allow user to open the file manager
Fix #260
2015-06-25 10:02:45 +02:00
grossmj
2de4d36c9f Restart the GNS3VM if required in preferences. 2015-06-24 23:27:27 -06:00
grossmj
ef01212ce8 Moves KVM setting to Qemu preferences. 2015-06-24 22:46:21 -06:00
grossmj
9fa8c36b5f Start the GNS3 VM if enabled in the preferences. Fixes #468. 2015-06-24 21:51:37 -06:00
grossmj
a4973616b4 Adds console_type to topology.json 2015-06-24 19:10:57 -06:00
grossmj
a8dd2ed2da VNC console support for Qemu VMs. Implements #447. 2015-06-24 19:09:03 -06:00
Julien Duponchelle
2766667d16 Move common code of the setupCallback to the base class VM
This will allow to add common initialization code more easily.
2015-06-24 18:35:07 +02:00
Julien Duponchelle
a7e7b9a3ca If VMWware host type exist we use it.
Related to #467
2015-06-24 17:01:46 +02:00
Julien Duponchelle
7df272fd4a Support auth for the GNS3 VM
Move all the synchronous HTTP code to HTTP Client

Fix #461
2015-06-24 16:33:40 +02:00
Julien Duponchelle
cd694366ed Refresh the list of VM when I click on the GNS3 VM tab
Fix #466
2015-06-24 15:02:28 +02:00
Julien Duponchelle
4a9fb62663 Fix startup of the GNS3VM
Fix #467
2015-06-24 14:49:39 +02:00
Julien Duponchelle
8c7144205b Hide auto generated user name in url 2015-06-24 12:11:51 +02:00
Julien Duponchelle
f4057d4c2c Keep the old gns3.net directory on Mac for user testing 1.3/1.4
Fix #451
2015-06-24 12:02:46 +02:00
Julien Duponchelle
ad5de8c84c Change the location of the config file on OSX
Fix #451
2015-06-24 11:55:36 +02:00
grossmj
15f414cae3 Adds first port name option (for management interfaces). Completes #309. 2015-06-23 22:18:39 -06:00
Jeremy
54d01d2ffd Support for custom port names. #309. 2015-06-23 19:18:09 -06:00
Julien Duponchelle
f8e87c5aa1 Add a force quit button when closing the app
Fix #438
2015-06-23 21:58:08 +02:00
Julien Duponchelle
090a85bdd5 Fix Crash report sending errors when no reliable Internet connection
Fix #403
2015-06-23 19:26:30 +02:00
grossmj
403611443f Merge remote-tracking branch 'origin/unstable' into unstable 2015-06-23 10:53:01 -06:00
grossmj
82b14a14e0 Force item children to redraw because of a problem with QGraphicsEffect. 2015-06-23 10:52:50 -06:00
Julien Duponchelle
a0789b45e4 Test for 12975b1ecf
Fix #462
2015-06-23 18:42:44 +02:00
Jeremy
12975b1ecf Preserve settings we don't use. 2015-06-23 10:20:28 -06:00
Julien Duponchelle
59de1212cb Basic auth support for remote servers
Fix #391
2015-06-23 16:54:09 +02:00
grossmj
cc8246b474 Merge remote-tracking branch 'origin/unstable' into unstable 2015-06-23 07:34:38 -06:00
grossmj
f13a91e83e Fixes versions. 2015-06-23 07:34:25 -06:00
Julien Duponchelle
598aae8ef1 PEP 8 2015-06-23 15:27:46 +02:00
Julien Duponchelle
fe5414bdf4 Ensure password is written to disk 2015-06-23 10:29:23 +02:00
Julien Duponchelle
fd40289887 Fix version number 2015-06-23 10:14:40 +02:00
Julien Duponchelle
225b8aa63a Merge branch 'master' into unstable 2015-06-23 09:46:08 +02:00
Julien Duponchelle
e63bbe734c Fix topology schema for old topologies 2015-06-23 09:17:32 +02:00
grossmj
8c76fb6f7c Symbol selection dialog supports pixmap node items. 2015-06-22 23:17:22 -06:00
grossmj
617ed0a3cd Fixes #449 (cannot turn off the local server). 2015-06-22 21:45:17 -06:00
grossmj
e32e7dd828 Save custom symbols in the project-files directory for projects. 2015-06-22 21:28:13 -06:00
grossmj
d6ba027ae7 Adds symbol overview in tooltips for all symbol text fields. 2015-06-22 20:54:54 -06:00
grossmj
1ea4a7c113 Symbol and category can be changed for VPCS VM template. 2015-06-22 17:56:31 -06:00
grossmj
8d7e161662 Rename symbols: *.normal.svg to *.svg 2015-06-22 17:30:23 -06:00
grossmj
332b04d640 Remove "hover symbol" references. 2015-06-22 16:23:18 -06:00
grossmj
e7af8305c2 Remove SVG icons used in hover events. 2015-06-22 16:04:13 -06:00
grossmj
edffba3496 Support for custom symbols. Still some work to do on the QGraphicsEffect. Implements #388. 2015-06-22 15:56:29 -06:00
Julien Duponchelle
88834250c5 1.3.8dev1 2015-06-22 20:22:31 +02:00
Julien Duponchelle
0da15c21e6 Update changelog 2015-06-22 19:50:21 +02:00
grossmj
3076f98127 Merge remote-tracking branch 'origin/master' 2015-06-22 11:42:40 -06:00
grossmj
5a7f52b41f Makes sure Hub Ethernet port names are string. 2015-06-22 11:42:24 -06:00
grossmj
f403ff7776 Makes sure Hub Ethernet port names are string. 2015-06-22 11:39:35 -06:00
Julien Duponchelle
57998195f6 1.3.7 2015-06-22 19:04:10 +02:00
Julien Duponchelle
e873150542 Repare generation of password for local server
More generally this support dictonnary in dictionnary in
the config.

Fix #452
2015-06-22 17:22:29 +02:00
Julien Duponchelle
73440be270 Upgrade Travis to the last pyqt build 2015-06-22 15:11:56 +02:00
Julien Duponchelle
e8caa8853e Fix SSH support broken when adding the ram_limit
Test suite is Green again
2015-06-22 15:02:36 +02:00
Julien Duponchelle
4d63643fbf Fix Zoom reset is broken
Fix #448
2015-06-22 12:24:43 +02:00
Julien Duponchelle
c632303fd6 Add progressText argurment to HTTP query
When you load the VirtualBox VMs instead of
Waiting for local server you see:
List VirtualBox VMs

Related to #359
2015-06-22 12:13:34 +02:00
Julien Duponchelle
06596d8626 Hide the Load Balance settings when choosing GNS3VM
Fix #441
2015-06-22 11:24:20 +02:00
grossmj
e0a47b050a Avoid reload config loops. 2015-06-21 12:19:07 -06:00
grossmj
55bd9bbc7e RAM usage based load balancing. #419. 2015-06-21 11:49:49 -06:00
grossmj
a4c47b920c Creates a new "Servers" config section and moves "LocalServer", "RemoteServers" and "GNS3VM" under it. 2015-06-20 19:25:11 -06:00
grossmj
25355596bb Move "RecentFiles" and "GUI" settings under MainWindow settings. 2015-06-20 16:10:33 -06:00
grossmj
a40d128704 Fixes cosmetic issue: QSettings().fileName() returns paths containing slashes on Windows. 2015-06-20 15:15:16 -06:00
grossmj
2f0ab09250 Change the default configs directory to the new default location. 2015-06-20 15:13:51 -06:00
Julien Duponchelle
6be425de4d Avoid configuration reload loops
This avoid false detection of configuration file
changed.

And avoid to write partial settings to the configuration
file.

Fix #406
2015-06-20 23:08:19 +02:00
grossmj
aa55b984a2 Backport: support spaces in the local server log path. 2015-06-20 14:51:39 -06:00
grossmj
35725b2324 Support spaces in the local server log path. 2015-06-20 14:48:07 -06:00
grossmj
bb4a3487a2 Backport: fixes issue when setting the local server settings. 2015-06-20 11:42:39 -06:00
grossmj
2f51f985d8 Round-Robin load balancing support. #419. 2015-06-20 11:40:47 -06:00
Jeremy Grossmann
bacdca038e Merge pull request #446 from GNS3/progress_dialog_refactor
Refactor of the progress dialog.
2015-06-19 12:18:56 -07:00
grossmj
f80e190e22 Moves base configs to a dedicated directory (default is ~/GNS3/configs). Fixes #420. 2015-06-19 13:09:50 -06:00
grossmj
024aec1891 Fixes local server restart from the preferences. 2015-06-19 12:16:25 -06:00
Julien Duponchelle
b80f6cc507 Auto upload image if missing on remote server
Fix #378
2015-06-19 18:16:57 +02:00
Julien Duponchelle
be11046cfc Refactor of the progress dialog
setWindowModality(Qt.Qt.ApplicationModal) allow to have the progress
dialog modal for the app

Creation of a context block where you can temporary change the
behavior of the progress dialog.

Fix #359
2015-06-19 12:26:29 +02:00
Julien Duponchelle
0a0522d92d Fix a crash when uploading images 2015-06-19 12:17:01 +02:00
Jeremy
f436a34474 Fixes issue when looking vmrun on Windows. 2015-06-18 18:43:43 -06:00
Jeremy
7ab3884cb9 ACPI shutdown support for VMware VMs. Fixes #436. 2015-06-18 15:02:31 -06:00
Jeremy
b616be8c0e Fixes #442. 2015-06-18 14:45:00 -06:00
Julien Duponchelle
65431c462f Fix a typo in GNS3VM preferences
Fix #440
2015-06-18 14:44:28 +02:00
Julien Duponchelle
bc6ae0e773 Fix update issues with the configuration
Previoulsy we used a reference to settings instead a new one
this mean you can modify the settings and when writting the
settings we can't detect the configuration has changed.

Fix #431
2015-06-18 12:50:46 +02:00
Julien Duponchelle
5698b2eab9 Add timestamps to gns3_gui.log
Fix #426
2015-06-18 12:08:21 +02:00
Julien Duponchelle
4b1ff7deb5 Fix TypeError: 'NoneType' object is not callable in http_client
Fix #439
2015-06-18 11:59:14 +02:00
Julien Duponchelle
327c0d7a2e Fix NameError: name 'QtGui' is not defined
Fix #433
2015-06-18 11:57:50 +02:00
Julien Duponchelle
90522914c0 Fix a crash about server_name not a module 2015-06-18 11:34:29 +02:00
Julien Duponchelle
cf44a36153 Fix topology test 2015-06-18 11:30:01 +02:00
Julien Duponchelle
ba23cfdaca Store MD5 of images in topology
Fix #390
2015-06-18 11:26:29 +02:00
Julien Duponchelle
a85888bcbd Update crash report key for 1.3.7 2015-06-18 11:26:29 +02:00
grossmj
3d8c25159d Do not load settings that the GUI doesn't use. 2015-06-18 11:25:56 +02:00
grossmj
543f73bf7a Bump version to 1.3.7.dev1 2015-06-18 11:25:37 +02:00
Julien Duponchelle
4130082a8d 1.3.6 2015-06-18 11:24:47 +02:00
grossmj
692815713b Ubridge is not in 1.3.x 2015-06-18 11:24:35 +02:00
Julien Duponchelle
14bef07d25 1.3.6dev1 2015-06-18 11:24:03 +02:00
Julien Duponchelle
e9c69a118c 1.3.5 2015-06-18 11:23:39 +02:00
grossmj
275faea616 Prevent the local server to restart in Preferences if no settings have been changed. 2015-06-18 11:23:11 +02:00
Julien Duponchelle
f4268bb447 Do not crash in a very rare case on Windows when stoping local server
Fix #430
2015-06-18 11:21:39 +02:00
grossmj
35eeae7c58 Support to open projects that use the GNS3 VM. 2015-06-17 23:01:18 -06:00
Jeremy
ed04df26f8 Auto start and stop for VirtualBox GNS3 VM completed. Completes #387. 2015-06-17 19:23:19 -06:00
Jeremy
79efaad817 Fixes #427. 2015-06-17 18:43:23 -06:00
Jeremy
1075745439 GNS3 VM works from the GUI. 2015-06-17 18:38:22 -06:00
Jeremy
2f7255301d VMware topology validation. 2015-06-17 17:42:49 -06:00
Jeremy
fc60d50560 Comments code preventing to load projects. 2015-06-17 17:35:56 -06:00
Jeremy
488d32974f Find vmrun on Windows. 2015-06-17 17:05:58 -06:00
Jeremy
bdd12b262e Revert: do not load settings that the GUI doesn't use since it breaks something. 2015-06-17 15:06:39 -06:00
grossmj
ec8e645679 Merge remote-tracking branch 'origin/master' 2015-06-17 15:06:32 -06:00
grossmj
a239c923a3 Revert: do not load settings that GUI doesn't use since it breaks something. 2015-06-17 15:06:21 -06:00
Jeremy
e2fa8b3199 Port from 1.3.7: do not load settings that the GUI doesn't use. 2015-06-17 14:25:37 -06:00
Jeremy
2128f46165 Port from 1.3.7: fixes WICs are not displayed correctly. 2015-06-17 14:23:55 -06:00
Julien Duponchelle
1378cab008 Update crash report key for 1.3.7 2015-06-17 09:29:04 +02:00
Julien Duponchelle
65aca8ab76 Fix a crash with Python 3.3
Fix #435
2015-06-17 09:21:23 +02:00
grossmj
9db42c9783 Fixes WICs are not displayed correctly. Fixes #434. 2015-06-16 21:17:08 -06:00
grossmj
d9e551031d Do not load settings that the GUI doesn't use. 2015-06-16 21:09:44 -06:00
grossmj
554a163d7d Bump version to 1.3.7.dev1 2015-06-16 14:39:20 -06:00
grossmj
14bd2c6a3b Support to shutdown the GNS3 VM when closing GNS3. 2015-06-14 17:11:39 -06:00
grossmj
109ee591c1 Save local server logs to a logfile. 2015-06-14 16:02:50 -06:00
grossmj
c67cf56dc5 Prepare the GNS3 VM to be used in projects. 2015-06-14 16:01:34 -06:00
grossmj
727dcc149d Support to automatically start the GNS3 VM and retrieve the guest IP address. 2015-06-14 14:24:18 -06:00
grossmj
b450178444 Fixes issue with socket not properly closed. 2015-06-14 14:21:01 -06:00
grossmj
23e281689a Restore missing IPv6 support code. 2015-06-13 19:54:40 -06:00
Julien Duponchelle
f96eb630d1 Use the native glob.escape function
Fix #424
2015-06-12 15:12:37 +02:00
Julien Duponchelle
7cd16c7063 Merge branch 'master' into unstable 2015-06-12 15:11:24 +02:00
Julien Duponchelle
87a04193f9 Fix QIODevice::read: device not open
Fix #360
2015-06-12 14:22:52 +02:00
Julien Duponchelle
5c196d7e47 Fix QMessageBox.NoButton): argument 1 has unexpected type 'Servers'
Fix #423
2015-06-12 13:40:44 +02:00
grossmj
8db1b230d4 Merge remote-tracking branch 'origin/unstable' into unstable 2015-06-11 22:55:54 -06:00
grossmj
f92e98d587 Remove duplicate entries in node dictionaries. 2015-06-11 22:55:42 -06:00
Julien Duponchelle
9b3ed76fb0 SSL support
Fix #385
2015-06-11 21:16:57 +02:00
Julien Duponchelle
91780f0b9c Merge branch 'master' into unstable 2015-06-11 19:50:25 +02:00
Julien Duponchelle
0f7f7946cd More topology corruption check thanks again to Bernhard Ehlers 2015-06-11 18:37:14 +02:00
Julien Duponchelle
c4a0037956 Catch the sigint and sigterm signal and do a clean exit
Fix #374
2015-06-11 16:22:28 +02:00
Julien Duponchelle
586492fc92 Support saving qemu arch in topologies
Fix #401
2015-06-11 16:12:28 +02:00
Julien Duponchelle
cfe34628fa Support more topologies in JSON schema 2015-06-11 15:52:41 +02:00
Julien Duponchelle
1e2b7c7e02 Try to install Qt5 on Travis 2015-06-11 11:23:15 +02:00
Julien Duponchelle
c12a91ee5f Fix crash when you drag nodes with the shift key
Fix #421
2015-06-11 11:20:15 +02:00
Julien Duponchelle
c93a2dcb1c Fix ugly text when connecting to local GNS3 server
Fix #398
2015-06-11 11:13:50 +02:00
Julien Duponchelle
9baa529200 Use the correct qmake binary for travis 2015-06-11 10:59:15 +02:00
Julien Duponchelle
29bf1e5dc4 Schemas cleanup 2015-06-11 10:37:01 +02:00
Julien Duponchelle
0f1b78e1a5 More supported topology in corruption check
Thanks to @ehlers
2015-06-11 10:27:34 +02:00
Julien Duponchelle
d03820bf91 Fix errori in IOU config export 2015-06-11 09:32:31 +02:00
Jeremy
adbf7aeb42 Graphical base to manage the GNS3 VM. 2015-06-10 17:46:39 -06:00
Jeremy
b57bf29247 Removes settings types (was used for QSettings). 2015-06-10 15:11:19 -06:00
Julien Duponchelle
4a96468e42 Fix add image to topology
Fix #417
2015-06-10 17:49:54 +02:00
Julien Duponchelle
272c7850d7 Fix local server auto start
Fix #415
2015-06-10 17:39:50 +02:00
Julien Duponchelle
7466bda816 Change the location of the topology json schema
Fix #415
2015-06-10 17:04:38 +02:00
Julien Duponchelle
c202399eb6 Merge branch 'master' into unstable 2015-06-10 16:43:35 +02:00
Julien Duponchelle
3c046020ef Fix topology check for 3725
Fix #415
2015-06-10 16:22:28 +02:00
Julien Duponchelle
af3ab140bd Catch extraction error and exit
Fix #372
2015-06-10 11:38:37 +02:00
Julien Duponchelle
9d9d43a249 Add a specific icon for VPCS
Fix #410
2015-06-10 11:04:35 +02:00
Julien Duponchelle
5045447bc6 Ensure no colored log output on Windows 2015-06-10 10:33:34 +02:00
Jeremy
a9772dd313 Remember previously chosen directories for QFileDialog calls. Fixes #349. 2015-06-09 17:43:50 -06:00
Julien Duponchelle
59999abb61 Merge pull request #399 from GNS3/json_schema
JSON schema for checking topologies
2015-06-09 09:32:36 +02:00
grossmj
23a42b7c48 Enable KVM acceleration option. 2015-06-08 14:51:06 -06:00
grossmj
8bda1fe719 Removes some unneeded QtGui imports. 2015-06-08 14:29:42 -06:00
grossmj
207e55e869 Apply the result of the auto Idle-PC feature to other routers with the same IOS image. 2015-06-08 11:55:41 -06:00
grossmj
a09b8f1762 Fixes issues when setting MAC address for a Qemu VM or IOS router. 2015-06-07 22:18:41 -06:00
grossmj
28f23ae595 Fixes issue with Node Properties dialog when only 1 node is selected. 2015-06-07 22:17:39 -06:00
Jeremy Grossmann
da0dc31ed0 Merge pull request #405 from boenrobot/qemuImgPatch1
Made Qemu image wizard text agnostic
2015-06-07 14:38:46 -06:00
Julien Duponchelle
6a9873dbaa Fix an issue with partial config written 2015-06-07 22:31:06 +02:00
grossmj
c68d311f92 Merge remote-tracking branch 'origin/unstable' into unstable 2015-06-07 14:16:39 -06:00
grossmj
c148fa9000 Fixes style for QListWidget and QComboBox. Fixes #218. 2015-06-07 14:16:15 -06:00
Julien Duponchelle
26fc48ce14 Improve config change autodetect
Related to #406
2015-06-07 22:04:37 +02:00
grossmj
0eb7174183 Show in file manager (#260: to complete using the VM directory instead). 2015-06-07 11:53:53 -06:00
grossmj
248e8750e1 Open/save dialog is opened in project folder when importing/exporting configs. Fixes #299. 2015-06-07 11:23:33 -06:00
grossmj
6c240fc5d3 Adds debug to help with #368 2015-06-07 11:01:38 -06:00
Vasil Rangelov
def3d617b0 Made the Qemu image wizard text-agnostic (uses form element names as indicators, instead of their texts). 2015-06-07 17:48:26 +03:00
grossmj
c49314e755 IPv6 support. 2015-06-06 21:37:34 -06:00
grossmj
18a80d4fdd Fixes issue with QFileDialog.getSaveFileName() returning a tuple in PyQt5. 2015-06-06 15:20:49 -06:00
grossmj
195e136798 Adds contributor names to the about dialog. 2015-06-06 15:19:45 -06:00
grossmj
606e702e6f Import/Export support for IOU nvrams. 2015-06-06 15:15:03 -06:00
grossmj
aed7a6fbf3 Merge remote-tracking branch 'origin/master' into unstable 2015-06-05 14:56:15 -06:00
grossmj
bbb79bba0f Option to drop nvram & disk files for IOS routers in order to save disk space. 2015-06-05 14:54:22 -06:00
Julien Duponchelle
731a838c16 Another travis fix... 2015-06-05 18:05:25 +02:00
Julien Duponchelle
1a55c472e0 Fix PyQT download link 2015-06-05 17:57:42 +02:00
Julien Duponchelle
222b476d84 Add travis debug 2015-06-05 17:49:46 +02:00
Julien Duponchelle
0e42f31b88 Fix Travis PyQT5 2015-06-05 14:43:49 +02:00
Julien Duponchelle
d5b3f605f3 Fix IOU server edit
Fix #396
2015-06-05 14:41:20 +02:00
Julien Duponchelle
17471db248 JSON schema for checking topologies
Support:
* IOU
* Dynamips
* VPCS
* VirtualBox
* Qemu

VMWare is not supported

Tests schema are available in:
tests/schemas

And the test suite is auto generated from this directory you
can take a look to test_topology_check

Fix #342,#392,#384
2015-06-04 19:55:01 +02:00
Julien Duponchelle
e2cdff3604 Drop python 3.3 build 2015-06-04 10:48:18 +02:00
Julien Duponchelle
118b1a039b Merge branch 'master' into unstable 2015-06-04 10:46:27 +02:00
grossmj
ea8119f3ad Replace RuntimeError by SystemExit. 2015-06-03 19:58:58 -06:00
grossmj
9b0f548336 Support for base MAC address for Qemu VMs. 2015-06-03 14:52:49 -06:00
grossmj
e8a7c15fee Drop Python 3.3 2015-06-03 12:08:24 -06:00
Julien Duponchelle
6055127118 Fix tests after merge 2015-06-03 18:56:44 +02:00
Julien Duponchelle
81e4a402f2 Merge branch 'master' into unstable 2015-06-03 18:45:17 +02:00
Julien Duponchelle
ca975e4f94 Fix: _findDynamips() takes 0 positional arguments but 1 was given
Fix #382
2015-06-03 18:35:22 +02:00
grossmj
2e06972161 ACPI shutdown support for Qemu VMs. 2015-06-02 22:33:38 -06:00
grossmj
d6f26f78a5 ACPI shutdown support for VirtualBox VMs. 2015-06-02 16:30:35 -06:00
grossmj
ae53634f48 Catch exception in snapshot dialog. 2015-06-01 21:54:08 -06:00
Jeremy
09d8e1ce6b Rename node configurator to node properties. 2015-06-01 16:46:53 -06:00
Jeremy Grossmann
535069587e Merge pull request #381 from GNS3/doubleclick_label
If you doubleclick on a node label we open the change hostname dialog
2015-06-01 16:16:15 -06:00
Julien Duponchelle
5318fbaca1 If you doubleclick on a label we open the change hostname dialog
Corresponding idea:
https://community.gns3.com/ideas/1426
2015-06-01 15:54:12 +02:00
Julien Duponchelle
5f251c296e Merge branch 'master' into unstable
Fix #379
2015-06-01 10:54:09 +02:00
grossmj
291f87e197 Support for VMware linked clones. 2015-05-30 20:26:38 -06:00
Julien Duponchelle
cca86141fd Avoid recursion issue in config loading 2015-05-29 16:48:01 +02:00
Julien Duponchelle
84c4fb825c Merge branch 'master' into unstable 2015-05-28 17:58:46 +02:00
Julien Duponchelle
79272be631 Fix a small display issue introduce by the addtion of the ubridge path 2015-05-28 17:25:59 +02:00
Julien Duponchelle
4b9d03fb59 Detect config file change and reload the nodes
You can edit the list of devices in the config file
and the file will be reloaded. Usefull also if you run
two gns3-gui.
2015-05-28 16:54:39 +02:00
Julien Duponchelle
3c95e88f08 Typo same player build again .... 2015-05-28 13:19:21 +02:00
Julien Duponchelle
73ed4fa6f3 Do not crash if cacert is not found 2015-05-28 12:56:01 +02:00
Julien Duponchelle
6451f580cb Search resources relative to executable, avoid issue if run from another directory 2015-05-28 12:38:56 +02:00
Julien Duponchelle
dcf8a4948b Enable fault handler only for dev 2015-05-28 12:18:55 +02:00
Julien Duponchelle
3458cec41e Enable fault handler for dev version 2015-05-28 12:15:33 +02:00
Julien Duponchelle
809008561f Fix merge crash 2015-05-28 12:15:16 +02:00
Julien Duponchelle
72d60e1227 Merge branch 'master' into unstable 2015-05-28 12:01:01 +02:00
Julien Duponchelle
8fc335718a Fix cacert path on OSX 2015-05-28 11:21:05 +02:00
Julien Duponchelle
e6129ad78b Fix GNS3 server location for OSX 2015-05-28 11:17:56 +02:00
grossmj
10802d6a2c Serial console implementation for VMware VMs. 2015-05-27 21:06:18 -06:00
grossmj
7b05d26d7e Removes legacy QSettings system because it can cause issues (1.3.x was the transition). 2015-05-27 19:54:45 -06:00
grossmj
fa8d67ebe1 Ubridge configuration support. 2015-05-27 19:37:31 -06:00
grossmj
0d320c26cd Adds contributor to the About dialog. 2015-05-27 17:18:59 -06:00
Jeremy Grossmann
96c0276a0e Merge pull request #353 from boenrobot/qemuImg
Qemu-img wizard.
2015-05-27 17:07:34 -06:00
Vasil Rangelov
3cc5a8ae5c Adds a wizard for creating images with qemu-img and mofified qemu configuration page to use it. 2015-05-28 00:21:01 +03:00
Julien Duponchelle
5b0ca03640 Fix tests 2015-05-27 18:48:18 +02:00
Julien Duponchelle
0af08cf578 Merge pull request #369 from GNS3/download_project
Download remote project with md5 support
2015-05-26 09:46:33 +02:00
Julien Duponchelle
dfe21c5b8c Prevent downloading a running project 2015-05-26 09:46:21 +02:00
Julien Duponchelle
f8c5da52f3 Download remote project with md5 support 2015-05-26 09:46:21 +02:00
Julien Duponchelle
20752bf48e Merge pull request #357 from GNS3/remote_ssh
Support connection to GNS3 servers via SSH tunnels
2015-05-26 09:45:12 +02:00
Julien Duponchelle
7778790bae SSH 2015-05-26 09:44:57 +02:00
grossmj
fccbc90307 Avoid moving .gns3_temporary files. 2015-05-25 16:58:51 -06:00
grossmj
c5895a7d21 Merge remote-tracking branch 'origin/unstable' into unstable 2015-05-21 21:49:32 -06:00
grossmj
9262c8527b VMware vmnets management almost complete. 2015-05-21 21:48:59 -06:00
Julien Duponchelle
32484570bb PEP8 2015-05-21 11:41:50 +02:00
Julien Duponchelle
f26f6c33d0 Fix tests 2015-05-21 11:39:32 +02:00
Julien Duponchelle
ec2db0594e Pep 8 2015-05-18 17:25:12 +02:00
Julien Duponchelle
a6f3f425c3 Qt4 -> Qt5 fix
Fix #363
2015-05-18 17:25:12 +02:00
Jeremy
4566fec58a Adapters for VMware VMs. 2015-05-15 19:09:48 -06:00
Jeremy Grossmann
a362206e55 Merge pull request #362 from AdrianSimionov/patch-1
Adds support for IOS-XRv under Qemu wizard.
2015-05-15 09:40:36 -06:00
Julien Duponchelle
a9543e50f2 Correct Qemu browse button errors introduce in 1.4dev1
Fix #356
2015-05-15 16:10:33 +02:00
Julien Duponchelle
06fc3e726a New crash report key 2015-05-15 13:38:23 +02:00
Julien Duponchelle
e19a2ac67a Bump to 1.3.4 2015-05-15 13:38:22 +02:00
Julien Duponchelle
5e28fb5246 Version 1.3.3 2015-05-15 13:37:55 +02:00
grossmj
2ad9fcf701 New inline help text for the idle-pc dialog. 2015-05-15 13:37:42 +02:00
Jeremy
427b38912f Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template. 2015-05-15 13:37:04 +02:00
Jeremy
5d4619a70a Adds name to the thank you section. 2015-05-15 13:36:33 +02:00
Jeremy
5e56f27e45 Prevent users to use VirtualBox linked clone VMs in temporary projects (for now). 2015-05-15 13:34:31 +02:00
Jeremy
92fca450f1 Fixes #337. 2015-05-15 13:34:31 +02:00
grossmj
e52f8bbbde Fixes VMware Fusion local server deactivation. 2015-05-14 21:03:54 -06:00
grossmj
94c6ccb549 Remove unused export.svg file. 2015-05-14 20:46:53 -06:00
grossmj
a8edd8ffb9 No support for VMware Fusion (for now). 2015-05-14 20:14:53 -06:00
Julien Duponchelle
298768f0cb Fix crash 2015-05-14 18:55:22 +02:00
Jeremy
b566098a56 Adds missing icons in resources file. Fixes #343. 2015-05-13 13:47:46 -06:00
Jeremy
f8f34e46e3 Build resources. 2015-05-13 13:43:24 -06:00
Jeremy
9227831922 Adds missing icons. 2015-05-13 13:40:19 -06:00
Julien Duponchelle
625d4c951e Merge branch 'master' into unstable 2015-05-13 19:03:27 +02:00
Julien Duponchelle
5cdc479029 Fix a crash when right click in create link mode 2015-05-13 14:03:39 +02:00
Adrian Simionov
d385585291 Removed -enable-kvm option as it is linux specific. 2015-05-12 12:57:26 +02:00
Adrian Simionov
248107bfb8 Add support for IOS-XRv under qemu wizard.
I used router SVG file but I propose to be changed to something more appropriate for IOS-XRv.
2015-05-12 10:54:43 +02:00
Julien Duponchelle
539e336fa1 Fix notification stream
Fix #352
2015-05-11 17:26:02 +02:00
Julien Duponchelle
8ab07563e0 Merge branch 'master' into unstable 2015-05-11 14:39:22 +02:00
grossmj
487332df40 Some cleaning. 2015-05-10 20:26:43 -06:00
Julien Duponchelle
68d156c0e8 Merge pull request #338 from boenrobot/zoomFix
CTRL+Wheel fix
2015-05-10 21:23:19 +02:00
Julien Duponchelle
a9a5622525 Merge branch 'boenrobot-zoomFix' into unstable 2015-05-10 21:22:43 +02:00
Vasil Rangelov
9e02950844 Fixes the CTRL+Wheel zoom (Qt5 misses "orientation" for some reason, and has renamed delta() to angleDelta()). 2015-05-10 21:22:03 +02:00
Julien Duponchelle
a1730c9524 Fix node dropping
Fix #348
2015-05-10 21:19:35 +02:00
Julien Duponchelle
ed3257399b Merge pull request #302 from GNS3/upload_image
Upload image via the GUI
2015-05-10 20:47:06 +02:00
Julien Duponchelle
763f65cbbe Upload images from gui 2015-05-10 20:46:45 +02:00
grossmj
26675eac64 Fixes issue with callback in HTTP Client. 2015-05-09 17:32:46 -06:00
Jeremy Grossmann
4789be70fc Merge pull request #303 from GNS3/listen_for_notification
Listen for VM notifications.
2015-05-09 17:26:09 -06:00
Jeremy Grossmann
2b0168296f Merge pull request #341 from boenrobot/alphaText
Allows alpha channel for text color.
2015-05-09 12:06:42 -06:00
grossmj
2a8868e029 Replaced old style signal connections that don't work in PyQt5. 2015-05-08 18:22:07 -06:00
grossmj
7410abf895 Adds missing cloud configuration page. 2015-05-08 14:00:33 -06:00
Vasil Rangelov
86d0c3bfcb Text can now has an alpha channel, allowing for transparent or semi-transparent text. 2015-05-08 15:49:20 +03:00
Julien Duponchelle
fb14747a8b Merge branch 'master' into unstable 2015-05-08 14:40:12 +02:00
Julien Duponchelle
d11fbc1069 Merge branch 'boenrobot-singleNodeConfig' into unstable 2015-05-07 09:52:40 +02:00
Julien Duponchelle
b48d8d4372 Merge branch 'singleNodeConfig' of https://github.com/boenrobot/gns3-gui into boenrobot-singleNodeConfig 2015-05-07 09:49:54 +02:00
Julien Duponchelle
e257a64772 Merge branch 'boenrobot-winPathFix' into unstable 2015-05-07 09:32:20 +02:00
Vasil Rangelov
a6b1961d77 Patched the file and folder dialogs (in both Qt4 and Qt5) to replace any "/" with the OS folder seprator (notably useful on Windows). 2015-05-07 00:13:28 +03:00
Vasil Rangelov
d363212191 The device list in the configuration dialog is hidden by default when only one device is selected. 2015-05-06 21:14:33 +03:00
Vasil Rangelov
266bf202bb Fixes the CTRL+Wheel zoom (Qt5 misses "orientation" for some reason, and has renamed delta() to angleDelta()). 2015-05-06 18:30:51 +03:00
Vasil Rangelov
f280445138 Adds multi select support in all device template pages.
* Also adds "Delete" as part of the context menu of devices.
* (Some methods are reordered for consistency; Legacy signaling is fixed to the new one where encountered)
* Patched build_pyqt.py for Windows, and for it to remove the path of the .ui file in the comment.
* Shrinked the minimum width of all preference dialogs.
* Removed zorder nodes (PyQt5 complains...) and regenerated all preference dialogs.
2015-05-06 14:42:59 +02:00
Julien Duponchelle
fcd93c8db8 Merge branch 'master' into unstable 2015-05-06 13:41:47 +02:00
Julien Duponchelle
d02f0bf5b4 Merge branch 'master' into unstable 2015-05-06 13:39:20 +02:00
Julien Duponchelle
986734e29b Merge branch 'boenrobot-dbClickToConfigure' into unstable 2015-05-06 13:28:23 +02:00
Julien Duponchelle
147d9dbe83 Merge branch 'dbClickToConfigure' of https://github.com/boenrobot/gns3-gui into boenrobot-dbClickToConfigure 2015-05-06 13:25:37 +02:00
Julien Duponchelle
c974853fe8 Merge branch 'boenrobot-readmeAndReq' into unstable 2015-05-06 13:24:42 +02:00
Julien Duponchelle
d124f9c8e0 Merge branch 'readmeAndReq' of https://github.com/boenrobot/gns3-gui into boenrobot-readmeAndReq 2015-05-06 13:24:28 +02:00
Julien Duponchelle
cb9222271d Merge error 2015-05-06 13:23:42 +02:00
Julien Duponchelle
72505b550d Merge branch 'master' into unstable 2015-05-06 13:22:24 +02:00
Julien Duponchelle
7500b602dc Merge branch 'master' into unstable 2015-05-05 14:21:34 +02:00
Vasil Rangelov
89410cc55d Modified version requirements, so that they require the dependency versions as minimums.
Added some more detailed instructions for compilation on Windows.
2015-05-04 23:33:23 +03:00
Vasil Rangelov
9d3b17d86b Adjusted the double click action so that a click on a stopped node opens the configuration dialog with all selected nodes and a double click on a started node consoles to all selected devices. 2015-05-04 23:23:19 +03:00
Julien Duponchelle
553e3b02f5 Fix about dialog with PyQT4 by turning off translation
Fix #312
2015-05-04 12:08:56 +02:00
Julien Duponchelle
756f1dd218 Merge branch 'master' into unstable 2015-05-04 09:37:29 +02:00
Julien Duponchelle
accf332668 Fix crash 2015-05-04 09:36:11 +02:00
grossmj
4b0b5cb85c List all available VMware VMs. 2015-05-01 18:47:46 -06:00
Jeremy
23ad776a74 Basic VMware support (start & stop a VM). 2015-04-30 19:05:37 -06:00
Jeremy
02cc9ec935 Old style signals are not supported in PyQt5. 2015-04-30 18:24:01 -06:00
Julien Duponchelle
1d0c21eff1 Listen for notifications from servers. 2015-04-30 17:29:55 +02:00
Julien Duponchelle
422c97f681 Support for reading an HTTP stream 2015-04-30 17:29:55 +02:00
Julien Duponchelle
f0d00420c3 Fix tests 2015-04-30 17:29:36 +02:00
Julien Duponchelle
64c1eac2d0 Merge branch 'master' into unstable 2015-04-30 16:59:39 +02:00
Julien Duponchelle
ae7c98ce78 Merge branch 'master' into unstable
Conflicts:
	gns3/version.py
2015-04-29 14:27:55 +02:00
Julien Duponchelle
e04fc7d950 Fix an invalid import 2015-04-28 11:08:36 +02:00
Julien Duponchelle
97f75fb971 Merge branch 'master' into unstable
Conflicts:
	gns3/graphics_view.py
	gns3/http_client.py
	gns3/main_window.py
	gns3/modules/dynamips/dialogs/ios_router_wizard.py
	gns3/modules/dynamips/pages/ios_router_preferences_page.py
	gns3/modules/vpcs/pages/vpcs_device_configuration_page.py
	gns3/utils/file_copy_worker.py
	gns3/utils/process_files_worker.py
	gns3/utils/progress_dialog.py
	gns3/utils/wait_for_connection_worker.py
2015-04-28 11:01:16 +02:00
Jeremy
0e2c411198 Removes unneeded progress dialog in VMWizard. 2015-04-24 17:03:21 -06:00
Jeremy
ca7917b407 Merge remote-tracking branch 'origin/unstable' into unstable 2015-04-24 14:40:31 -06:00
Jeremy Grossmann
69013d2765 Merge pull request #281 from GNS3/api_list_images
Load list of images from the server.
2015-04-24 14:39:39 -06:00
Jeremy
8a022a2365 Merge remote-tracking branch 'remotes/origin/unstable' into api_list_images
Conflicts:
	gns3/modules/iou/dialogs/iou_device_wizard.py
2015-04-24 14:39:10 -06:00
Jeremy
0d07af4862 Merge branch 'api_list_images' into unstable 2015-04-24 14:35:38 -06:00
Jeremy
d5279fb50c Rename DeviceWizard to VMWizard. 2015-04-24 14:32:05 -06:00
Julien Duponchelle
7d887bdbef Cleanup 2015-04-24 21:55:08 +02:00
Julien Duponchelle
d12770f97c Drop QEMU_BINARIES_FOR_CLOUD because we have generic implementation now 2015-04-24 18:50:36 +02:00
Julien Duponchelle
a3d9efbda2 Show / hide dock widget experimental 2015-04-24 18:45:17 +02:00
Julien Duponchelle
3f6479e578 Merge branch 'master' into unstable 2015-04-24 18:44:24 +02:00
Julien Duponchelle
5d8d8a5ffe Load list of images from the server
Support Qemu, IOU and Dynamips

* IOU: Choose l2 or l3 depending of user input
* Refactor Qemu, IOU and Dynamips wizard to share common code
* Dynamips set the correct platform in all cases
2015-04-24 10:58:48 +02:00
Julien Duponchelle
9d511e0370 Merge branch 'master' into unstable
Conflicts:
	gns3/modules/dynamips/pages/ios_router_preferences_page.py
	gns3/modules/dynamips/utils/decompress_ios_thread.py
2015-04-24 10:36:41 +02:00
Jeremy Grossmann
73d26b45fc Merge pull request #286 from GNS3/wireshark_remote_capture
Wireshark remote packet capture support.
2015-04-23 16:47:48 -06:00
Jeremy
03d1cc4f78 Completes remote packet capture support. 2015-04-23 16:46:47 -06:00
Jeremy Grossmann
49c89810d8 Merge pull request #283 from GNS3/qt5
PyQt5 support with backward PyQt4 compatibility.
2015-04-23 16:13:13 -06:00
Jeremy
4dc3647370 Completes PyQt5 support with backward PyQt4 compatibility. 2015-04-23 16:10:22 -06:00
Julien Duponchelle
6ab00e46b2 Migration to QT5
I drop some related cloud support, we will restore it when we will be ready.
* Switch to Python3 super style
* Drop old object inheritance
* Fixed old super call
2015-04-23 12:24:26 +02:00
Julien Duponchelle
ec22d72f3f Merge branch 'master' into unstable 2015-04-23 11:41:58 +02:00
Julien Duponchelle
97324ce4d4 Wireshark remote packet capture 2015-04-21 18:20:54 +02:00
Julien Duponchelle
a11d84e812 Merge branch 'master' into unstable 2015-04-21 18:17:53 +02:00
Julien Duponchelle
1d7774bcd6 Support for reading an HTTP stream 2015-04-21 12:45:48 +02:00
Julien Duponchelle
806a8efe94 Merge branch 'master' into unstable 2015-04-20 17:25:35 +02:00
Julien Duponchelle
b0894b1e75 Merge branch 'master' into unstable 2015-04-17 15:07:57 +02:00
Julien Duponchelle
92faccdd90 Merge branch 'master' into unstable 2015-04-16 18:38:35 +02:00
Julien Duponchelle
7025accd88 1.4.0 dev1 2015-04-13 15:56:41 +02:00
472 changed files with 180125 additions and 232919 deletions

9
.gitignore vendored
View File

@@ -37,6 +37,7 @@ nosetests.xml
# PyCharm
.idea
/.eggs
# OSX
.DS_Store
@@ -50,3 +51,11 @@ nosetests.xml
# Qt creator
*.autosave
# Licence keys
keys
# Custom config
/gns3_server.ini
updates
.cache

View File

@@ -1,33 +1,19 @@
language: python
sudo: required
#New container architecture
#http://docs.travis-ci.com/user/workers/container-based-infrastructure/
#sudo: false
python:
- "3.3"
- "3.4"
cache:
apt: true
directories:
- build
before_install:
- sh scripts/prepare_travis.sh
services:
- docker
notifications:
email:
- julien@gns3.net
email: false
#email:
# - julien@gns3.net
#irc:
# channels:
# - "chat.freenode.net#gns3"
# 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

View File

@@ -1 +1,2 @@
Jeremy Grossmann
Jeremy Grossmann
Julien Duponchelle

573
CHANGELOG
View File

@@ -1,5 +1,578 @@
# Change Log
## 1.4.3 19/02/2016
* Allow idlepc 0x0 in topology
* Show an explicit error message when status code 0 is returned. Fixes #1034.
* Fixes minor bug when dropping a VirtualBox VM on the scene. Fixes #748.
* Correctly check local server if only local is available in vm wizard
* Make the GNS3 VM server running value more reliable
* Make VM configuration dialog modal
* Cannot take GIF screenshots (write is not supported by Qt).
## 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
* CLear the list before asking for VM list for Vbox and Vmware
* Fix password lost for remote servers
* Fix schema for virtualbox 1.3.2 topologies
* Refactor all VM wizards
* Fix Apply not enabled when removing a remote server
* Fix remote server list display when use_local_server for Qemu
* Fixes #601 (spelling error).
* Store the url of server in gns3_gui for third party apps
* Fix error when editing a qemu device with a relative path
* Catch exception when starting packet capture reader for a remote packet capture. Fixes #597.
* Fixes KeyError: 'vmx_path'. Fixes #595.
* Support for CPUs setting for Qemu VMs.
* Fix chicken of VNC command and add a warning about bugs in OSX VNC
* Fix issue whith auto update when we release a new build
## 1.4.0alpha4 04/08/2015
* Use half the available physical memory for the GNS3 VM in the Setup Wizard.
* Drop useless notion of resource_type
* Fix When you modify config from outside router list is not refresh
* Sync auth settings between 1.3 and 1.4
* Group HTTP bad request by path
* Disallow connection to a different major version in all cases
* Show the server choice in wizard if you have a VM or a remote server
* Fix local server settings erased at each launch
* Support for Qemu disk interfaces, cd/dvd-rom image and boot priority.
* Prevents progress dialog to stay displayed (fixes graphical bug).
* Check that DHCP is enabled on the VirtualBox host-only network associated with the VirtualBox GNS3 VM. Fixes #287.
* Fixes Qt5 incompatibility.
* Catch exception when trying to launch Wireshark.
* Fixes migration of cloud interfaces. Fixes #582.
* Wait for the server to be fully started in the GNS3 VM. Fixes #581.
* Fix issue with file upload and Qt 5.5
* Improves the symbol dialog. Implements #514.
## 1.3.9 03/08/2015
* Catch exception when trying to launch Wireshark.
* Backport: fixes migration of cloud interfaces.
## 1.4.0alpha3 28/07/2015
* IOUVM converter
* Create qemu disk image on remote server
* Fix _addRemoteServer() got an unexpected keyword argument 'local'
* Do not crash if configuration file is removed
* Fixes rare issue when adding a link. Fixes #573.
* Fixes crash with PyQt 5.5. Fixes #568.
* Changes how to look for the vmrun.exe location.
* Inform user before exiting preferences dialog with changes
* Write GNS3 upgrade to appdata
* Fix windows asking for upgrade to the wrong version
## 1.3.8 27/07/2015
* Fixes rare issue when adding a link. Fixes #573.
* Backport: option to drop nvram & disk files for IOS routers in order to save disk space.
* Avoid the creation of a NIO when one has been cancelled.
* Fix Crash with chinese characters
* Use the same location for the server config on GUI and server
* Catch invalid reply from the remote server
## 1.4.0alpha2 22/07/2015
* Cloud support with the GNS3 VM.
* Display an error message when Qemu binaries cannot be retrieved in the Qemu VM configuration page.
* Remove default FLASH when no hda disk for Qemu VMs. Fixes #535.
* Use the registry to find vmrun if the default VMware install path doesn't exist. Fixes #546.
* Avoid the creation of a NIO when one has been cancelled.
* Fix Crash with chinese characters
* Display an error if terminal command is invalid
* Prevents "Show in File Manager" to be used with generic switches.
* Remove unused dependencies
* Drop PyQt4 support and show an error for users
* Fixes symbol for VM template gone after restart. Fixes #538.
* Fix VirtualBox GNS3 VM
* Fix issue with remote server not saved/migrated
* Remove ram as a mandatory dynamips settings
* Force UTF-8 when reading server configuration file
## 1.4.0alpha1 09/07/2015
* Remove unused cloud code from the 1.4
* Setup Wizard (to be tweaked). Implements #402.
* Adds -no-kvm to the ASA template and ignore -no-kvm on platforms other than Linux. Should resolve #472.
* Explicitly set the acceleration method to tcg for ASA templates. Should resolve #472.
* Show an error if the console port range overlaps the default VNC port range (5900 to 6000) in the server preferences.
* Support self update of the application
* Option to adjust the local server IP address to be in the same subnet as the GNS3 VM.
* Warning about deprecated ASA on Qemu
* Moves KVM setting to Qemu preferences.
* VNC console support for Qemu VMs. Implements #447.
* Change the location of the config file on OSX
* Adds first port name option (for management interfaces). Completes #309.
* Add a force quit button when closing the app
* Basic auth support for remote servers
* Adds symbol overview in tooltips for all symbol text fields.
* Remove SVG icons used in hover events.
* Support for custom symbols
* RAM usage based load balancing. #419.
* Creates a new "Servers" config section and moves "LocalServer", "RemoteServers" and "GNS3VM" under it.
* Support spaces in the local server log path.
* Round-Robin load balancing support. #419.
* Auto upload image if missing on remote server
* ACPI shutdown support for VMware VMs. Fixes #436.
* Add timestamps to gns3_gui.log
* Store MD5 of images in topology
* SSL support
* GNS3 VM support
* Add a specific icon for VPCS
* Ensure no colored log output on Windows
* Enable KVM acceleration option.
* Apply the result of the auto Idle-PC feature to other routers with the same IOS image.
* Improve config change autodetect
* Show in file manager (#260: to complete using the VM directory instead).
* Open/save dialog is opened in project folder when importing/exporting configs. Fixes #299.
* IPv6 support.
* Import/Export support for IOU nvrams.
* Option to drop nvram & disk files for IOS routers in order to save disk space.
* Fix IOU server edit
* JSON schema for checking topologies
* Support for base MAC address for Qemu VMs.
* Drop Python 3.3
* ACPI shutdown support for Qemu VMs.
* ACPI shutdown support for VirtualBox VMs.
* Rename node configurator to node properties.
* Merge pull request #381 from GNS3/doubleclick_label
* If you doubleclick on a label we open the change hostname dialog
* Fix GNS3 server location for OSX
* Serial console implementation for VMware VMs.
* Ubridge configuration support.
* Adds a wizard for creating images with qemu-img and mofified qemu configuration page to use it.
* Download remote project with md5 support
* SSH support
* Avoid moving .gns3_temporary files.
* New inline help text for the idle-pc dialog.
* Add support for IOS-XRv under qemu wizard.
* Upload images from gui
* Text can now has an alpha channel, allowing for transparent or semi-transparent text.
* The device list in the configuration dialog is hidden by default when only one device is selected.
* Adds multi select support in all device template pages.
* Adjusted the double click action so that a click on a stopped node opens the configuration dialog with all selected nodes and a double click on a started node consoles to all selected devices.
* VMware support for Windows and Linux
* Listen for notifications from servers.
* Migration to QT5
* Wireshark remote packet capture
## 1.3.7 22/06/2015
* Makes sure Hub Ethernet port names are string.
* Support spaces in the local server log path.
* Fixes issue when setting the local server settings.
* Fix a crash with Python 3.3
* Fixes WICs are not displayed correctly. Fixes #434.
* Do not load settings that the GUI doesn't use.
## 1.3.6 16/06/2015
* Fix an issue with 1.4dev compatibility

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,9 +18,9 @@ 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 libraries
- PyQt 5 libraries
- Apache Libcloud library
- Requests library
- Paramiko library
@@ -30,6 +30,13 @@ The following commands will install some of these dependencies:
.. code:: bash
sudo apt-get install python3-setuptools
sudo apt-get install python3-pyqt5
sudo apt-get install python3-pyqt5.qtsvg
sudo apt-get install python3-pyqt5.qtwebkit
If you want to test using PyQt4
.. code:: bash
sudo apt-get install python3-pyqt4
Finally these commands will install the GUI as well as the rest of the dependencies:
@@ -43,14 +50,14 @@ 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:
- Python (3.3 or above) - https://www.python.org/downloads/windows/
- Pywin32 - https://sourceforge.net/projects/pywin32/
- Qt4 - http://www.qt.io/download-open-source/
- PyQt4 - http://www.riverbankcomputing.com/software/pyqt/download
- Qt5 - http://www.qt.io/download-open-source/
- PyQt5 - http://www.riverbankcomputing.com/software/pyqt/download5
- PyCrypto (which if you compile from source, requires Visual Studio 2010 with GMP or MPIR libraries)
And finally, call
@@ -75,6 +82,11 @@ Then install the GNS3 dependencies.
brew install python3
brew install qt
brew install sip --without-python --with-python3
brew install pyqt5 --without-python --with-python3
If you want to test using PyQt4
.. code:: bash
brew install pyqt --without-python --with-python3
Finally, install both the GUI & server from the source.
@@ -91,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:
@@ -100,3 +112,31 @@ 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
~~~~~~~~~~~~~~~~
If you want to simulate a user with PyQT4:
.. code:: bash
export GNS3_QT4=1
python gns3/main.py

38
fake_frozen_gns3.py Executable file
View File

@@ -0,0 +1,38 @@
#!/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/>.
"""
This script fake GNS3 run as a frozen app.
Use it for testing stuff like self update.
"""
import os
import sys
import importlib
# Fake GNS3 run from a binary
sys.executable = os.path.realpath(__file__)
# Add site-package directory before cx_freeze directory
sys.path.insert(0, os.path.dirname(sys.executable))
sys.path.insert(0, os.path.join(os.path.dirname(sys.executable), 'site-packages'))
sys.frozen = True
module = importlib.import_module("gns3.main")
module.main()

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

@@ -1,337 +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/>.
"""
Base cloud controller class.
Base class for interacting with Cloud APIs to create and manage cloud
instances.
"""
from collections import namedtuple
import hashlib
import os
import logging
from io import StringIO, BytesIO
from libcloud.compute.base import NodeAuthSSHKey
from libcloud.storage.types import ContainerAlreadyExistsError, ContainerDoesNotExistError, ObjectDoesNotExistError
from .exceptions import ItemNotFound, KeyPairExists, MethodNotAllowed
from .exceptions import OverLimit, BadRequest, ServiceUnavailable
from .exceptions import Unauthorized, ApiError
KeyPair = namedtuple("KeyPair", ['name'], verbose=False)
log = logging.getLogger(__name__)
def parse_exception(exception):
"""
Parse the exception to separate the HTTP status code from the text.
Libcloud raises many exceptions of the form:
Exception("<http status code> <http error> <reponse body>")
in lieu of raising specific incident-based exceptions.
"""
e_str = str(exception)
try:
status = int(e_str[0:3])
error_text = e_str[3:]
except ValueError:
status = None
error_text = e_str
return status, error_text
class BaseCloudCtrl(object):
""" Base class for interacting with a cloud provider API. """
http_status_to_exception = {
400: BadRequest,
401: Unauthorized,
404: ItemNotFound,
405: MethodNotAllowed,
413: OverLimit,
500: ApiError,
503: ServiceUnavailable
}
GNS3_CONTAINER_NAME = 'GNS3'
def __init__(self, username, api_key):
self.username = username
self.api_key = api_key
def _handle_exception(self, status, error_text, response_overrides=None):
""" Raise an exception based on the HTTP status. """
if response_overrides:
if status in response_overrides:
raise response_overrides[status](error_text)
raise self.http_status_to_exception[status](error_text)
def authenticate(self):
""" Validate cloud account credentials. Return boolean. """
raise NotImplementedError
def list_sizes(self):
""" Return a list of NodeSize objects. """
return self.driver.list_sizes()
def list_flavors(self):
""" Return an iterable of flavors """
raise NotImplementedError
def create_instance(self, name, size_id, image_id, keypair):
"""
Create a new instance with the supplied attributes.
Return a Node object.
"""
try:
image = self.get_image(image_id)
if image is None:
raise ItemNotFound("Image not found")
size = self.driver.ex_get_size(size_id)
args = {
"name": name,
"size": size,
"image": image,
}
if keypair is not None:
auth_key = NodeAuthSSHKey(keypair.public_key)
args["auth"] = auth_key
args["ex_keyname"] = name
return self.driver.create_node(**args)
except Exception as e:
status, error_text = parse_exception(e)
if status:
self._handle_exception(status, error_text)
else:
log.error("create_instance method raised an exception: {}".format(e))
log.error('image id {}'.format(image_id))
def delete_instance(self, instance):
""" Delete the specified instance. Returns True or False. """
try:
return self.driver.destroy_node(instance)
except Exception as e:
status, error_text = parse_exception(e)
if status:
self._handle_exception(status, error_text)
else:
raise e
def get_instance(self, instance):
""" Return a Node object representing the requested instance. """
for i in self.driver.list_nodes():
if i.id == instance.id:
return i
raise ItemNotFound("Instance not found")
def list_instances(self):
""" Return a list of instances in the current region. """
return self.driver.list_nodes()
def create_key_pair(self, name):
""" Create and return a new Key Pair. """
response_overrides = {
409: KeyPairExists
}
try:
return self.driver.create_key_pair(name)
except Exception as e:
status, error_text = parse_exception(e)
if status:
self._handle_exception(status, error_text, response_overrides)
else:
raise e
def delete_key_pair(self, keypair):
""" Delete the keypair. Returns True or False. """
try:
return self.driver.delete_key_pair(keypair)
except Exception as e:
status, error_text = parse_exception(e)
if status:
self._handle_exception(status, error_text)
else:
raise e
def delete_key_pair_by_name(self, keypair_name):
""" Utility method to incapsulate boilerplate code """
kp = KeyPair(name=keypair_name)
return self.delete_key_pair(kp)
def list_key_pairs(self):
""" Return a list of Key Pairs. """
return self.driver.list_key_pairs()
def upload_file(self, file_path, cloud_object_name):
"""
Uploads file to cloud storage (if it is not identical to a file already in cloud storage).
:param file_path: path to file to upload
:param cloud_object_name: name of file saved in cloud storage
:return: True if file was uploaded, False if it was skipped because it already existed and was identical
"""
try:
gns3_container = self.storage_driver.create_container(self.GNS3_CONTAINER_NAME)
except ContainerAlreadyExistsError:
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
with open(file_path, 'rb') as file:
local_file_hash = hashlib.md5(file.read()).hexdigest()
cloud_hash_name = cloud_object_name + '.md5'
cloud_objects = [obj.name for obj in gns3_container.list_objects()]
# if the file and its hash are in object storage, and the local and storage file hashes match
# do not upload the file, otherwise upload it
if cloud_object_name in cloud_objects and cloud_hash_name in cloud_objects:
hash_object = gns3_container.get_object(cloud_hash_name)
cloud_object_hash = ''
for chunk in hash_object.as_stream():
cloud_object_hash += chunk.decode('utf8')
if cloud_object_hash == local_file_hash:
return False
file.seek(0)
self.storage_driver.upload_object_via_stream(file, gns3_container, cloud_object_name)
self.storage_driver.upload_object_via_stream(StringIO(local_file_hash), gns3_container, cloud_hash_name)
return True
def list_projects(self):
"""
Lists projects in cloud storage
:return: Dictionary where project names are keys and values are names of objects in storage
"""
try:
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
projects = {
obj.name.replace('projects/', '').replace('.zip', ''): obj.name
for obj in gns3_container.list_objects()
if obj.name.startswith('projects/') and obj.name[-4:] == '.zip'
}
return projects
except ContainerDoesNotExistError:
return []
def download_file(self, file_name, destination=None):
"""
Downloads file from cloud storage. If a file exists at destination, and it is identical to the file in cloud
storage, it is not downloaded.
:param file_name: name of file in cloud storage to download
:param destination: local path to save file to (if None, returns file contents as a file-like object)
:return: A file-like object if file contents are returned, or None if file is saved to filesystem
"""
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
storage_object = gns3_container.get_object(file_name)
if destination is not None:
if os.path.isfile(destination):
# if a file exists at destination and its hash matches that of the
# file in cloud storage, don't download it
with open(destination, 'rb') as f:
local_file_hash = hashlib.md5(f.read()).hexdigest()
hash_object = gns3_container.get_object(file_name + '.md5')
cloud_object_hash = ''
for chunk in hash_object.as_stream():
cloud_object_hash += chunk.decode('utf8')
if local_file_hash == cloud_object_hash:
return
storage_object.download(destination)
else:
contents = b''
for chunk in storage_object.as_stream():
contents += chunk
return BytesIO(contents)
def find_storage_image_names(self, images_to_find):
"""
Maps names of image files to their full name in cloud storage
:param images_to_find: list of image names to find
:return: A dictionary where keys are image names, and values are the corresponding names of
the files in cloud storage
"""
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
images_in_storage = [obj.name for obj in gns3_container.list_objects() if obj.name.startswith('images/')]
images = {}
for image_name in images_to_find:
images_with_same_name =\
list(filter(lambda storage_image_name: storage_image_name.endswith(image_name), images_in_storage))
if len(images_with_same_name) == 1:
images[image_name] = images_with_same_name[0]
else:
raise Exception('Image does not exist in cloud storage or is duplicated')
return images
def delete_file(self, file_name):
gns3_container = self.storage_driver.get_container(self.GNS3_CONTAINER_NAME)
try:
object_to_delete = gns3_container.get_object(file_name)
object_to_delete.delete()
except ObjectDoesNotExistError:
pass
try:
hash_object = gns3_container.get_object(file_name + '.md5')
hash_object.delete()
except ObjectDoesNotExistError:
pass

View File

@@ -1,67 +0,0 @@
""" Exception classes for CloudCtrl classes. """
class ApiError(Exception):
""" Raised when the server returns 500 Compute Error. """
pass
class BadRequest(Exception):
""" Raised when the server returns 400 Bad Request. """
pass
class ComputeFault(Exception):
""" Raised when the server returns 400|500 Compute Fault. """
pass
class Forbidden(Exception):
""" Raised when the server returns 403 Forbidden. """
pass
class ItemNotFound(Exception):
""" Raised when the server returns 404 Not Found. """
pass
class KeyPairExists(Exception):
""" Raised when the server returns 409 Conflict Key pair exists. """
pass
class MethodNotAllowed(Exception):
""" Raised when the server returns 405 Method Not Allowed. """
pass
class OverLimit(Exception):
""" Raised when the server returns 413 Over Limit. """
pass
class ServerCapacityUnavailable(Exception):
""" Raised when the server returns 503 Server Capacity Uavailable. """
pass
class ServiceUnavailable(Exception):
""" Raised when the server returns 503 Service Unavailable. """
pass
class Unauthorized(Exception):
""" Raised when the server returns 401 Unauthorized. """
pass

View File

@@ -1,259 +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/>.
""" Interacts with Rackspace API to create and manage cloud instances. """
from .base_cloud_ctrl import BaseCloudCtrl
import json
import requests
from libcloud.compute.drivers.rackspace import ENDPOINT_ARGS_MAP
from libcloud.compute.providers import get_driver
from libcloud.compute.types import Provider
from libcloud.storage.providers import get_driver as get_storage_driver
from libcloud.storage.types import Provider as StorageProvider
from .exceptions import ItemNotFound, ApiError
from ..version import __version__
from collections import OrderedDict
import logging
log = logging.getLogger(__name__)
RACKSPACE_REGIONS = [{ENDPOINT_ARGS_MAP[k]['region']: k} for k in
ENDPOINT_ARGS_MAP]
class RackspaceCtrl(BaseCloudCtrl):
""" Controller class for interacting with Rackspace API. """
def __init__(self, username, api_key, *args, **kwargs):
super(RackspaceCtrl, self).__init__(username, api_key)
# set this up so it can be swapped out with a mock for testing
self.post_fn = requests.post
self.driver_cls = get_driver(Provider.RACKSPACE)
self.storage_driver_cls = get_storage_driver(StorageProvider.CLOUDFILES)
self.driver = None
self.storage_driver = None
self.region = None
self.instances = {}
self.authenticated = False
self.identity_ep = \
"https://identity.api.rackspacecloud.com/v2.0/tokens"
self.regions = []
self.token = None
self.tenant_id = None
self.flavor_ep = "https://dfw.servers.api.rackspacecloud.com/v2/{username}/flavors"
self._flavors = OrderedDict([
('2', '512MB, 1 VCPU'),
('3', '1GB, 1 VCPU'),
('4', '2GB, 2 VCPUs'),
('5', '4GB, 2 VCPUs'),
('6', '8GB, 4 VCPUs'),
('7', '15GB, 6 VCPUs'),
('8', '30GB, 8 VCPUs'),
('performance1-1', '1GB Performance, 1 VCPU'),
('performance1-2', '2GB Performance, 2 VCPUs'),
('performance1-4', '4GB Performance, 4 VCPUs'),
('performance1-8', '8GB Performance, 8 VCPUs'),
('performance2-15', '15GB Performance, 4 VCPUs'),
('performance2-30', '30GB Performance, 8 VCPUs'),
('performance2-60', '60GB Performance, 16 VCPUs'),
('performance2-90', '90GB Performance, 24 VCPUs'),
('performance2-120', '120GB Performance, 32 VCPUs',)
])
def authenticate(self):
"""
Submit username and api key to API service.
If authentication is successful, set self.regions and self.token.
Return boolean.
"""
self.authenticated = False
if len(self.username) < 1:
return False
if len(self.api_key) < 1:
return False
data = json.dumps({
"auth": {
"RAX-KSKEY:apiKeyCredentials": {
"username": self.username,
"apiKey": self.api_key
}
}
})
headers = {
'Content-type': 'application/json',
'Accept': 'application/json'
}
response = self.post_fn(self.identity_ep, data=data, headers=headers)
if response.status_code == 200:
api_data = response.json()
self.token = self._parse_token(api_data)
if self.token:
self.authenticated = True
user_regions = self._parse_endpoints(api_data)
self.regions = self._make_region_list(user_regions)
self.tenant_id = self._parse_tenant_id(api_data)
else:
self.regions = []
self.token = None
response.connection.close()
return self.authenticated
def list_regions(self):
""" Return a list the regions available to the user. """
return self.regions
def list_flavors(self):
""" Return the dictionary containing flavors id and names """
return self._flavors
def _parse_endpoints(self, api_data):
"""
Parse the JSON-encoded data returned by the Identity Service API.
Return a list of regions available for Compute v2.
"""
region_codes = []
for ep_type in api_data['access']['serviceCatalog']:
if ep_type['name'] == "cloudServersOpenStack" \
and ep_type['type'] == "compute":
for ep in ep_type['endpoints']:
if ep['versionId'] == "2":
region_codes.append(ep['region'])
return region_codes
def _parse_token(self, api_data):
""" Parse the token from the JSON-encoded data returned by the API. """
try:
token = api_data['access']['token']['id']
except KeyError:
return None
return token
def _parse_tenant_id(self, api_data):
""" """
try:
roles = api_data['access']['user']['roles']
for role in roles:
if 'tenantId' in role and role['name'] == 'compute:default':
return role['tenantId']
return None
except KeyError:
return None
def _make_region_list(self, region_codes):
"""
Make a list of regions for use in the GUI.
Returns a list of key-value pairs in the form:
<API's Region Name>: <libcloud's Region Name>
eg,
[
{'DFW': 'dfw'}
{'ORD': 'ord'},
...
]
"""
region_list = []
for ep in ENDPOINT_ARGS_MAP:
if ENDPOINT_ARGS_MAP[ep]['region'] in region_codes:
region_list.append({ENDPOINT_ARGS_MAP[ep]['region']: ep})
return region_list
def set_region(self, region):
""" Set self.region and self.driver. Returns True or False. """
try:
self.driver = self.driver_cls(self.username, self.api_key,
region=region)
self.storage_driver = self.storage_driver_cls(self.username, self.api_key,
region=region)
except ValueError:
return False
self.region = region
return True
def get_image(self, image_id):
return self.driver.get_image(image_id)
def get_provider(cloud_settings):
"""
Utility function to retrieve a cloud provider instance already authenticated and with the
region set
:param cloud_settings: cloud settings dictionary
:return: a provider instance or None on errors
"""
try:
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")
return
if not region:
region = provider.list_regions().values()[0]
if not provider.set_region(region):
log.error("Unable to set cloud provider region")
return
return provider

View File

@@ -1,538 +0,0 @@
from contextlib import contextmanager
import io
import json
from socket import error as socket_error
import logging
import os
import tempfile
import time
import zipfile
from ..qt import QtCore
from .exceptions import KeyPairExists
from .rackspace_ctrl import get_provider
from ..topology import Topology
from ..servers import Servers
log = logging.getLogger(__name__)
@contextmanager
def ssh_client(host, key_string):
"""
Context manager wrapping a SSHClient instance: the client connects on
enter and close the connection on exit
"""
import paramiko
class AllowAndForgetPolicy(paramiko.MissingHostKeyPolicy):
"""
Custom policy for server host keys: we simply accept the key
the server sent to us without storing it.
"""
def missing_host_key(self, *args, **kwargs):
"""
According to MissingHostKeyPolicy protocol, to accept
the key, simply return.
"""
return
client = paramiko.SSHClient()
try:
f_key = io.StringIO(key_string)
key = paramiko.RSAKey.from_private_key(f_key)
client.set_missing_host_key_policy(AllowAndForgetPolicy())
client.connect(hostname=host, username="root", pkey=key)
yield client
except socket_error as e:
log.debug("SSH connection socket error to {}: {}".format(host, e))
yield None
except Exception as e:
log.debug("SSH connection error to {}: {}".format(host, e))
yield None
finally:
client.close()
class ListInstancesThread(QtCore.QThread):
"""
Helper class to retrieve data from the provider in a separate thread,
avoid freezing the gui
"""
instancesReady = QtCore.pyqtSignal(object)
def __init__(self, parent, provider):
super().__init__(parent)
self._provider = provider
def run(self):
try:
instances = self._provider.list_instances()
log.debug('Instance list: {}'.format([(i.name, i.state) for i in instances]))
self.instancesReady.emit(instances)
except Exception as e:
log.info('list_instances error: {}'.format(e))
class CreateInstanceThread(QtCore.QThread):
"""
Helper class to create instances in a separate thread
"""
instanceCreated = QtCore.pyqtSignal(object, object)
def __init__(self, parent, provider, name, flavor_id, image_id):
super().__init__(parent)
self._provider = provider
self._name = name
self._flavor_id = flavor_id
self._image_id = image_id
def run(self):
log.debug("Creating cloud keypair with name {}".format(self._name))
try:
k = self._provider.create_key_pair(self._name)
except KeyPairExists:
log.debug("Cloud keypair with name {} exists. Recreating.".format(self._name))
# delete keypairs if they already exist
self._provider.delete_key_pair_by_name(self._name)
k = self._provider.create_key_pair(self._name)
log.debug("Creating cloud server with name {}".format(self._name))
i = self._provider.create_instance(self._name, self._flavor_id, self._image_id, k)
log.debug("Cloud server {} created".format(self._name))
self.instanceCreated.emit(i, k)
class DeleteInstanceThread(QtCore.QThread):
"""
Helper class to remove an instance in a separate thread
"""
instanceDeleted = QtCore.pyqtSignal(object)
def __init__(self, parent, provider, instance):
super().__init__(parent)
self._provider = provider
self._instance = instance
def run(self):
if self._provider.delete_instance(self._instance):
self.instanceDeleted.emit(self._instance)
class StartGNS3ServerThread(QtCore.QThread):
"""
Perform an SSH connection to the instances in a separate thread,
outside the GUI event loop, and start GNS3 server
"""
gns3server_started = QtCore.pyqtSignal(str, str, str)
# This is for testing without pushing to github
# commands = '''
# DEBIAN_FRONTEND=noninteractive dpkg --configure -a
# DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
# DEBIAN_FRONTEND=noninteractive apt-get -y update
# DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
# DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
# DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
# ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
# mkdir -p /opt/gns3
# tar xzf /tmp/gns3-server.tgz -C /opt/gns3
# cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
# cd /opt/gns3/gns3-server; python3 ./setup.py install
# ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
# wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
# python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
# hostname gns3-iouvm
# tar xzf iouyap.tar.gz -C /usr/local/bin
# killall python3 gns3server gns3dms
# '''
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap-64-bit.tar.gz'
tar xzf iouyap-64-bit.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
wget -O vpcs http://sourceforge.net/projects/vpcs/files/0.6/vpcs_0.6_Linux64/download
cp vpcs /usr/local/bin/vpcs
chmod a+x /usr/local/bin/vpcs
killall python3 gns3server gns3dms
'''
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
super().__init__(parent)
self._host = host
self._private_key_string = private_key_string
self._server_id = server_id
self._username = username
self._api_key = api_key
self._region = region
self._dead_time = dead_time
def exec_command(self, client, cmd, wait_time=-1):
cmd += '; exit $?'
stdout_data = b''
stderr_data = b''
log.debug('cmd: {}'.format(cmd))
# Send the command (non-blocking)
stdin, stdout, stderr = client.exec_command(cmd)
# Wait for the command to terminate
wait = int(wait_time)
while not stdout.channel.exit_status_ready() and wait != 0:
time.sleep(1)
wait -= 1
stdout_data = stdout.read()
stderr_data = stderr.read()
log.debug('exit status: {}'.format(stdout.channel.exit_status))
log.debug('stdout: {}'.format(stdout_data.decode('utf-8')))
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
def run(self):
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
ssh_connected = False
while not ssh_connected:
with ssh_client(self._host, self._private_key_string) as client:
if client is None:
time.sleep(1)
continue
ssh_connected = True
# This is for testing without pushing to github
# os.system('rm -rf /tmp/gns3-server')
# os.system('cp -a /Users/jseutter/projects/gns3-server /tmp/gns3-server')
# os.system('cd /tmp; tar czf /tmp/gns3-server.tgz gns3-server')
# sftp = client.open_sftp()
# sftp.put('/tmp/gns3-server.tgz', '/tmp/gns3-server.tgz')
# sftp.close()
for cmd in [l for l in self.commands.splitlines() if l.strip()]:
self.exec_command(client, cmd)
data = {
'instance_id': self._server_id,
'cloud_user_name': self._username,
'cloud_api_key': self._api_key,
'cloud_region': self._region,
'dead_time': self._dead_time,
}
# TODO: Properly escape the data portion of the command line
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._host, data)
stdout, stderr = self.exec_command(client, start_cmd, wait_time=15)
response = stdout.decode('utf-8')
self.gns3server_started.emit(str(self._server_id), str(self._host), str(response))
class WSConnectThread(QtCore.QThread):
"""
Establish a websocket connection with the remote gns3server
instance. Run outside the GUI event loop.
"""
established = QtCore.pyqtSignal(str)
def __init__(self, parent, provider, server_id, host, port, ca_file,
auth_user, auth_password, ssh_pkey, instance_id):
super().__init__(parent)
self._provider = provider
self._server_id = server_id
self._host = host
self._port = port
self._ca_file = ca_file
self._auth_user = auth_user
self._auth_password = auth_password
self._ssh_pkey = ssh_pkey
self._instance_id = instance_id
def run(self):
"""
Establish a websocket connection to gns3server on the cloud instance.
"""
log.debug('WSConnectThread.run() begin')
servers = Servers.instance()
server = servers.getCloudServer(self._host, self._port, self._ca_file,
self._auth_user, self._auth_password, self._ssh_pkey,
self._instance_id)
log.debug('after getCloudServer call. {}'.format(server))
self.established.emit(str(self._server_id))
log.debug('WSConnectThread.run() end')
# emit signal on success
self.established.emit(self._server_id)
class UploadProjectThread(QtCore.QThread):
"""
Zip and Upload project to the cloud
"""
# signals to update the progress dialog.
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_settings, project_path, images_path):
super().__init__(parent)
self.cloud_settings = cloud_settings
self.project_path = project_path
self.images_path = images_path
def run(self):
try:
log.info("Exporting project to cloud")
self.update.emit(0)
zipped_project_file = self.zip_project_dir()
self.update.emit(10) # update progress to 10%
provider = get_provider(self.cloud_settings)
provider.upload_file(zipped_project_file, 'projects/' + os.path.basename(zipped_project_file))
self.update.emit(20) # update progress to 20%
topology = Topology.instance()
images = set([node.settings()["image"] for node in topology.nodes() if 'image' in node.settings()])
for i, image in enumerate(images):
provider.upload_file(image, 'images/' + os.path.relpath(image, self.images_path))
self.update.emit(20 + (float(i) / len(images) * 80))
self.completed.emit()
except Exception as e:
log.exception("Error exporting project to cloud")
self.error.emit("Error exporting project: {}".format(e), True)
def zip_project_dir(self):
"""
Zips project files
:return: path to zipped project file
"""
project_name = os.path.basename(self.project_path)
output_filename = os.path.join(tempfile.gettempdir(), project_name + ".zip")
project_dir = os.path.dirname(self.project_path)
relroot = os.path.abspath(os.path.join(project_dir, os.pardir))
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
for root, dirs, files in os.walk(project_dir):
# add directory (needed for empty dirs)
zip_file.write(root, os.path.relpath(root, relroot))
for file in files:
filename = os.path.join(root, file)
if os.path.isfile(filename) and not self._should_exclude(filename): # regular files only
arcname = os.path.join(os.path.relpath(root, relroot), file)
zip_file.write(filename, arcname)
return output_filename
def _should_exclude(self, filename):
"""
Returns True if file should be excluded from zip of project files
:param filename:
:return: True if file should be excluded from zip, False otherwise
"""
return filename.endswith('.ghost')
def stop(self):
self.quit()
class UploadFilesThread(QtCore.QThread):
"""
Uploads files to cloud files
:param cloud_settings:
:param files_to_upload: list of tuples of (file path, file name to save in cloud)
"""
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_settings, files_to_upload):
super().__init__(parent)
self._cloud_settings = cloud_settings
self._files_to_upload = files_to_upload
def run(self):
self.update.emit(0)
try:
for i, file_to_upload in enumerate(self._files_to_upload):
provider = get_provider(self._cloud_settings)
log.debug('Uploading image {} to cloud as {}'.format(file_to_upload[0], file_to_upload[1]))
provider.upload_file(file_to_upload[0], file_to_upload[1])
self.update.emit((i + 1) * 100 / len(self._files_to_upload))
log.debug('Uploading image completed')
except Exception as e:
log.exception("Error uploading images to cloud")
self.error.emit("Error uploading images: {}".format(e), True)
self.completed.emit()
def stop(self):
self.quit()
class DownloadProjectThread(QtCore.QThread):
"""
Downloads project from cloud storage
"""
# signals to update the progress dialog.
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super().__init__(parent)
self.project_name = cloud_project_file_name
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
self.cloud_settings = cloud_settings
def run(self):
try:
self.update.emit(0)
provider = get_provider(self.cloud_settings)
zip_file = provider.download_file(self.project_name)
zip_file = zipfile.ZipFile(zip_file, mode='r')
zip_file.extractall(self.project_dest_path)
zip_file.close()
project_name = zip_file.namelist()[0].strip('/')
self.update.emit(20)
with open(os.path.join(self.project_dest_path, project_name, project_name + '.gns3'), 'r') as f:
project_settings = json.loads(f.read())
images = set()
for node in project_settings["topology"].get("nodes", []):
if "properties" in node and "image" in node["properties"]:
images.add(node["properties"]["image"])
image_names_in_cloud = provider.find_storage_image_names(images)
for i, image in enumerate(images):
dest_path = os.path.join(self.images_dest_path, *image_names_in_cloud[image].split('/')[1:])
if not os.path.exists(os.path.dirname(dest_path)):
os.makedirs(os.path.dirname(dest_path))
provider.download_file(image_names_in_cloud[image], dest_path)
self.update.emit(20 + (float(i) / len(images) * 80))
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(e), True)
def stop(self):
self.quit()
class DownloadImagesThread(QtCore.QThread):
"""
Downloads multiple files from cloud files
"""
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, cloud_settings, images_dest_path, image_names):
super().__init__()
self._cloud_settings = cloud_settings
self._images_dest_path = images_dest_path
self._image_names = image_names
def run(self):
self.update.emit(0)
try:
provider = get_provider(self._cloud_settings)
image_names_in_cloud = provider.find_storage_image_names(self._image_names)
for i, image in enumerate(self._image_names):
dest_path = os.path.join(self._images_dest_path, *image_names_in_cloud[image].split('/')[1:])
if not os.path.exists(os.path.dirname(dest_path)):
os.makedirs(os.path.dirname(dest_path))
provider.download_file(image_names_in_cloud[image], dest_path)
self.update.emit(i * 100 / len(self._image_names))
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(e), True)
def stop(self):
self.quit()
class DeleteProjectThread(QtCore.QThread):
"""
Deletes project from cloud storage
"""
# signals to update the progress dialog.
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, project_file_name, cloud_settings):
super().__init__(parent)
self.project_file_name = project_file_name
self.cloud_settings = cloud_settings
def run(self):
try:
provider = get_provider(self.cloud_settings)
provider.delete_file(self.project_file_name)
self.completed.emit()
except Exception as e:
log.exception("Error deleting project")
self.error.emit("Error deleting project: {}".format(e), True)
def stop(self):
pass
def get_cloud_projects(cloud_settings):
provider = get_provider(cloud_settings)
return provider.list_projects()

View File

@@ -1,252 +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/>.
"""
"""
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtCore import QThread
import ast
import logging
import os
import time
from .cloud.utils import ssh_client
from .cloud.exceptions import KeyPairExists
from .servers import Servers
from .topology import Topology
log = logging.getLogger(__name__)
class CloudBuilder(QThread):
"""
"""
# Notify with progress amount and instance_id
progressUpdate = pyqtSignal(object, str)
# Notify with current state and instance_id
stateChange = pyqtSignal(object, str)
# Notify when instance is ready with instance_id
buildComplete = pyqtSignal(str)
# Notify when the instance has been created with instance and keypair
instanceCreated = pyqtSignal(object, object)
# Notify when the public ip is available with ip and instance_id
instanceHasIP = pyqtSignal(str, str)
# Notify when instance id exists with builder and instance_id
instanceIdExists = pyqtSignal(object, str)
def __init__(self, parent, cloud_provider, ca_dir):
super(QThread, self).__init__(parent)
# Store our parent so it can be passed to threads we spawn.
self._parent = parent
self._provider = cloud_provider
self._ca_dir = ca_dir
self._start_at_create = False
self._start_at_setup = False
self._instance = None
def startAtCreate(self, instance_name, flavor_id, image_id):
self._start_at_create = True
self._instance_name = instance_name
self._flavor_id = flavor_id
self._image_id = image_id
def startAtSetup(self, instance, keypair):
self._start_at_setup = True
self._instance = instance
self._key_pair = keypair
def run(self):
try:
log.debug('CloudBuilder.run')
if self._start_at_create:
log.debug('CloudBuilder._start_at_create')
self._createInstance(self._provider, self._instance_name, self._flavor_id,
self._image_id)
log.debug('got here 3')
if self._start_at_setup:
log.debug('CloudBuilder start at setup')
self._instanceCreated(self._instance, self._key_pair)
except Exception:
log.exception("CloudBuilder trapped an exception:")
log.error('CloudBuilder stopped in error state.')
def _createInstance(self, provider, name, flavor_id, image_id):
log.debug("Creating cloud keypair with name {}".format(name))
key_pair = None
while key_pair is None:
try:
key_pair = provider.create_key_pair(name)
except KeyPairExists:
log.debug("Deleting old key pair with name {}.".format(name))
self._provider.delete_key_pair_by_name(name)
except Exception as e:
log.debug("create_key_pair exception {}".format(e))
log.debug("Creating cloud server with name {}".format(name))
instance = None
while instance is None:
try:
instance = self._provider.create_instance(name, flavor_id, image_id, key_pair)
except Exception as e:
log.debug("create_instance exception {}".format(e))
log.debug("Cloud server {} created".format(name))
self._instanceCreated(instance, key_pair)
def _instanceCreated(self, instance, key_pair):
log.debug('CloudBuilder._instanceCreated {}'.format(instance.id))
self._instance = instance
self._instance_id = instance.id
self._key_pair = key_pair
self.instanceIdExists.emit(self, instance.id)
self.instanceCreated.emit(instance, key_pair)
self._waitForPublicIP()
def _waitForPublicIP(self):
public_ip = None
while public_ip is None:
time.sleep(10)
try:
instance = self._provider.get_instance(self._instance)
# Look for public ip address
for ip in instance.public_ips:
# Don't use the ipv6 address
if ':' not in ip:
public_ip = ip
break
except Exception as e:
log.debug('list_instances error: {}'.format(e))
# updated info, keep it.
self._instance = instance
self._public_ip = public_ip
self.instanceHasIP.emit(self._public_ip, self._instance.id)
time.sleep(60)
self._startGNS3Server(1800)
def _startGNS3Server(self, dead_time):
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
wget 'http://downloads.sourceforge.net/project/vpcs/0.6/vpcs_0.6_Linux64'
cp vpcs_0.6_Linux64 /usr/local/bin/vpcs
chmod a+x /usr/local/bin/vpcs
killall python3 gns3server gns3dms
'''
def exec_command(client, cmd, wait_time=-1):
cmd += '; exit $?'
stdout_data = b''
stderr_data = b''
log.debug('cmd: {}'.format(cmd))
# Send the command (non-blocking)
stdin, stdout, stderr = client.exec_command(cmd)
# Wait for the command to terminate
wait = int(wait_time)
while not stdout.channel.exit_status_ready() and wait != 0:
time.sleep(1)
wait -= 1
stdout_data = stdout.read()
stderr_data = stderr.read()
log.debug('exit status: {}'.format(stdout.channel.exit_status))
log.debug('stdout: {}'.format(stdout_data.decode('utf-8')))
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
ssh_connected = False
response = None
while not ssh_connected:
with ssh_client(self._public_ip, self._key_pair.private_key) as client:
if client is None:
time.sleep(1)
continue
ssh_connected = True
for cmd in [l for l in commands.splitlines() if l.strip()]:
exec_command(client, cmd)
data = {
'instance_id': self._instance_id,
'cloud_user_name': self._provider.username,
'cloud_api_key': self._provider.api_key,
'cloud_region': self._provider.region,
'dead_time': dead_time,
}
# TODO: Properly escape the data portion of the command line
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._public_ip, data)
stdout, stderr = exec_command(client, start_cmd, wait_time=15)
response = stdout.decode('utf-8')
log.debug(response)
data = ast.literal_eval(response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(self._public_ip)
ca_dir = self._ca_dir
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(self._instance_id)
top_instance.set_later_attributes(self._public_ip, port, ssl_cert, ca_file)
servers = Servers.instance()
server = servers.getCloudServer(self._public_ip, port, ca_file, username, password,
self._key_pair.private_key, self._instance_id)
servers.save()
log.debug('Cloud server gns3server started.')
self.buildComplete.emit(self._instance_id)

View File

@@ -1,427 +0,0 @@
# -*- coding: utf-8 -*-
from collections import namedtuple
import logging
import os
import json
from libcloud.compute.types import NodeState
from .qt import QtCore, QtGui
from .cloud.utils import (ListInstancesThread, DeleteInstanceThread)
from .topology import Topology
from .servers import Servers
# this widget was promoted on Creator, must use absolute imports
from gns3.ui.cloud_inspector_view_ui import Ui_CloudInspectorView
from gns3.cloud_builder import CloudBuilder
from gns3.cloud_instances import CloudInstances
log = logging.getLogger(__name__)
POLLING_TIMER = 10000 # in milliseconds
class RunningInstanceState(NodeState):
"""
GNS3 states for running instances
"""
GNS3SERVER_STARTING = -1
GNS3SERVER_STARTED = -2
WS_CONNECTED = -3
class InstanceTableModel(QtCore.QAbstractTableModel):
"""
A custom table model storing data of cloud instances
"""
def __init__(self, *args, **kwargs):
super(InstanceTableModel, self).__init__(*args, **kwargs)
self._header_data = ['Instance', '', 'Size', 'Devices'] # status has an empty header label
self._width = len(self._header_data)
self._instances = {}
self._ids = []
self.flavors = {}
@property
def instanceIds(self):
return self._ids
def clear(self):
self._instances = {}
self._ids = []
self.reset()
def _get_status_icon_path(self, instance):
"""
Return a string pointing to the graphic resource
"""
if instance.state == RunningInstanceState.WS_CONNECTED:
return ':/icons/led_green.svg'
elif instance.state in (RunningInstanceState.STOPPED,
RunningInstanceState.TERMINATED,
RunningInstanceState.UNKNOWN):
return ':/icons/led_red.svg'
else:
return ':/icons/led_yellow.svg'
def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
return len(self._instances)
def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
return self._width if len(self._instances) else 0
def data(self, index, role=None):
instance = self._instances.get(self._ids[index.row()])
col = index.column()
if role == QtCore.Qt.DecorationRole:
if col == 1:
# status
return QtGui.QIcon(self._get_status_icon_path(instance))
elif role == QtCore.Qt.DisplayRole:
if col == 0:
# name
return instance.name
elif col == 2:
# size
try:
# for Rackspace instances, update flavor id with a verbose description
return self.flavors.get(instance.extra['flavorId'])
except KeyError:
# fallback to libcloud size property
if instance.size:
return instance.size.ram
# giveup on showing size
return 'Unknown'
elif col == 3:
# devices
count = 0
topology = Topology.instance()
for node in topology.nodes():
id = node._server.instance_id or 0
if instance.id == id:
count += 1
return count
return None
def headerData(self, section, orientation, role=None):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
try:
return self._header_data[section]
except IndexError:
return None
return super(InstanceTableModel, self).headerData(section, orientation, role)
def addInstance(self, instance):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
if not len(self._instances):
self.beginInsertColumns(QtCore.QModelIndex(), 0, self._width - 1)
self.endInsertColumns()
self._ids.append(instance.id)
self._instances[instance.id] = instance
self.endInsertRows()
def getInstance(self, index):
"""
Retrieve the i-th instance if index is in range
"""
try:
return self._instances.get(self._ids[index])
except IndexError:
return None
def removeInstance(self, instance):
self.removeInstanceById(instance.id)
def removeInstanceById(self, instance_id):
try:
index = self._ids.index(instance_id)
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
del self._instances[instance_id]
del self._ids[index]
self.endRemoveRows()
except ValueError:
pass
def updateInstanceFields(self, instance, field_names):
"""
Update model data and notify connected views
"""
if instance.id in self._ids:
index = self._ids.index(instance.id)
current = self._instances[instance.id]
for field in field_names:
setattr(current, field, getattr(instance, field))
first_index = self.createIndex(index, 0)
last_index = self.createIndex(index, self.columnCount() - 1)
self.dataChanged.emit(first_index, last_index)
else:
self.addInstance(instance)
def getInstanceById(self, instance_id):
return self._instances.get(instance_id, None)
class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
"""
Table view showing data coming from InstanceTableModel
Signals:
instanceSelected(int) Emitted when users click and select an instance on the inspector.
Param int is the ID of the instance
"""
instanceSelected = QtCore.pyqtSignal(str)
def __init__(self, parent):
super(QtGui.QWidget, self).__init__(parent)
self.setupUi(self)
self._provider = None
self._settings = None
self._project_instances_id = []
self._main_window = None
self._model = InstanceTableModel() # shortcut for self.uiInstancesTableView.model()
self.uiInstancesTableView.setModel(self._model)
self.uiInstancesTableView.verticalHeader().hide()
self.uiInstancesTableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.uiInstancesTableView.horizontalHeader().setStretchLastSection(True)
# connections
self.uiInstancesTableView.customContextMenuRequested.connect(self._contextMenu)
self.uiInstancesTableView.clicked.connect(self._rowChanged)
self.uiCreateInstanceButton.clicked.connect(self._create_new_instance)
self._pollingTimer = QtCore.QTimer(self)
self._pollingTimer.timeout.connect(self._polling_slot)
# map flavor ids to combobox indexes
self.flavor_index_id = []
# A dictionary of {image_id, CloudBuilder}
self._builders = {}
def _get_flavor_index(self, flavor_id):
try:
return self.flavor_index_id.index(flavor_id)
except ValueError:
return -1
def load(self, main_win, instance_ids):
"""
Fill the model data layer with instance info loaded from the topology file
"""
self._main_window = main_win
self._provider = main_win.cloudProvider
self._settings = main_win.cloudSettings()
log.info('CloudInspectorView.load')
for instance_id in instance_ids:
self._project_instances_id.append(instance_id)
update_thread = ListInstancesThread(self, self._provider)
update_thread.instancesReady.connect(self._update_model)
update_thread.start()
self._pollingTimer.start(POLLING_TIMER)
# fill sizes comboboxes
for id, name in self._provider.list_flavors().items():
self.uiCreateInstanceComboBox.addItem(name)
self.flavor_index_id.append(id)
# select default flavor
new_instance_flavor = self._settings["new_instance_flavor"]
self.uiCreateInstanceComboBox.setCurrentIndex(self._get_flavor_index(new_instance_flavor))
def addInstance(self, instance):
"""
Add a new instance to the inspector
"""
self._project_instances_id.append(instance.id)
def clear(self):
"""
Clear contents and stop polling timer
"""
self._model.clear()
self._pollingTimer.stop()
self._project_instances_id = []
def _contextMenu(self, pos):
# create actions
delete_action = QtGui.QAction("Delete", self)
delete_action.triggered.connect(self._deleteSelectedInstance)
# create context menu and add actions
menu = QtGui.QMenu(self.uiInstancesTableView)
menu.addAction(delete_action)
# show the menu
menu.popup(self.uiInstancesTableView.viewport().mapToGlobal(pos))
def _deleteSelectedInstance(self):
"""
Delete the instance corresponding to the selected table row
"""
sel = self.uiInstancesTableView.selectedIndexes()
if len(sel) and self._provider is not None:
index = sel[0].row()
instance = self._model.getInstance(index)
# warn user this is destructive
msg = "Do you want to remove the instance and any devices running on it?"
proceed = QtGui.QMessageBox.question(self, 'Warning', msg,
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if proceed == QtGui.QMessageBox.Yes:
# disconnect and remove the server
servers = Servers.instance()
cs = servers.cloudServerById(instance.id)
if cs is not None:
servers.removeCloudServer(cs)
# remove instance from the the topology
topology = Topology.instance()
topology.removeInstance(instance.id)
delete_thread = DeleteInstanceThread(self, self._provider, instance)
delete_thread.instanceDeleted.connect(self._main_window.remove_instance_from_project)
delete_thread.start()
instance.name = 'Deleting...'
self._model.updateInstanceFields(instance, ['name'])
def _rowChanged(self, index):
"""
This slot is invoked every time users change the current selected row on the
inspector
"""
selection = self.uiInstancesTableView.selectionModel().selection()
if selection.isEmpty():
return
item = selection.indexes()[0]
if item.isValid():
instance = self._model.getInstance(item.row())
self.instanceSelected.emit(instance.id)
def _polling_slot(self):
"""
Sync model data with instances status
"""
if self._provider is None:
return
update_thread = ListInstancesThread(self, self._provider)
update_thread.instancesReady.connect(self._update_model)
update_thread.start()
def _instanceBuilt(self, id):
"""
This slot is called when instance has finished building.
"""
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.WS_CONNECTED
self._model.updateInstanceFields(instance, ['state'])
if self._main_window.loading_cloud_project:
project = self._main_window.project()
path = project.topologyPath()
with open(path, "r") as f:
json_topology = json.load(f)
topology = Topology.instance()
topology.load(json_topology)
self._main_window.loading_cloud_project = False
def _update_model(self, instances):
if not instances:
return
# Filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
# populate underlying model if this is the first call
if self._model.rowCount() == 0 and len(project_instances) > 0:
self._populate_model(project_instances)
self._rebuild_instances(project_instances)
instance_manager = CloudInstances.instance()
instance_manager.update_instances(instances)
# Clean up removed instances
real = set(i.id for i in project_instances)
current = set(self._model.instanceIds)
for i in current.difference(real):
self._model.removeInstanceById(i)
self.uiInstancesTableView.resizeColumnsToContents()
# Update instance status
for i in project_instances:
# get the customized instance state from self._model
model_instance = self._model.getInstanceById(i.id)
# update model instance state if needed
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
def _populate_model(self, instances):
log.info('CloudInspectorView._populate_model')
self._model.flavors = self._provider.list_flavors()
# filter instances for current project
for inst in instances:
self._model.addInstance(inst)
self.uiInstancesTableView.resizeColumnsToContents()
def _create_new_instance(self):
idx = self.uiCreateInstanceComboBox.currentIndex()
flavor_id = self.flavor_index_id[idx]
image_id = self._settings['default_image']
name, ok = QtGui.QInputDialog.getText(self,
"New instance",
"Choose a name for the instance and press Ok,\n"
"then wait for the instance to appear in the inspector.")
if ok:
self.createInstance(name, flavor_id, image_id)
def createInstance(self, instance_name, flavor_id, image_id):
if not instance_name.endswith("-gns3"):
instance_name += "-gns3"
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
builder = CloudBuilder(self, self._provider, ca_dir)
builder.startAtCreate(instance_name, flavor_id, image_id)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder
def _associateBuilderWithInstance(self, builder, instance_id):
self._builders[instance_id] = builder
def _rebuild_instances(self, instances):
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
for instance in instances:
log.debug('CloudInspectorView._rebuild_instances {}'.format(instance.name))
builder = CloudBuilder(self, self._provider, ca_dir)
cloud_instance = CloudInstances.instance().get_instance(instance.id)
public_key = cloud_instance.public_key
private_key = cloud_instance.private_key
# Fake a KeyPair object because we don't store it.
keypair = namedtuple('KeyPair', ['private_key', 'public_key'])(private_key, public_key)
builder.startAtSetup(instance, keypair)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder

View File

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

View File

@@ -36,10 +36,6 @@ except ImportError:
class ConsoleCmd(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
def do_version(self, args):
"""
Show the version of GNS3 and its dependencies.
@@ -194,7 +190,7 @@ class ConsoleCmd(cmd.Cmd):
name = node.name()
console_port = node.console()
console_host = node.server().host
console_host = node.server().host()
try:
from .telnet_console import telnetConsole
telnetConsole(name, console_host, console_port)
@@ -204,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() == "":
@@ -212,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

@@ -20,11 +20,14 @@ import sys
import struct
import inspect
import datetime
from .qt import QtCore
from .topology import Topology
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):
@@ -38,12 +41,17 @@ 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).\n" \
"Copyright (c) 2006-{} GNS3 Technologies.".format(__version__, platform.system(), bitness, 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:
PyCutExt.__init__(self, None, self.intro, parent=parent)
super().__init__(None, self.intro, parent=parent)
# dynamically get all the available commands so we can color them
methods = inspect.getmembers(self, predicate=inspect.ismethod)
@@ -186,8 +194,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
if node:
if node.name():
name = " {}:".format(node.name())
server = "from {}:{}".format(node.server().host,
node.server().port)
server = "from {}".format(node.server().url())
text = "Server error {server}:{name} {message}".format(server=server,
name=name,

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
@@ -27,43 +28,61 @@ except ImportError:
# raven is not installed with deb package in order to simplify packaging
RAVEN_AVAILABLE = False
from .version import __version__
from .servers import Servers
from .utils.get_resource import get_resource
from .version import __version__, __version_info__
import logging
log = logging.getLogger(__name__)
# Dev build
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
log.info("Enable catching segfault")
faulthandler.enable()
class CrashReport:
"""
Report crash to a third party service
"""
DSN = "sync+https://b6bab8ad3e9f4ea790e59170d99d2149:c6616918e1f043e0bb0d041478ccb175@app.getsentry.com/38506"
DSN = "sync+https://3d44e34021504514a5fb0539ae6f8f92:af41562761754b4c9beca492d1b9115d@app.getsentry.com/38506"
if hasattr(sys, "frozen"):
cacert = os.path.join(os.getcwd(), "cacert.pem")
if os.path.isfile(cacert):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
DSN += "?ca_certs={}".format(cacert)
else:
log.warning("The SSL certificate bundle file '{}' could not be found".format(cacert))
_instance = None
def __init__(self):
self._client = None
# We don't want sentry making noise if an error is catched when you don't have internet
sentry_errors = logging.getLogger('sentry.errors')
sentry_errors.disabled = True
sentry_uncaught = logging.getLogger('sentry.errors.uncaught')
sentry_uncaught.disabled = True
def captureException(self, exception, value, tb):
if not RAVEN_AVAILABLE:
return
if os.path.exists(".git"):
log.warning("A .git directory exist crash report is turn off for developers")
return
from .servers import Servers
local_server = Servers.instance().localServerSettings()
if local_server["report_errors"]:
if self._client is None:
self._client = raven.Client(CrashReport.DSN, release=__version__)
self._client.tags_context({
if not RAVEN_AVAILABLE:
return
if os.path.exists(".git"):
log.warning("A .git directory exist crash report is turn off for developers")
return
if hasattr(exception, "fingerprint"):
client = raven.Client(CrashReport.DSN, release=__version__, fingerprint=['{{ default }}', exception.fingerprint])
else:
client = raven.Client(CrashReport.DSN, release=__version__)
context = {
"os:name": platform.system(),
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
@@ -75,13 +94,27 @@ class CrashReport:
"python:bit": struct.calcsize("P") * 8,
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
})
}
context = self._add_qt_information(context)
client.tags_context(context)
try:
report = self._client.captureException((exception, value, tb))
report = client.captureException((exception, value, tb))
except Exception as e:
log.error("Can't send crash report to Sentry: {}".format(e))
return
log.info("Crash report sent with event ID: {}".format(self._client.get_ident(report)))
log.info("Crash report sent with event ID: {}".format(client.get_ident(report)))
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
return context
@classmethod
def instance(cls):

View File

@@ -15,12 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..qt import QtGui
from ..qt import QtWidgets
from ..version import __version__
from ..ui.about_dialog_ui import Ui_AboutDialog
class AboutDialog(QtGui.QDialog, Ui_AboutDialog):
class AboutDialog(QtWidgets.QDialog, Ui_AboutDialog):
"""
About dialog.
@@ -28,7 +28,7 @@ class AboutDialog(QtGui.QDialog, Ui_AboutDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
# dynamically add the current version number

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

@@ -19,12 +19,12 @@
Dialog to configure and update node settings using widget pages.
"""
from ..qt import QtGui
from ..qt import QtWidgets
from ..ui.configuration_dialog_ui import Ui_configurationDialog
from .node_configurator_dialog import ConfigurationError
from .node_properties_dialog import ConfigurationError
class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
class ConfigurationDialog(QtWidgets.QDialog, Ui_configurationDialog):
"""
Configuration dialog implementation.
@@ -37,13 +37,14 @@ class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
def __init__(self, name, settings, configuration_page, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self.uiTitleLabel.setText(name)
self.setWindowTitle(configuration_page.windowTitle())
self.uiConfigStackedWidget.addWidget(configuration_page)
self.uiConfigStackedWidget.setCurrentWidget(configuration_page)
self.setModal(True)
configuration_page.loadSettings(settings)
self._settings = settings
self._configuration_page = configuration_page
@@ -55,11 +56,11 @@ class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
:param button: button that was clicked (QAbstractButton)
"""
if button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Cancel):
QtGui.QDialog.reject(self)
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
try:
self._configuration_page.saveSettings(self._settings)
except ConfigurationError:
return
QtGui.QDialog.accept(self)
QtWidgets.QDialog.accept(self)

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

@@ -15,11 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets
from ..ui.exec_command_dialog_ui import Ui_ExecCommandDialog
class ExecCommandDialog(QtGui.QDialog, Ui_ExecCommandDialog):
class ExecCommandDialog(QtWidgets.QDialog, Ui_ExecCommandDialog):
"""
Execute a command and display its output.
@@ -27,7 +27,7 @@ class ExecCommandDialog(QtGui.QDialog, Ui_ExecCommandDialog):
def __init__(self, parent, command, params):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self.setWindowTitle("Executing {}".format(command))
@@ -57,4 +57,4 @@ class ExecCommandDialog(QtGui.QDialog, Ui_ExecCommandDialog):
self._process.kill()
self._process.waitForFinished()
QtGui.QDialog.done(self, result)
super().done(result)

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,81 +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, QtWebKit
from ..ui.getting_started_dialog_ui import Ui_GettingStartedDialog
from ..utils.get_resource import get_resource
from ..local_config import LocalConfig
class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
"""
GettingStarted dialog.
"""
def __init__(self, parent):
QtGui.QDialog.__init__(self, 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(QtWebKit.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self._local_config = LocalConfig.instance()
gui_settings = self._local_config.loadSectionSettings("GUI", {"hide_getting_started_dialog": False})
self.uiCheckBox.setChecked(gui_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
"""
self._local_config.saveSectionSettings("GUI", {"hide_getting_started_dialog": self.uiCheckBox.isChecked()})
QtGui.QDialog.done(self, 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:
QtGui.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))

View File

@@ -18,12 +18,12 @@
import os
import re
from ..qt import QtGui
from ..qt import QtWidgets
from ..topology import Topology
from ..ui.idlepc_dialog_ui import Ui_IdlePCDialog
class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
class IdlePCDialog(QtWidgets.QDialog, Ui_IdlePCDialog):
"""
Idle-PC dialog.
@@ -31,10 +31,10 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
def __init__(self, router, idlepcs, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applySlot)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Help).clicked.connect(self._helpSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applySlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._helpSlot)
self._router = router
self._idlepcs = idlepcs
@@ -59,8 +59,7 @@ Finding the right idle-pc value is a trial and error process, consisting of appl
Select each value that appears in the list and click Apply, and note the CPU usage a few moments later. When you have found the value that minimises the CPU usage, apply that value.
"""
QtGui.QMessageBox.information(self, "Hints for Idle-PC", help_text)
QtWidgets.QMessageBox.information(self, "Hints for Idle-PC", help_text)
def _applySlot(self):
"""
@@ -68,7 +67,7 @@ Select each value that appears in the list and click Apply, and note the CPU usa
"""
if not self.uiComboBox.count():
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry could not find a valid Idle-PC value, please check again with Cisco IOS in a different state")
QtWidgets.QMessageBox.critical(self, "Idle-PC", "Sorry could not find a valid Idle-PC value, please check again with Cisco IOS in a different state")
return
idlepc = self.uiComboBox.itemData(self.uiComboBox.currentIndex())
@@ -90,4 +89,4 @@ Select each value that appears in the list and click Apply, and note the CPU usa
if result:
self._applySlot()
QtGui.QDialog.done(self, result)
super().done(result)

View File

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

View File

@@ -16,12 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
from ..ui.new_project_dialog_ui import Ui_NewProjectDialog
from ..settings import ENABLE_CLOUD
class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
class NewProjectDialog(QtWidgets.QDialog, Ui_NewProjectDialog):
"""
New project dialog.
@@ -33,7 +32,7 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
def __init__(self, parent, showed_from_startup=False):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._main_window = parent
@@ -46,8 +45,6 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
self.uiLocationBrowserToolButton.clicked.connect(self._projectPathSlot)
self.uiOpenProjectPushButton.clicked.connect(self._openProjectActionSlot)
self.uiRecentProjectsPushButton.clicked.connect(self._showRecentProjectsSlot)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
if not showed_from_startup:
self.uiOpenProjectPushButton.hide()
@@ -72,8 +69,9 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
Slot to select the a new project location.
"""
path = QtGui.QFileDialog.getSaveFileName(self, "Project location", os.path.join(self._main_window.projectsDirPath(),
self.uiNameLineEdit.text()))
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Project location", os.path.join(self._main_window.projectsDirPath(),
self.uiNameLineEdit.text()))
if path:
self.uiLocationLineEdit.setText(path)
@@ -104,7 +102,7 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
lot to show all the recent projects in a menu.
"""
menu = QtGui.QMenu()
menu = QtWidgets.QMenu()
menu.triggered.connect(self._menuTriggeredSlot)
for action in self._main_window._recent_file_actions:
menu.addAction(action)
@@ -115,26 +113,23 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
if result:
project_name = self.uiNameLineEdit.text()
project_location = self.uiLocationLineEdit.text()
if self.uiCloudRadioButton.isChecked():
project_type = "cloud"
else:
project_type = "local"
project_type = "local"
if not project_name:
QtGui.QMessageBox.critical(self, "New project", "Project name is empty")
QtWidgets.QMessageBox.critical(self, "New project", "Project name is empty")
return
if not project_location:
QtGui.QMessageBox.critical(self, "New project", "Project location is empty")
QtWidgets.QMessageBox.critical(self, "New project", "Project location is empty")
return
if os.path.isdir(project_location):
reply = QtGui.QMessageBox.question(self,
"New project",
"Location {} already exists, overwrite it?".format(project_location),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.No:
reply = QtWidgets.QMessageBox.question(self,
"New project",
"Location {} already exists, overwrite it?".format(project_location),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return
self._project_settings["project_name"] = project_name
@@ -142,4 +137,4 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
self._project_settings["project_files_dir"] = project_location
self._project_settings["project_type"] = project_type
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -19,17 +19,14 @@
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
from ..ui.node_configurator_dialog_ui import Ui_NodeConfiguratorDialog
from ..qt import QtCore, QtGui, QtWidgets
from ..ui.node_properties_dialog_ui import Ui_NodePropertiesDialog
class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
"""
Node configurator implementation.
Node properties implementation.
:param node_items: list of NodeItem instances
:param parent: parent widget
@@ -37,41 +34,44 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
def __init__(self, node_items, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._node_items = node_items
self._parent_items = {}
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
self.previousItem = None
self.previousPage = None
# load the empty page widget by default
self.uiEmptyPageWidget = self.uiConfigStackedWidget.findChildren(QtGui.QWidget, "uiEmptyPageWidget")[0]
self.uiEmptyPageWidget = self.uiConfigStackedWidget.findChildren(QtWidgets.QWidget, "uiEmptyPageWidget")[0]
self.uiConfigStackedWidget.setCurrentWidget(self.uiEmptyPageWidget)
self._loadNodeItems()
self.splitter.setSizes([250, 600])
self._loadNodeItems()
self.uiNodesTreeWidget.itemClicked.connect(self.showConfigurationPageSlot)
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
def _loadNodeItems(self):
"""
Loads the nodes into the Node configurator QTreeWidget
Loads the nodes into the Node properties QTreeWidget
"""
# create the parent (group) items
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:
item = QtGui.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
item = QtWidgets.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
item.setIcon(0, QtGui.QIcon(node_item.node().defaultSymbol()))
item.setExpanded(True)
self._parent_items[parent] = item
@@ -81,11 +81,19 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
if not node_item.node().initialized():
continue
parent = " {} group".format(str(node_item.node()))
item = ConfigurationPageItem(self._parent_items[parent], node_item)
ConfigurationPageItem(self._parent_items[parent], node_item)
# sort the tree
self.uiNodesTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
if len(self._node_items) == 1:
parent = " {} group".format(str(node_item.node()))
item = self._parent_items[parent].child(0)
item.setSelected(True)
self.uiNodesTreeWidget.setCurrentItem(item)
self.showConfigurationPageSlot(item, 0)
self.splitter.setSizes([0, 600])
def showConfigurationPageSlot(self, item, column):
"""
Shows a configuration page widget.
@@ -122,11 +130,11 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
self.uiConfigStackedWidget.setCurrentWidget(page)
if page != self.uiEmptyPageWidget:
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).setEnabled(True)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(True)
else:
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
def on_uiButtonBox_clicked(self, button):
"""
@@ -136,18 +144,15 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
"""
try:
from gns3.main_window import MainWindow
if button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply):
if button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply):
self.applySettings()
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset):
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset):
self.resetSettings()
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Cancel):
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.reject(self)
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
self.applySettings()
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.accept(self)
QtWidgets.QDialog.accept(self)
except ConfigurationError:
pass
@@ -208,7 +213,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
child.setSettings(child.node().settings().copy())
class ConfigurationPageItem(QtGui.QTreeWidgetItem):
class ConfigurationPageItem(QtWidgets.QTreeWidgetItem):
"""
Item for the QTreeWidget instance.
@@ -221,7 +226,7 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
def __init__(self, parent, node_item):
self._node = node_item.node()
QtGui.QTreeWidgetItem.__init__(self, parent, [self._node.name()])
super().__init__(parent, [self._node.name()])
# return the configuration page widget used to configure the node.
self._page = self._node.configPage()
@@ -242,7 +247,7 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
def page(self):
"""
Returns the page widget to be displayed by the node configurator.
Returns the page widget to be displayed by the node properties dialog.
:returns: QWidget instance
"""
@@ -285,4 +290,4 @@ class ConfigurationError(Exception):
def __init__(self):
Exception.__init__(self)
super().__init__()

View File

@@ -19,19 +19,15 @@
Dialog to load module and built-in preference pages.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets
from ..ui.preferences_dialog_ui import Ui_PreferencesDialog
from ..pages.server_preferences_page import ServerPreferencesPage
from ..pages.general_preferences_page import GeneralPreferencesPage
from ..pages.cloud_preferences_page import CloudPreferencesPage
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
from ..modules import MODULES
from ..settings import ENABLE_CLOUD
from ..http_client import HTTPClient
from ..progress import Progress
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
"""
Preferences dialog implementation.
@@ -41,17 +37,37 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
def __init__(self, parent):
QtGui.QDialog.__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.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applyPreferences)
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
self._applyButton.clicked.connect(self._applyPreferences)
self._applyButton.setEnabled(False)
self._applyButton.setStyleSheet("QPushButton:disabled {color: gray}")
self._items = []
self._loadPreferencePages()
# select the first available page
self.uiTreeWidget.setCurrentItem(self._items[0])
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
# Something has change?
self._modified = False
def _loadPreferencePages(self):
"""
@@ -64,18 +80,17 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
ServerPreferencesPage,
PacketCapturePreferencesPage,
]
if ENABLE_CLOUD:
pages.append(CloudPreferencesPage)
for page in pages:
preferences_page = page(self)
preferences_page.loadPreferences()
name = preferences_page.windowTitle()
item = QtGui.QTreeWidgetItem(self.uiTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiTreeWidget)
item.setText(0, name)
item.setData(0, QtCore.Qt.UserRole, preferences_page)
self.uiStackedWidget.addWidget(preferences_page)
self._items.append(item)
self._watchForChanges(preferences_page)
# load module preference pages
for module in MODULES:
@@ -85,17 +100,43 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
preferences_page = cls()
preferences_page.loadPreferences()
name = preferences_page.windowTitle()
item = QtGui.QTreeWidgetItem(parent)
item = QtWidgets.QTreeWidgetItem(parent)
item.setText(0, name)
item.setData(0, QtCore.Qt.UserRole, preferences_page)
self.uiStackedWidget.addWidget(preferences_page)
self._items.append(item)
if cls is preference_pages[0]:
parent = item
self._watchForChanges(preferences_page)
# expand all items by default
self.uiTreeWidget.expandAll()
def _watchForChanges(self, preferences_page):
"""
Connect all the widget of a page to check if something has change
"""
# Class name, changed signal
widget_to_watch = {
QtWidgets.QLineEdit: "textChanged",
QtWidgets.QTreeWidget: "itemChanged",
QtWidgets.QComboBox: "currentIndexChanged",
QtWidgets.QSpinBox: "valueChanged",
QtWidgets.QAbstractButton: "pressed"
}
for widget, signal in widget_to_watch.items():
for children in preferences_page.findChildren(widget):
getattr(children, signal).connect(self._preferenceChangeSlot)
def _preferenceChangeSlot(self, *args):
"""
Called when somthing change in the preference dialog
"""
self._applyButton.setEnabled(True)
self._modified = True
def _showPreferencesPageSlot(self, current, previous):
"""
Shows a preference page in the current dialog.
@@ -132,6 +173,9 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
# if page.savePreferences() returns None, assume success
if ok is not None and not ok:
success = False
if success:
self._applyButton.setEnabled(False)
self._modified = False
return success
def reject(self):
@@ -139,9 +183,15 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
Closes this dialog.
"""
from gns3.main_window import MainWindow
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.reject(self)
if self._modified:
reply = QtWidgets.QMessageBox.warning(self,
"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):
"""
@@ -149,11 +199,10 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
"""
# close the nodes dock to refresh the node list
main_window = self.parentWidget()
from ..main_window import MainWindow
main_window = MainWindow.instance()
main_window.uiNodesDockWidget.setVisible(False)
main_window.uiNodesDockWidget.setWindowTitle("")
if self._applyPreferences():
from gns3.main_window import MainWindow
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.accept(self)
QtWidgets.QDialog.accept(self)

View File

@@ -0,0 +1,295 @@
# -*- 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 sys
import os
import psutil
from gns3.qt import QtCore, QtWidgets, QtGui
from gns3.servers import Servers
from ..gns3_vm import GNS3VM
from ..dialogs.preferences_dialog import PreferencesDialog
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):
"""
Base class for VM wizard.
"""
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self._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"])
# by default all radio buttons are unchecked
self.uiVmwareRadioButton.setAutoExclusive(False)
self.uiVirtualBoxRadioButton.setAutoExclusive(False)
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 (required for VMware player) is probably not installed. You can download it from https://www.vmware.com/support/developer/vix-api/")
return
self._refreshVMListSlot()
def _listVirtualBoxVMsSlot(self):
"""
Slot to refresh the VirtualBox VMs list.
"""
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
self.uiVmwareRadioButton.setChecked(False)
from gns3.modules import VirtualBox
settings = VirtualBox.instance().settings()
if not os.path.exists(settings["vboxmanage_path"]):
QtWidgets.QMessageBox.critical(self, "VirtualBox", "VBoxManage could not be found, VirtualBox is probably not installed")
return
self._refreshVMListSlot()
def showit(self):
"""
Either this dialog should be automatically showed at startup.
:returns: boolean
"""
return not self.uiShowCheckBox.isChecked()
def _setPreferencesPane(self, dialog, name):
"""
Finds the first child of the QTreeWidgetItem name.
:param dialog: PreferencesDialog instance
:param name: QTreeWidgetItem name
:returns: current QWidget
"""
pane = dialog.uiTreeWidget.findItems(name, QtCore.Qt.MatchFixedString)[0]
child_pane = pane.child(0)
dialog.uiTreeWidget.setCurrentItem(child_pane)
return dialog.uiStackedWidget.currentWidget()
def initializePage(self, page_id):
"""
Initialize Wizard pages.
:param page_id: page identifier
"""
super().initializePage(page_id)
if self.page(page_id) == self.uiVMWizardPage:
cpu_count = psutil.cpu_count()
self.uiCPUSpinBox.setValue(cpu_count)
# we want to allocate half of the available physical memory
ram = int(psutil.virtual_memory().total / (1024 * 1024) / 2)
# value must be a multiple of 4 (VMware requirement)
ram -= ram % 4
self.uiRAMSpinBox.setValue(ram)
def validateCurrentPage(self):
"""
Validates the settings.
"""
gns3_vm = GNS3VM.instance()
servers = Servers.instance()
if self.currentPage() == self.uiVMWizardPage:
vmname = self.uiVMListComboBox.currentText()
if vmname:
# save the GNS3 VM settings
vm_settings = {"auto_start": True,
"vmname": vmname,
"vmx_path": self.uiVMListComboBox.currentData()}
if self.uiVmwareRadioButton.isChecked():
vm_settings["virtualization"] = "VMware"
elif self.uiVirtualBoxRadioButton.isChecked():
vm_settings["virtualization"] = "VirtualBox"
gns3_vm.setSettings(vm_settings)
servers.save()
# set the vCPU count and RAM
vpcus = self.uiCPUSpinBox.value()
ram = self.uiRAMSpinBox.value()
if ram < 1024:
QtWidgets.QMessageBox.warning(self, "GNS3 VM memory", "It is recommended to allocate a minimum of 1024 MB of RAM to the GNS3 VM")
available_ram = int(psutil.virtual_memory().available / (1024 * 1024))
if ram > available_ram:
QtWidgets.QMessageBox.warning(self, "GNS3 VM memory", "You have probably allocated too much memory for the GNS3 VM! (available memory is {} MB)".format(available_ram))
if gns3_vm.setvCPUandRAM(vpcus, ram) is False:
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Could not configure vCPUs and RAM amounts for the GNS3 VM")
# start the GNS3 VM
servers.initVMServer()
worker = WaitForVMWorker()
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()
new_local_server_ip = gns3_vm.adjustLocalServerIP()
self.uiShowCheckBox.setChecked(True)
# restart the local server if necessary
if new_local_server_ip != previous_local_server_ip:
servers.stopLocalServer(wait=True)
if servers.startLocalServer():
worker = WaitForConnectionWorker(new_local_server_ip, servers.localServer().port())
dialog = ProgressDialog(worker, "Local server", "Connecting...", "Cancel", busy=True, parent=self)
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:
use_local_server = self.uiLocalRadioButton.isChecked()
if use_local_server:
# deactivate the GNS3 VM if using the local server
vm_settings = {"auto_start": False}
gns3_vm.setSettings(vm_settings)
servers.save()
self.uiShowCheckBox.setChecked(True)
from gns3.modules import Dynamips
Dynamips.instance().setSettings({"use_local_server": use_local_server})
if sys.platform.startswith("linux"):
# IOU only works on Linux
from gns3.modules import IOU
IOU.instance().setSettings({"use_local_server": use_local_server})
from gns3.modules import Qemu
Qemu.instance().setSettings({"use_local_server": use_local_server})
from gns3.modules import VPCS
VPCS.instance().setSettings({"use_local_server": use_local_server})
dialog = PreferencesDialog(self)
if self.uiAddIOSRouterCheckBox.isChecked():
self._setPreferencesPane(dialog, "Dynamips").uiNewIOSRouterPushButton.clicked.emit(False)
if self.uiAddIOUDeviceCheckBox.isChecked():
self._setPreferencesPane(dialog, "IOS on UNIX").uiNewIOUDevicePushButton.clicked.emit(False)
if self.uiAddQemuVMcheckBox.isChecked():
self._setPreferencesPane(dialog, "QEMU").uiNewQemuVMPushButton.clicked.emit(False)
if self.uiAddVirtualBoxVMcheckBox.isChecked():
self._setPreferencesPane(dialog, "VirtualBox").uiNewVirtualBoxVMPushButton.clicked.emit(False)
if self.uiAddVMwareVMcheckBox.isChecked():
self._setPreferencesPane(dialog, "VMware").uiNewVMwareVMPushButton.clicked.emit(False)
dialog.exec_()
return True
def _refreshVMListSlot(self):
"""
Refresh the list of VM available in VMware or VirtualBox.
"""
server = Servers.instance().localServer()
if self.uiVmwareRadioButton.isChecked():
server.get("/vmware/vms", self._getVMsFromServerCallback)
elif self.uiVirtualBoxRadioButton.isChecked():
server.get("/virtualbox/vms", self._getVMsFromServerCallback)
def _getVMsFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getVMsFromServer.
:param progress_dialog: QProgressDialog instance
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "VM List", "{}".format(result["message"]))
else:
self.uiVMListComboBox.clear()
for vm in result:
self.uiVMListComboBox.addItem(vm["vmname"], vm.get("vmx_path", ""))
gns3_vm = Servers.instance().vmSettings()
index = self.uiVMListComboBox.findText(gns3_vm["vmname"])
if index != -1:
self.uiVMListComboBox.setCurrentIndex(index)
else:
index = self.uiVMListComboBox.findText("GNS3 VM")
if index != -1:
self.uiVMListComboBox.setCurrentIndex(index)
else:
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "Could not find a VM named 'GNS3 VM', is it imported in VMware or VirtualBox?")
def done(self, result):
"""
This dialog is closed.
:param result: ignored
"""
settings = self.parentWidget().settings()
settings["hide_setup_wizard"] = self.uiShowCheckBox.isChecked()
self.parentWidget().setSettings(settings)
super().done(result)
def nextId(self):
"""
Wizard rules!
"""
current_id = self.currentId()
if self.page(current_id) == self.uiServerWizardPage and not self.uiVMRadioButton.isChecked():
# skip the GNS3 VM page if using the local server.
return self.uiServerWizardPage.nextId() + 1
return QtWidgets.QWizard.nextId(self)

View File

@@ -24,7 +24,7 @@ import re
import time
import os
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets
from ..utils.progress_dialog import ProgressDialog
from ..utils.process_files_worker import ProcessFilesWorker
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
@@ -32,7 +32,7 @@ from ..topology import Topology
from ..node import Node
class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
"""
Snapshots dialog implementation.
@@ -42,7 +42,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
def __init__(self, parent, project_path, project_files_dir):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._project_path = project_path
@@ -70,7 +70,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
snapshot_name = match.group(1)
snapshot_date = match.group(2)[:2] + '/' + match.group(2)[2:4] + '/' + match.group(2)[4:]
snapshot_time = match.group(3)[:2] + ':' + match.group(3)[2:4] + ':' + match.group(3)[4:]
item = QtGui.QListWidgetItem(self.uiSnapshotsList)
item = QtWidgets.QListWidgetItem(self.uiSnapshotsList)
item.setText("{} on {} at {}".format(snapshot_name, snapshot_date, snapshot_time))
item.setData(QtCore.Qt.UserRole, os.path.join(snapshot_dir, snapshot))
@@ -89,7 +89,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
Slot to create a snapshot.
"""
snapshot_name, ok = QtGui.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtGui.QLineEdit.Normal, "Unnamed")
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.Normal, "Unnamed")
if ok and snapshot_name:
from ..main_window import MainWindow
MainWindow.instance().saveProject(self._project_path)
@@ -134,9 +134,9 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
snapshot_name = match.group(1)
else:
snapshot_name = "Unknown"
reply = QtGui.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot \"{}\" was taken?".format(snapshot_name),
QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel)
if reply == QtGui.QMessageBox.Cancel:
reply = QtWidgets.QMessageBox.question(self, "Snapshots", "This will discard any changes made to your project since the snapshot \"{}\" was taken?".format(snapshot_name),
QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Cancel)
if reply == QtWidgets.QMessageBox.Cancel:
return
# stop all the nodes
@@ -167,7 +167,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
os.remove(self._project_path)
shutil.copy(os.path.join(snapshot_path, os.path.basename(self._project_path)), self._project_path)
except OSError as e:
QtGui.QMessageBox.critical(self, "Restore snapshot", "Cannot restore snapshot: {}".format(e))
QtWidgets.QMessageBox.critical(self, "Restore snapshot", "Cannot restore snapshot: {}".format(e))
else:
worker = ProcessFilesWorker(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)

View File

@@ -19,11 +19,11 @@
Style editor to edit Shape items.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets, QtGui
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
"""
Style editor dialog.
@@ -34,13 +34,13 @@ class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
def __init__(self, parent, items):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._items = items
self.uiColorPushButton.clicked.connect(self._setColorSlot)
self.uiBorderColorPushButton.clicked.connect(self._setBorderColorSlot)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiBorderStyleComboBox.addItem("Solid", QtCore.Qt.SolidLine)
self.uiBorderStyleComboBox.addItem("Dash", QtCore.Qt.DashLine)
@@ -74,7 +74,7 @@ class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
Slot to select the filling color.
"""
color = QtGui.QColorDialog.getColor(self._color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._color = color
self.uiColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._color.red(),
@@ -87,7 +87,7 @@ class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
Slot to select the border color.
"""
color = QtGui.QColorDialog.getColor(self._border_color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
color = QtWidgets.QColorDialog.getColor(self._border_color, self, "Select Color", QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._border_color = color
self.uiBorderColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(self._border_color.red(),
@@ -118,4 +118,4 @@ class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
if result:
self._applyPreferencesSlot()
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -16,15 +16,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Dialog to change the topology symbol of NodeItems
Dialog to change node symbols.
"""
from ..qt import QtSvg, QtCore, QtGui
import os
from ..qt import QtCore, QtGui, QtWidgets
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from ..ui.symbol_selection_dialog_ui import Ui_SymbolSelectionDialog
from ..node import Node
from ..servers import Servers
import logging
log = logging.getLogger(__name__)
class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
"""
Symbol selection dialog.
@@ -33,90 +39,158 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
:param items: list of items
"""
def __init__(self, parent, items=None, symbol=None, category=None):
def __init__(self, parent, items=None, symbol=None):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._items = items
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
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
selected_category = category
if not self._items:
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).hide()
# current categories
categories = {"Routers": Node.routers,
"Switches": Node.switches,
"End devices": Node.end_devices,
"Security devices": Node.security_devices
}
index = 0
for name, category in categories.items():
self.uiCategoryComboBox.addItem(name, category)
if category == selected_category:
self.uiCategoryComboBox.setCurrentIndex(index)
index += 1
else:
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
custom_symbol = items[0].defaultRenderer().objectName()
if not custom_symbol:
symbol_name = items[0].node().defaultSymbol()
else:
symbol_name = custom_symbol
selected_symbol = symbol_name
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
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(".normal.svg"):
name = symbol[:-11]
item = QtGui.QListWidgetItem(self.uiSymbolListWidget)
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:
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)
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.
:param checked: either the button is checked or not
"""
if checked:
self.uiCustomSymbolGroupBox.setEnabled(True)
self.uiCustomSymbolGroupBox.show()
self.uiBuiltInGroupBox.setEnabled(False)
self.uiBuiltInGroupBox.hide()
self.adjustSize()
def _builtInSymbolToggledSlot(self, checked):
"""
Slot for when the built-in symbol radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiCustomSymbolGroupBox.setEnabled(False)
self.uiCustomSymbolGroupBox.hide()
self.uiBuiltInGroupBox.setEnabled(True)
self.uiBuiltInGroupBox.show()
self.adjustSize()
def _applyPreferencesSlot(self):
"""
Applies the selected symbol to the items.
"""
current = self.uiSymbolListWidget.currentItem()
if current:
name = current.text()
path = ":/symbols/{}.normal.svg".format(name)
default_renderer = QtSvg.QSvgRenderer(path)
default_renderer.setObjectName(path)
path = ":/symbols/{}.selected.svg".format(name)
hover_renderer = QtSvg.QSvgRenderer(path)
hover_renderer.setObjectName(path)
symbol_path = self.getSymbol()
pixmap = QtGui.QPixmap(symbol_path)
if not pixmap.isNull():
for item in self._items:
item.setDefaultRenderer(default_renderer)
item.setHoverRenderer(hover_renderer)
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
def getSymbols(self):
return True
current = self.uiSymbolListWidget.currentItem()
if current:
name = current.text()
normal_symbol = ":/symbols/{}.normal.svg".format(name)
selected_symbol = ":/symbols/{}.selected.svg".format(name)
return normal_symbol, selected_symbol
def getSymbol(self):
def getCategory(self):
if self.uiSymbolListWidget.isEnabled():
current = self.uiSymbolListWidget.currentItem()
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:
return self.uiSymbolLineEdit.text()
return None
return self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
def _symbolBrowserSlot(self):
# supported image file formats
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
self._symbols_dir = os.path.dirname(path)
self.uiSymbolLineEdit.clear()
self.uiSymbolLineEdit.setText(path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(path))
def done(self, result):
"""
@@ -125,6 +199,10 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
:param result: boolean (accepted or rejected)
"""
if result and self._items:
self._applyPreferencesSlot()
QtGui.QDialog.done(self, result)
if result:
if not self.uiSymbolListWidget.isEnabled() and not os.path.exists(self.uiSymbolLineEdit.text()):
QtWidgets.QMessageBox.critical(self, "Custom symbol", "Invalid path to custom symbol: {}".format(self.uiSymbolLineEdit.text()))
result = 0
elif result and self._items and not self._applyPreferencesSlot():
result = 0
super().done(result)

View File

@@ -19,12 +19,11 @@
Text editor to edit Note items.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets
from ..ui.text_editor_dialog_ui import Ui_TextEditorDialog
class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
"""
Text editor dialog.
@@ -34,36 +33,49 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
def __init__(self, parent, items):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self._items = items
self.uiFontPushButton.clicked.connect(self._setFontSlot)
self.uiColorPushButton.clicked.connect(self._setColorSlot)
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
# use the first item in the list as the model
first_item = items[0]
self._color = first_item.defaultTextColor()
self._setColor(first_item.defaultTextColor())
self.uiRotationSpinBox.setValue(first_item.rotation())
self.uiColorPushButton.setStyleSheet("background-color: {}".format(self._color.name()))
self.uiPlainTextEdit.setPlainText(first_item.toPlainText())
self.uiPlainTextEdit.setFont(first_item.font())
self.uiPlainTextEdit.setStyleSheet("color : {}".format(self._color.name()))
if not first_item.editable():
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()
def _setColor(self, color):
self._color = color
self.uiColorPushButton.setStyleSheet("background-color: rgba({}, {}, {}, {});".format(color.red(),
color.green(),
color.blue(),
color.alpha()))
self.uiPlainTextEdit.setStyleSheet("color: rgba({}, {}, {}, {});".format(color.red(),
color.green(),
color.blue(),
color.alpha()))
def _setFontSlot(self):
"""
Slot to select the font.
"""
selected_font, ok = QtGui.QFontDialog.getFont(self.uiPlainTextEdit.font(), self)
selected_font, ok = QtWidgets.QFontDialog.getFont(self.uiPlainTextEdit.font(), self)
if ok:
self.uiPlainTextEdit.setFont(selected_font)
@@ -72,11 +84,9 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
Slot to select the color.
"""
color = QtGui.QColorDialog.getColor(self._color, self)
color = QtWidgets.QColorDialog.getColor(self._color, self, None, QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self._color = color
self.uiColorPushButton.setStyleSheet("background-color: {}".format(self._color.name()))
self.uiPlainTextEdit.setStyleSheet("color : {}".format(self._color.name()))
self._setColor(color)
def _applyPreferencesSlot(self):
"""
@@ -84,9 +94,11 @@ class TextEditorDialog(QtGui.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())
@@ -99,4 +111,4 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
if result:
self._applyPreferencesSlot()
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -0,0 +1,194 @@
#!/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 .vm_wizard import VMWizard
from gns3.qt import QtWidgets
from gns3.servers import Servers
class VMWithImagesWizard(VMWizard):
"""
Base class for VM wizard with image management (Qemu, IOU...)
:param devices: List of existing device for this type
:param use_local_server: Value the use_local_server settings for this module
:param parent: parent widget
"""
def __init__(self, devices, use_local_server, parent):
# The list of images combo box (Qemu support multiple images)
self._images_combo_boxes = set()
# The list of radio button for existing image or new images
self._radio_existing_images_buttons = set()
super().__init__(devices, use_local_server, parent)
def refreshImageStepsButtons(self):
"""
When changing the server type (remote or local)
Refresh all the image selectors
"""
for radio_button in self._radio_existing_images_buttons:
radio_button.setChecked(radio_button.isChecked())
def _vmToggledSlot(self, checked):
super()._vmToggledSlot(checked)
if checked:
self.refreshImageStepsButtons()
def _remoteServerToggledSlot(self, checked):
super()._remoteServerToggledSlot(checked)
if checked:
self.refreshImageStepsButtons()
def _localToggledSlot(self, checked):
super()._localToggledSlot(checked)
if checked:
self.refreshImageStepsButtons()
def addImageSelector(self, radio_button, combo_box, line_edit, browser, image_selector, create_button=None, create_image_wizard=None, image_suffix=""):
"""
Add a remote image selector
:param radio_button: Radio button which toggle display of the listbox
:param combo_box: The image choice combo box
:param line_edit: The edit for the image
:param browser: file upload browser button
:param image_selector: function which display an image selector and return path
:param create_button: Image create button None if you don't need one
:param create_image_wizard: Wizard Class for creating a new image
"""
combo_box.currentIndexChanged.connect(lambda index: self._imageListIndexChangedSlot(index, combo_box, line_edit))
self._images_combo_boxes.add(combo_box)
browser.clicked.connect(lambda: self._imageBrowserSlot(line_edit, image_selector))
if create_button:
assert create_image_wizard is not None
create_button.clicked.connect(lambda: self._imageCreateSlot(line_edit, create_image_wizard, image_suffix))
self._existingImageToggledSlot(True, combo_box, line_edit, browser, create_button)
radio_button.toggled.connect(lambda checked: self._existingImageToggledSlot(checked, combo_box, line_edit, browser, create_button))
self._radio_existing_images_buttons.add(radio_button)
def _imageCreateSlot(self, line_edit, create_image_wizard, image_suffix):
server = Servers.instance().getServerFromString(self.getSettings()["server"])
create_dialog = create_image_wizard(self, server, self.uiNameLineEdit.text() + image_suffix)
if QtWidgets.QDialog.Accepted == create_dialog.exec_():
line_edit.setText(create_dialog.uiLocationLineEdit.text())
def _imageBrowserSlot(self, line_edit, image_selector):
"""
Slot to open a file browser and select an image.
"""
server = Servers.instance().getServerFromString(self.getSettings()["server"])
path = image_selector(self, server)
if not path:
return
line_edit.clear()
line_edit.setText(path)
def _imageListIndexChangedSlot(self, index, combo_box, line_edit):
"""
User select a different image in the combo box
"""
item = combo_box.itemData(index)
if item and item["path"]:
line_edit.setText(item["path"])
else:
line_edit.setText("")
def _existingImageToggledSlot(self, checked, combo_box, line_edit, browser, create_button):
"""
User select the option of using an existing image
"""
if create_button:
create_button.hide()
if checked:
combo_box.show()
browser.hide()
line_edit.hide()
if combo_box.count() > 0:
line_edit.setText(combo_box.itemData(combo_box.currentIndex())["path"])
else:
combo_box.hide()
line_edit.setText("")
line_edit.show()
browser.show()
if create_button:
create_button.show()
def loadImagesList(self, endpoint):
"""
Fill the list box with available Images"
:param endpoint: server endpoint with the list of Images
"""
self._server.get(endpoint, self._getImagesFromServerCallback)
def _getImagesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for loadImagesList.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Images", "Error while getting the VMs: {}".format(result["message"]))
return
# 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

166
gns3/dialogs/vm_wizard.py Normal file
View File

@@ -0,0 +1,166 @@
# -*- 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 sys
from gns3.qt import QtWidgets
from gns3.servers import Servers
from gns3.gns3_vm import GNS3VM
class VMWizard(QtWidgets.QWizard):
"""
Base class for VM wizard.
:param devices: List of existing device for this type
:param use_local_server: Value the use_local_server settings for this module
:param parent: parent widget
"""
def __init__(self, devices, use_local_server, parent):
super().__init__(parent)
self.setupUi(self)
self.setModal(True)
self._devices = devices
self._use_local_server = use_local_server
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
self.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)
# By default we use the local server
self._server = Servers.instance().localServer()
self.uiLocalRadioButton.setChecked(True)
self._localToggledSlot(True)
if Servers.instance().isNonLocalServerConfigured() is False:
# 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.
: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 setStartId(self, index):
"""
Which page should we use when starting the Wizard
"""
super().setStartId(index)
# If we skip the initial page (choosing a server)
# we check the settings
if index != 0:
self.uiLocalRadioButton.setChecked(True)
def initializePage(self, page_id):
if self.page(page_id) == self.uiServerWizardPage:
self.uiRemoteServersComboBox.clear()
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.isEnabled():
self.uiLocalRadioButton.setChecked(True)
else:
if self.uiRemoteRadioButton.isEnabled():
self.uiRemoteRadioButton.setChecked(True)
else:
self.uiLocalRadioButton.setChecked(True)
def validateCurrentPage(self):
"""
Validates the server.
"""
if hasattr(self, "uiNamePlatformWizardPage") and self.currentPage() == self.uiNamePlatformWizardPage:
name = self.uiNameLineEdit.text()
for device in self._devices.values():
if device["name"] == name:
QtWidgets.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
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:
self._server = Servers.instance().localServer()
return True
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)

309
gns3/gns3_vm.py Normal file
View File

@@ -0,0 +1,309 @@
# -*- 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/>.
"""
Manages the GNS3 VM.
"""
import sys
import subprocess
import codecs
import shutil
from .qt import QtNetwork
from collections import OrderedDict
from .servers import Servers
import logging
log = logging.getLogger(__name__)
class GNS3VM:
"""
GNS3 VM management class.
"""
def __init__(self):
self._is_running = False
# The current running vboxmanage and vmrun process
self._running_process = None
def settings(self):
"""
Returns the GNS3 VM settings.
:returns: GNS3 VM settings (dict)
"""
return Servers.instance().vmSettings()
def setSettings(self, settings):
"""
Set new GNS3 VM settings.
:param settings: GNS3 VM settings (dict)
"""
Servers.instance().setVMsettings(settings)
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()
vmrun_path = vmware_settings["vmrun_path"]
if sys.platform.startswith("darwin"):
command = [vmrun_path, "-T", "fusion", subcommand]
else:
host_type = vmware_settings["host_type"]
command = [vmrun_path, "-T", host_type, subcommand]
command.extend(args)
log.debug("Executing vmrun with command: {}".format(command))
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()
vboxmanage_path = virtualbox_settings["vboxmanage_path"]
command = [vboxmanage_path, "--nologo", subcommand]
command.extend(args)
log.debug("Executing VBoxManage with command: {}".format(command))
return self._process_check_output(command, timeout=timeout)
@staticmethod
def parse_vmx_file(path):
"""
Parses a VMX file.
:param path: path to the VMX file
:returns: dict
"""
pairs = OrderedDict()
encoding = "utf-8"
# get the first line to read the .encoding value
with open(path, "rb") as f:
line = f.readline().decode(encoding, errors="ignore")
if line.startswith("#!"):
# skip the shebang
line = f.readline().decode(encoding, errors="ignore")
try:
key, value = line.split('=', 1)
if key.strip().lower() == ".encoding":
file_encoding = value.strip('" ')
try:
codecs.lookup(file_encoding)
encoding = file_encoding
except LookupError:
log.warning("Invalid file encoding detected in '{}': {}".format(path, file_encoding))
except ValueError:
log.warning("Couldn't find file encoding in {}, using {}...".format(path, encoding))
# read the file with the correct encoding
with open(path, encoding=encoding, errors="ignore") as f:
for line in f.read().splitlines():
try:
key, value = line.split('=', 1)
pairs[key.strip().lower()] = value.strip('" ')
except ValueError:
continue
return pairs
@staticmethod
def write_vmx_file(path, pairs):
"""
Write a VMware VMX file.
:param path: path to the VMX file
:param pairs: settings to write
"""
encoding = "utf-8"
if ".encoding" in pairs:
file_encoding = pairs[".encoding"]
try:
codecs.lookup(file_encoding)
encoding = file_encoding
except LookupError:
log.warning("Invalid file encoding detected in '{}': {}".format(path, file_encoding))
with open(path, "w", encoding=encoding, errors="ignore") as f:
if sys.platform.startswith("linux"):
# write the shebang on the first line on Linux
vmware_path = shutil.which("vmware")
if vmware_path:
f.write("#!{}\n".format(vmware_path))
for key, value in pairs.items():
entry = '{} = "{}"\n'.format(key, value)
f.write(entry)
def autoStart(self):
"""
Automatically start the GNS3 VM at startup.
:returns: boolean
"""
vm_settings = Servers.instance().vmSettings()
return vm_settings["auto_start"]
def adjustLocalServerIP(self):
"""
Adjust the local server IP address to be in the same subnet as the GNS3 VM.
:returns: the local server IP/host address
"""
servers = Servers.instance()
local_server_settings = servers.localServerSettings()
if Servers.instance().vmSettings()["adjust_local_server_ip"]:
vm_server = servers.vmServer()
vm_ip_address = vm_server.host()
log.debug("GNS3 VM IP address is {}".format(vm_ip_address))
for interface in QtNetwork.QNetworkInterface.allInterfaces():
for address in interface.addressEntries():
ip = address.ip().toString()
prefix_length = address.prefixLength()
subnet = QtNetwork.QHostAddress.parseSubnet("{}/{}".format(ip, prefix_length))
if QtNetwork.QHostAddress(vm_ip_address).isInSubnet(subnet):
if local_server_settings["host"] != ip:
log.info("Adjust local server IP address to {}".format(ip))
servers.setLocalServerSettings({"host": ip})
servers.registerLocalServer()
servers.save()
return ip
return local_server_settings["host"]
def setRunning(self, value):
"""
Sets either the GNS3 VM is running or not.
:param value: boolean
"""
self._is_running = value
def isRunning(self):
"""
Returns either the GNS3 VM is running or not.
:returns: boolean
"""
return self._is_running
def setvCPUandRAM(self, vcpus, ram):
"""
Set the vCPU cores and RAM amount for the GNS3 VM.
:param vcpus: number of vCPU cores
:param ram: amount of memory
:returns: boolean
"""
vm_settings = self.settings()
if vm_settings["virtualization"] == "VMware":
try:
pairs = self.parse_vmx_file(vm_settings["vmx_path"])
pairs["numvcpus"] = str(vcpus)
pairs["memsize"] = str(ram)
self.write_vmx_file(vm_settings["vmx_path"], pairs)
except OSError as e:
log.error('Could not read/write VMware VMX file "{}": {}'.format(vm_settings["vmx_path"], e))
return False
elif vm_settings["virtualization"] == "VirtualBox":
try:
self.execute_vboxmanage("modifyvm", [vm_settings["vmname"], "--cpus", str(vcpus)], timeout=3)
self.execute_vboxmanage("modifyvm", [vm_settings["vmname"], "--memory", str(ram)], timeout=3)
except (OSError, subprocess.SubprocessError) as e:
log.error("Could not execute VBoxManage: {}".format(e), True)
return False
except subprocess.TimeoutExpired:
log.error("VBoxmanage timeout expired", True)
return False
log.info("GNS3 VM vCPU count set to {} and RAM to {} MB".format(vcpus, ram))
return True
def shutdown(self, force=False):
"""
Gracefully shutdowns the GNS3 VM.
"""
vm_settings = self.settings()
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)
except (OSError, subprocess.SubprocessError):
pass
except subprocess.TimeoutExpired:
log.warning("Could not ACPI shutdown the VM (timeout expired)")
self._is_running = False
@staticmethod
def instance():
"""
Singleton to return only on instance of GNS3VM
:returns: instance of GNS3VM
"""
if not hasattr(GNS3VM, "_instance") or GNS3VM._instance is None:
GNS3VM._instance = GNS3VM()
return GNS3VM._instance

File diff suppressed because it is too large Load Diff

View File

@@ -18,20 +18,25 @@
import json
import http
import copy
import ipaddress
import uuid
import urllib.parse
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
import logging
log = logging.getLogger(__name__)
class HttpBadRequest(Exception):
"""We raise bad request exception for logging them in Sentry"""
pass
@@ -40,7 +45,7 @@ class HTTPClient(QtCore.QObject):
"""
HTTP client.
:param url: URL to connect to the server
:param settings: Dictionnary with connection information to the server
:param network_manager: A QT network manager
"""
@@ -49,38 +54,124 @@ 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, url, network_manager, user=None, password=None):
def __init__(self, settings, network_manager):
super().__init__()
self._url = url
self._version = ""
url_settings = urllib.parse.urlparse(url)
self.scheme = url_settings.scheme
self.host = url_settings.netloc.split(":")[0]
self.port = url_settings.port
self._user = user
self._password = password
self._scheme = settings.get("protocol", "http")
self._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)
self._password = settings.get("password", None)
self._connected = False
self._local = True
self._cloud = False
self._gns3_vm = False
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
# A buffer used by progress download
self._buffer = {}
# create an unique ID
self._id = HTTPClient._instance_count
HTTPClient._instance_count += 1
def notify_progress_start_query(self, query_id):
def settings(self):
"""
Return a dictionnary with server settings
"""
settings = {"protocol": self.protocol(),
"ram_limit": self.RAMLimit(),
"host": self.host(),
"port": self.port(),
"user": self.user(),
"password": self._password}
if self.protocol() == "https":
settings["accept_insecure_certificate"] = self.acceptInsecureCertificate()
return settings
def acceptInsecureCertificate(self, certificate=None):
"""
Does the server accept this insecure SSL certificate digest
:param: Certificate digest
"""
return self._accept_insecure_certificate
def setAcceptInsecureCertificate(self, certificate):
"""
Does the server accept this insecure SSL certificate digest
:param: Certificate digest
"""
self._accept_insecure_certificate = certificate
def host(self):
"""
Host display to user
"""
return self._host
def setHost(self, host):
self._host = host
self._http_host = host
def port(self):
"""
Port display to user
"""
return self._port
def setPort(self, port):
self._port = port
self._http_port = port
def protocol(self):
"""
Transport protocol
"""
return self._scheme
def user(self):
"""
User login display to GNS3 user
"""
return self._user
def setUser(self, user):
self._user = user
def setPassword(self, password):
self._password = password
def notify_progress_start_query(self, query_id, progress_text, response):
"""
Called when a query start
"""
if HTTPClient._progress_callback:
HTTPClient._progress_callback.add_query_signal.emit(query_id, "Waiting for {scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port))
if progress_text:
HTTPClient._progress_callback.add_query_signal.emit(query_id, progress_text, response)
else:
if self._local:
HTTPClient._progress_callback.add_query_signal.emit(query_id, "Waiting for local GNS3 server", response)
else:
HTTPClient._progress_callback.add_query_signal.emit(query_id, "Waiting for {}".format(self.url()), response)
def notify_progress_end_query(cls, query_id):
"""
@@ -90,6 +181,20 @@ class HTTPClient(QtCore.QObject):
if HTTPClient._progress_callback:
HTTPClient._progress_callback.remove_query_signal.emit(query_id)
def notify_progress_upload(self, query_id, sent, total):
"""
Called when a query upload progress
"""
if HTTPClient._progress_callback:
HTTPClient._progress_callback.progress_signal.emit(query_id, sent, total)
def notify_progress_download(self, query_id, sent, total):
"""
Called when a query download progress
"""
if HTTPClient._progress_callback:
HTTPClient._progress_callback.progress_signal.emit(query_id, sent, total)
@classmethod
def setProgressCallback(cls, progress_callback):
"""
@@ -107,7 +212,7 @@ class HTTPClient(QtCore.QObject):
def url(self):
"""Returns current server url"""
return "{scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port)
return getNetworkUrl(self.protocol(), self.host(), self.port(), self.user(), self.settings())
def id(self):
"""
@@ -133,6 +238,24 @@ class HTTPClient(QtCore.QObject):
return self._local
def setGNS3VM(self, value):
"""
Sets either this is a connection to the GNS3 VM or not.
:param value: boolean
"""
self._gns3_vm = value
def isGNS3VM(self):
"""
Returns either this is a connection to the GNS3 VM or not.
:returns: boolean
"""
return self._gns3_vm
def connected(self):
"""
Returns if the client is connected.
@@ -145,19 +268,38 @@ class HTTPClient(QtCore.QObject):
"""
Closes the connection with the server.
"""
log.info("Connection to %s closed", self.url())
self._connected = False
self.connection_closed_signal.emit()
def isLocalServerRunning(self):
"""
Check if a server is already running on this host.
Synchronous check if a server is already running on this host.
:returns: boolean
"""
try:
url = "{scheme}://{host}:{port}/v1/version".format(scheme=self.scheme, host=self.host, port=self.port)
if self._user is not None:
status, json_data = self.getSynchronous("version", timeout=2)
if json_data is None or status != 200:
return False
else:
version = json_data.get("version", None)
if version is None:
log.debug("Server is not a GNS3 server")
return False
return True
def getSynchronous(self, endpoint, timeout=2):
"""
Synchronous check if a server is running
:returns: Tuple (Status code, json of anwser). Status 0 is a non HTTP error
"""
try:
url = "{protocol}://{host}:{port}/v1/{endpoint}".format(protocol=self._scheme, host=self._http_host, port=self._http_port, endpoint=endpoint)
log.debug("Synchronous get %s with user %s", url, self._user)
if self._user is not None and len(self._user) > 0:
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm="GNS3 server",
uri=url,
@@ -166,69 +308,75 @@ class HTTPClient(QtCore.QObject):
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
response = urllib.request.urlopen(url, timeout=2)
response = urllib.request.urlopen(url, timeout=timeout)
content_type = response.getheader("CONTENT-TYPE")
if response.status == 200 and content_type == "application/json":
content = response.read()
json_data = json.loads(content.decode("utf-8"))
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)")
return False
return True
except (OSError, urllib.error.HTTPError, http.client.BadStatusLine, ValueError) as e:
log.debug("A non GNS3 server is already running on {}:{}: {}".format(self.host, self.port, e))
return False
if response.status == 200:
if content_type == "application/json":
content = response.read()
json_data = json.loads(content.decode("utf-8"))
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
def get(self, path, callback, context={}):
def get(self, path, callback, **kwargs):
"""
HTTP GET on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("GET", path, callback, context=context)
self.createHTTPQuery("GET", path, callback, **kwargs)
def put(self, path, callback, body={}, context={}):
def put(self, path, callback, **kwargs):
"""
HTTP PUT on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param body: params to send (dictionary)
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("PUT", path, callback, context=context, body=body)
self.createHTTPQuery("PUT", path, callback, **kwargs)
def post(self, path, callback, body={}, context={}):
def post(self, path, callback, **kwargs):
"""
HTTP POST on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param body: params to send (dictionary)
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("POST", path, callback, context=context, body=body)
self.createHTTPQuery("POST", path, callback, **kwargs)
def delete(self, path, callback, context={}):
def delete(self, path, callback, **kwargs):
"""
HTTP DELETE on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("DELETE", path, callback, context=context)
self.createHTTPQuery("DELETE", path, callback, **kwargs)
def _request(self, url):
"""
@@ -241,42 +389,77 @@ class HTTPClient(QtCore.QObject):
return QtNetwork.QNetworkRequest(url)
def createHTTPQuery(self, method, path, callback, body={}, context={}):
def _connect(self, query):
"""
Initialize the connection
:param query: The query to execute when all network stack is ready
"""
self.executeHTTPQuery("GET", "/version", query, {})
def createHTTPQuery(self, method, path, callback, body={}, context={}, downloadProgressCallback=None, showProgress=True, ignoreErrors=False, progressText=None):
"""
Call the remote server, if not connected, check connection before
:param method: HTTP method
:param path: Remote path
:param body: params to send (dictionary)
:param body: params to send (dictionary or pathlib.Path)
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param downloadProgressCallback: Callback called when received something, it can be an incomplete response
:param showProgress: Display progress to the user
:params progressText: Text display to user in the progress dialog. None for auto generated
:param ignoreErrors: Ignore connection error (usefull to not closing a connection when notification feed is broken)
:returns: QNetworkReply
"""
if self._connected:
self.executeHTTPQuery(method, path, callback, body, context=context)
return self.executeHTTPQuery(method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
else:
log.info("Connection to {}:{}".format(self.host, self.port))
self.executeHTTPQuery("GET", "/version", partial(self._callbackConnect, method, path, callback, body, context), {})
log.info("Connection to {}".format(self.url()))
query = qpartial(self._callbackConnect, method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
self._connect(query)
def _callbackConnect(self, method, path, callback, body, original_context, params, error=False, **kwargs):
def _connectionError(self, callback, msg=""):
"""
Return an error to user if connection failed
:param callback: User callback
:param msg: An optional additional message for the callback
"""
if self.isLocal():
server = "local server {}".format(self.url())
else:
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)
def _callbackConnect(self, method, path, callback, body, original_context, params, error=False, server=None, **kwargs):
"""
Callback after /version response. Continue execution of query
:param method: HTTP method
:param path: Remote path
:param body: params to send (dictionary)
:param body: params to send (dictionary or pathlib.Path)
:param original_context: Original context
:param callback: callback method to call when the server replies
"""
if error is not False:
msg = "Can't connect to server {}://{}:{}".format(self.scheme, self.host, self.port)
if callback is not None:
callback({"message": msg}, error=True, server=self)
self._connectionError(callback)
return
if "version" not in params or "local" not in params:
msg = "The remote server {}://{}:{} is not a GNS 3 server".format(self.scheme, self.host, self.port)
msg = "The remote server {} is not a GNS3 server".format(self.url())
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
@@ -285,29 +468,66 @@ class HTTPClient(QtCore.QObject):
if params["version"] != __version__:
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
log.error(msg)
# Official release
# Stable release
if __version_info__[3] == 0:
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
else:
print(msg)
print("WARNING: Use a different client and server version can create bugs. Use it at your own risk.")
# We don't allow different major version to interact even with dev build
elif parse_version(__version__)[:2] != parse_version(params["version"])[:2]:
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
print(msg)
print("WARNING: Use a different client and server version can create bugs. Use it at your own risk.")
if params["local"] != self.isLocal():
if self.isLocal():
msg = "Running server is not a GNS3 local server (not started with --local)"
else:
msg = "Remote running server is started with --local. It's forbidden for security reasons"
msg = "Remote running server is started with --local. It is forbidden for security reasons"
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
self.executeHTTPQuery(method, path, callback, body, context=original_context)
self._connected = True
self.connection_connected_signal.emit()
kwargs["context"] = original_context
self.executeHTTPQuery(method, path, callback, body, **kwargs)
self._version = params["version"]
def _addBodyToRequest(self, body, request):
"""
Add the require headers for sending the body.
It detect the type of body for sending the corresponding headers
and methods.
:param body: The body
:returns: The body compatible with Qt
"""
if body is None:
return None
if isinstance(body, dict):
body = json.dumps(body)
request.setRawHeader(b"Content-Type", b"application/json")
request.setRawHeader(b"Content-Length", str(len(body)).encode())
data = QtCore.QByteArray(body.encode())
body = QtCore.QBuffer(self)
body.setData(data)
body.open(QtCore.QIODevice.ReadOnly)
return body
elif isinstance(body, pathlib.Path):
body = QtCore.QFile(str(body), self)
body.open(QtCore.QFile.ReadOnly)
request.setRawHeader(b"Content-Type", b"application/octet-stream")
# QT is smart and will compute the Content-Lenght for us
return body
else:
return None
def addAuth(self, request):
"""
If require add basic auth header
@@ -316,10 +536,10 @@ class HTTPClient(QtCore.QObject):
auth_string = "{}:{}".format(self._user, self._password)
auth_string = base64.b64encode(auth_string.encode("utf-8"))
auth_string = "Basic {}".format(auth_string.decode())
request.setRawHeader("Authorization", auth_string)
request.setRawHeader(b"Authorization", auth_string.encode())
return request
def executeHTTPQuery(self, method, path, callback, body, context={}):
def executeHTTPQuery(self, method, path, callback, body, context={}, downloadProgressCallback=None, showProgress=True, ignoreErrors=False, progressText=None):
"""
Call the remote server
@@ -328,43 +548,89 @@ class HTTPClient(QtCore.QObject):
:param body: params to send (dictionary)
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param downloadProgressCallback: Callback called when received something, it can be an incomplete response
:param showProgress: Display progress to the user
:param progressText: Text display to user in progress dialog. None for auto generated
:param ignoreErrors: Ignore connection error (usefull to not closing a connection when notification feed is broken)
:returns: QNetworkReply
"""
import copy
context = copy.copy(context)
context["query_id"] = str(uuid.uuid4())
self.notify_progress_start_query(context["query_id"])
log.debug("{method} {scheme}://{host}:{port}/v1{path} {body}".format(method=method, scheme=self.scheme, host=self.host, port=self.port, path=path, body=body))
url = QtCore.QUrl("{scheme}://{host}:{port}/v1{path}".format(scheme=self.scheme, host=self.host, port=self.port, path=path))
try:
ip = self._http_host.rsplit('%', 1)[0]
ipaddress.IPv6Address(ip) # remove any scope ID
# this is an IPv6 address, we must surround it with brackets to be used with QUrl.
host = "[{}]".format(ip)
except ipaddress.AddressValueError:
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))
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)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
request.setRawHeader("User-Agent", "GNS3 QT Client v{version}".format(version=__version__))
request.setRawHeader(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
if method == "GET":
response = self._network_manager.get(request)
# By default QT doesn't support GET with body even if it's in the RFC that's why we need to use sendCustomRequest
body = self._addBodyToRequest(body, request)
if method == "PUT":
body = json.dumps(body)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
response = self._network_manager.put(request, body)
response = self._network_manager.sendCustomRequest(request, method.encode(), body)
if method == "POST":
body = json.dumps(body)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
response = self._network_manager.post(request, body)
context = copy.copy(context)
context["query_id"] = str(uuid.uuid4())
if method == "DELETE":
response = self._network_manager.deleteResource(request)
response.finished.connect(qpartial(self._processResponse, response, callback, context, body, ignoreErrors))
if downloadProgressCallback is not None:
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, bytesReceived, bytesTotal):
"""
Process a packet receive on the notification feed.
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:
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)
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
request_canceled = partial(self._requestCanceled, response, context)
request_canceled = qpartial(self._requestCanceled, response, context)
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
response.finished.connect(partial(self._processResponse, response, callback, context))
def _requestCanceled(self, response, context):
@@ -373,28 +639,38 @@ class HTTPClient(QtCore.QObject):
if "query_id" in context:
self.notify_progress_end_query(context["query_id"])
def _processResponse(self, response, callback, context):
def _processResponse(self, response, callback, context, request_body, ignore_errors):
if request_body is not None:
request_body.close()
status = None
body = None
if "query_id" in context:
self.notify_progress_end_query(context["query_id"])
if response.error() != QtNetwork.QNetworkReply.NoError:
error_code = response.error()
error_message = response.errorString()
log.info("Response error: {}".format(error_message))
if not ignore_errors:
log.info("Response error: %s (error: %d)", error_message, error_code)
if error_code < 200:
self._connected = False
if not ignore_errors:
self.close()
if callback is not None:
callback({"message": error_message}, error=True, server=self, context=context)
return
else:
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
if status == 401:
print(error_message)
try:
body = bytes(response.readAll()).decode("utf-8")
# Some time anti-virus intercept our query and reply with garbage content
body = bytes(response.readAll()).decode("utf-8").strip("\0")
# Some time antivirus intercept our query and reply with garbage content
except UnicodeError:
body = None
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
@@ -403,14 +679,22 @@ 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))
body = bytes(response.readAll()).decode("utf-8")
try:
body = bytes(response.readAll()).decode("utf-8").strip("\0")
# Some time anti-virus intercept our query and reply with garbage content
except UnicodeDecodeError:
body = None
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
log.debug(body)
if body and content_type == "application/json":
if body and len(body.strip(" \n\t")) > 0 and content_type == "application/json":
params = json.loads(body)
else:
params = {}
@@ -421,7 +705,54 @@ class HTTPClient(QtCore.QObject):
callback(params, server=self, context=context)
# response.deleteLater()
if status == 400:
raise HttpBadRequest(body)
try:
params = json.loads(body)
e = HttpBadRequest(body)
e.fingerprint = params["path"]
# If something goes wrong for a any reason just raise the bad request
except Exception:
e = HttpBadRequest(body)
raise e
def RAMLimit(self):
"""
Returns the RAM limit for this server (used for RAM usage load balancing).
:returns: RAM limit (integer)
"""
return self._ram_limit
def allocatedRAM(self):
"""
Amount of allocated RAM on this server (used for RAM usage load balancing).
:returns: allocated RAM (integer)
"""
return self._allocated_ram
def increaseAllocatedRAM(self, ram):
"""
Increase the amount of allocated RAM on this server (used for RAM usage load balancing).
:param ram: amount of RAM (integer)
"""
log.info("RAM usage on {} has increased by {} MB (total load is now {} MB)".format(self.url(), ram, self._allocated_ram + ram))
self._allocated_ram += ram
def decreaseAllocatedRAM(self, ram):
"""
Decrease the amount of allocated RAM on this server (used for RAM usage load balancing).
:param ram: amount of RAM (integer)
"""
log.info("RAM usage on {} has decreased by {} MB (total load is now {} MB)".format(self.url(), ram, self._allocated_ram - ram))
self._allocated_ram -= ram
if self._allocated_ram < 0:
self._allocated_ram = 0
def dump(self):
"""
@@ -429,11 +760,27 @@ class HTTPClient(QtCore.QObject):
:returns: dictionary
"""
return {"id": self._id,
"host": self.host,
"port": self.port,
"local": self._local,
"cloud": self._cloud}
server = self.settings()
server["id"] = self._id
server["local"] = self._local
server["vm"] = self._gns3_vm
#server["cloud"] = self._cloud
if "user" in server and self._local:
del server["user"]
if "password" in server:
del server["password"]
if server["protocol"] == "https":
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()

188
gns3/image_manager.py Normal file
View File

@@ -0,0 +1,188 @@
# -*- 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 pathlib
import glob
from gns3.servers import Servers
from gns3.qt import QtWidgets
from gns3.utils.file_copy_worker import FileCopyWorker
from gns3.utils.progress_dialog import ProgressDialog
class ImageManager:
def __init__(self):
# Remember if we already ask the user about this image for this server
self._asked_for_this_image = {}
def askCopyUploadImage(self, parent, path, server, vm_type):
"""
Ask user for copying the image to the default directory or upload
it to remote server.
:param parent: Parent window
:param path: File path on computer
:param server: The server where the images should be located
:param vm_type: Remote upload endpoint
:returns path: Final path
"""
if server and not server.isLocal():
return self._uploadImageToRemoteServer(path, server, vm_type)
else:
destination_directory = self.getDirectoryForType(vm_type)
if os.path.normpath(os.path.dirname(path)) != destination_directory:
# the IOS image is not in the default images directory
reply = QtWidgets.QMessageBox.question(parent,
'Image',
'Would you like to copy {} to the default images directory'.format(os.path.basename(path)),
QtWidgets.QMessageBox.Yes,
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()
progress_dialog.exec_()
errors = progress_dialog.errors()
if errors:
QtWidgets.QMessageBox.critical(parent, 'Image', '{}'.format(''.join(errors)))
return path
else:
path = destination_path
return path
def _uploadImageToRemoteServer(self, path, server, vm_type):
"""
Upload image to remote server
:param path: File path on computer
:param server: The server where the images should be located
:param vm_type: Image vm_type
:returns path: Final path
"""
if vm_type == 'QEMU':
upload_endpoint = '/qemu/vms'
elif vm_type == 'IOU':
upload_endpoint = '/iou/vms'
elif vm_type == 'DYNAMIPS':
upload_endpoint = '/dynamips/vms'
else:
raise Exception('Invalid image vm_type')
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):
"""
Add a missing image to the queue of images require to be upload on remote server
:param filename: Filename of the image
:param server: Server where image should be uploaded
:param vm_type: Type of the image
"""
if self._asked_for_this_image.setdefault(server.id(), {}).setdefault(filename, False):
return
self._asked_for_this_image[server.id()][filename] = True
if server.isLocal():
return
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]
def _askForUploadMissingImage(self, filename, server):
from gns3.main_window import MainWindow
parent = MainWindow.instance()
reply = QtWidgets.QMessageBox.warning(parent,
'Image',
'{} is missing on server {} but exist on your computer. Do you want to upload it?'.format(filename, server.url()),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
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.
:returns: path to the default images directory
"""
return Servers.instance().localServerSettings()['images_path']
def getDirectoryForType(self, vm_type):
"""
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')
else:
return os.path.join(self.getDirectory(), vm_type)
@staticmethod
def instance():
"""
Singleton to return only on instance of ImageManager.
:returns: instance of ImageManager
"""
if not hasattr(ImageManager, '_instance') or ImageManager._instance is None:
ImageManager._instance = ImageManager()
return ImageManager._instance

189
gns3/iouvm_converter.py Normal file
View File

@@ -0,0 +1,189 @@
#!/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
import shutil
import json
import os
from datetime import datetime
try:
from gns3.qt import QtGui, QtWidgets
except ImportError:
raise SystemExit("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
import logging
log = logging.getLogger(__name__)
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)
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
# set the window icon
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico")) # this info is necessary for QSettings
config = self._loadConfig()
self.uiPushButtonBrowse.clicked.connect(self._browseTopologiesSlot)
self.uiLineEditTopologiesPath.setText(config['Servers']['local_server']['projects_path'])
def _browseTopologiesSlot(self):
path = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select a directory')
self.uiLineEditTopologiesPath.setText(path)
def validateCurrentPage(self):
"""
Validates the settings.
"""
if self.currentPage() == self.uiWizardPageIOURCCheck:
return self._checkIOURC()
elif self.currentPage() == self.uiWizardUpdateConfiguration:
return self._updateConfig()
elif self.currentPage() == self.uiWizardPagePatchTopologies:
return self._patchTopologies()
return True
def _checkIOURC(self):
"""
Validate if the IOURC contain an entry for the IOUVM
"""
config = self._loadConfig()
iourc_path = config.get("IOU", {}).get("iourc_path", "")
if len(iourc_path) == 0:
QtWidgets.QMessageBox.critical(self, "Error", "The IOURC is not configured")
return False
try:
with open(iourc_path) as f:
if 'gns3vm' not in f.read():
QtWidgets.QMessageBox.critical(self, "Error", "The gns3vm doesn't exist in your iourc file".format(iourc_path))
except OSError:
QtWidgets.QMessageBox.critical(self, "Error", "IOURC file {} doesn't exist or not accessible".format(iourc_path))
return True
def _updateConfig(self):
"""
Update the config file to use the GNS3 VM instead of IOU VM
"""
config = self._loadConfig()
if "devices" in config["IOU"]:
for device in config["IOU"]["devices"]:
device["path"] = os.path.basename(device["path"])
device["server"] = "vm"
config["Servers"]["remote_servers"] = []
self._writeConfig(config)
return True
def _patchTopologies(self):
"""
Patch topologies to use the GNS3 VM
"""
path = self.uiLineEditTopologiesPath.text()
try:
for (dirpath, dirnames, filenames) in os.walk(path):
for filename in filenames:
if filename.endswith(".gns3"):
self._patchTopology(os.path.join(dirpath, filename))
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Error", "Can't open {}: {}".format(path, str(e)))
return False
return True
def _patchTopology(self, path):
"""
Path a specific topology
"""
try:
shutil.copy(path, "{}.{}.backup".format(path, datetime.now().isoformat()))
with open(path) as f:
topo = json.load(f)
if "topology" in topo and "servers" in topo["topology"]:
for server in topo["topology"]["servers"]:
if server["local"] is False:
server["vm"] = True
with open(path, 'w+') as f:
topo = json.dump(topo, f)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Error", "Can't open {}: {}".format(path, str(e)))
def _loadConfig(self):
with open(self._configurationFile()) as f:
return json.load(f)
def _writeConfig(self, config):
shutil.copy(self._configurationFile(), "{}.{}.backup".format(self._configurationFile(), datetime.now().isoformat()))
with open(self._configurationFile(), 'w+') as f:
json.dump(config, f, indent=4)
def _configurationFile(self):
if sys.platform.startswith("win"):
filename = "gns3_gui.ini"
else:
filename = "gns3_gui.conf"
directory = LocalConfig.configDirectory()
return os.path.join(directory, filename)
def main():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
# We force a full garbage collect before exit
# for unknow reason otherwise Qt Segfault on OSX in some
# conditions
import gc
gc.collect()
# Manage Ctrl + C or kill command
def sigint_handler(*args):
log.info("Signal received exiting the application")
app.closeAllWindows()
# signal.signal(signal.SIGINT, sigint_handler)
# signal.signal(signal.SIGTERM, sigint_handler)
mainwindow = IOUVMConverterWizard()
mainwindow.show()
exit_code = mainwindow.exec_()
# We force a full garbage collect before exit
# for unknow reason otherwise Qt Segfault on OSX in some
# conditions
import gc
gc.collect()
sys.exit(exit_code)
if __name__ == '__main__':
main()

View File

@@ -19,11 +19,11 @@
Graphical representation of an ellipse on the QGraphicsScene.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
from .shape_item import ShapeItem
class EllipseItem(ShapeItem, QtGui.QGraphicsEllipseItem):
class EllipseItem(QtWidgets.QGraphicsEllipseItem, ShapeItem):
"""
Class to draw an ellipse on the scene.
@@ -31,8 +31,8 @@ class EllipseItem(ShapeItem, QtGui.QGraphicsEllipseItem):
def __init__(self, pos=None, width=200, height=200):
QtGui.QGraphicsEllipseItem.__init__(self, 0, 0, width, height)
ShapeItem.__init__(self)
super().__init__()
self.setRect(0, 0, width, height)
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.DashLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self.setPen(pen)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 255)) # default color is white and not transparent
@@ -58,7 +58,7 @@ class EllipseItem(ShapeItem, QtGui.QGraphicsEllipseItem):
:param widget: QWidget instance
"""
QtGui.QGraphicsEllipseItem.paint(self, painter, option, widget)
super().paint(painter, option, widget)
self.drawLayerInfo(painter)
def duplicate(self):

View File

@@ -19,7 +19,7 @@
Graphical representation of an Ethernet link for QGraphicsScene.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
from .link_item import LinkItem
from .note_item import NoteItem
from ..ports.port import Port
@@ -41,7 +41,7 @@ class EthernetLinkItem(LinkItem):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
LinkItem.__init__(self, source_item, source_port, destination_item, destination_port, link, adding_flag, multilink)
super().__init__(source_item, source_port, destination_item, destination_port, link, adding_flag, multilink)
self._source_collision_offset = 0.0
self._destination_collision_offset = 0.0
@@ -75,7 +75,7 @@ class EthernetLinkItem(LinkItem):
:returns: QPainterPath instance
"""
path = QtGui.QGraphicsPathItem.shape(self)
path = QtWidgets.QGraphicsPathItem.shape(self)
offset = self._point_size / 2
if not self._adding_flag:
if self.length:
@@ -106,7 +106,7 @@ class EthernetLinkItem(LinkItem):
:param widget: QWidget instance.
"""
QtGui.QGraphicsPathItem.paint(self, painter, option, widget)
QtWidgets.QGraphicsPathItem.paint(self, painter, option, widget)
if not self._adding_flag and self._settings["draw_link_status_points"]:
# points disappears if nodes are too close to each others.

View File

@@ -19,10 +19,10 @@
Graphical representation of an image on the QGraphicsScene.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore
class ImageItem(QtGui.QGraphicsPixmapItem):
class ImageItem():
"""
Class to insert an image on the scene.
@@ -30,11 +30,9 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
show_layer = False
def __init__(self, pixmap, image_path, pos=None):
def __init__(self, image_path, pos=None):
QtGui.QGraphicsPixmapItem.__init__(self, pixmap)
self.setFlags(self.ItemIsMovable | self.ItemIsSelectable)
self.setTransformationMode(QtCore.Qt.SmoothTransformation)
self._image_path = image_path
if pos:
self.setPos(pos)
@@ -48,17 +46,6 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
from ..topology import Topology
Topology.instance().removeImage(self)
def duplicate(self):
"""
Duplicates this image item.
:return: ImageItem instance
"""
image_item = ImageItem(self.pixmap(), self._image_path, QtCore.QPointF(self.x() + 20, self.y() + 20))
image_item.setZValue(self.zValue())
return image_item
def paint(self, painter, option, widget=None):
"""
Paints the contents of an item in local coordinates.
@@ -68,7 +55,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
:param widget: QWidget instance
"""
QtGui.QGraphicsPixmapItem.paint(self, painter, option, widget)
super().paint(painter, option, widget)
if self.show_layer is False:
return
@@ -93,7 +80,7 @@ class ImageItem(QtGui.QGraphicsPixmapItem):
:param value: Z value
"""
QtGui.QGraphicsPixmapItem.setZValue(self, value)
super().setZValue(value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)

View File

@@ -23,10 +23,10 @@ Link items are graphical representation of a link on the QGraphicsScene
import math
import struct
import sys
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
class LinkItem(QtGui.QGraphicsPathItem):
class LinkItem(QtWidgets.QGraphicsPathItem):
"""
Base class for link items.
@@ -44,8 +44,8 @@ class LinkItem(QtGui.QGraphicsPathItem):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
QtGui.QGraphicsPathItem.__init__(self)
self.setAcceptsHoverEvents(True)
super().__init__()
self.setAcceptHoverEvents(True)
self.setZValue(-1)
self._link = None
@@ -172,6 +172,20 @@ class LinkItem(QtGui.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.
@@ -181,33 +195,33 @@ class LinkItem(QtGui.QGraphicsPathItem):
if not self._source_port.capturing() or not self._destination_port.capturing():
# start capture
start_capture_action = QtGui.QAction("Start capture", menu)
start_capture_action = QtWidgets.QAction("Start capture", menu)
start_capture_action.setIcon(QtGui.QIcon(':/icons/capture-start.svg'))
start_capture_action.triggered.connect(self._startCaptureActionSlot)
menu.addAction(start_capture_action)
if self._source_port.capturing() or self._destination_port.capturing():
# stop capture
stop_capture_action = QtGui.QAction("Stop capture", menu)
stop_capture_action = QtWidgets.QAction("Stop capture", menu)
stop_capture_action.setIcon(QtGui.QIcon(':/icons/capture-stop.svg'))
stop_capture_action.triggered.connect(self._stopCaptureActionSlot)
menu.addAction(stop_capture_action)
# start wireshark
start_wireshark_action = QtGui.QAction("Start Wireshark", menu)
start_wireshark_action = QtWidgets.QAction("Start Wireshark", menu)
start_wireshark_action.setIcon(QtGui.QIcon(":/icons/wireshark.png"))
start_wireshark_action.triggered.connect(self._startWiresharkActionSlot)
menu.addAction(start_wireshark_action)
if sys.platform.startswith("win") and struct.calcsize("P") * 8 == 64:
# Windows 64-bit only (Solarwinds RTV limitation).
analyze_action = QtGui.QAction("Analyze capture", menu)
analyze_action = QtWidgets.QAction("Analyze capture", menu)
analyze_action.setIcon(QtGui.QIcon(':/icons/rtv.png'))
analyze_action.triggered.connect(self._analyzeCaptureActionSlot)
menu.addAction(analyze_action)
# delete
delete_action = QtGui.QAction("Delete", menu)
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.setIcon(QtGui.QIcon(':/icons/delete.svg'))
delete_action.triggered.connect(self._deleteActionSlot)
menu.addAction(delete_action)
@@ -224,15 +238,15 @@ class LinkItem(QtGui.QGraphicsPathItem):
# send a escape key to the main window to cancel the link addition
from ..main_window import MainWindow
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtGui.QApplication.sendEvent(MainWindow.instance(), key)
QtWidgets.QApplication.sendEvent(MainWindow.instance(), key)
return
# create the contextual menu
self.setAcceptsHoverEvents(False)
menu = QtGui.QMenu()
self.setAcceptHoverEvents(False)
menu = QtWidgets.QMenu()
self.populateLinkContextualMenu(menu)
menu.exec_(QtGui.QCursor.pos())
self.setAcceptsHoverEvents(True)
self.setAcceptHoverEvents(True)
self._hovered = False
self.adjust()
@@ -243,7 +257,7 @@ class LinkItem(QtGui.QGraphicsPathItem):
:param event: QKeyEvent
"""
#On pressing backspace or delete key, the selected link gets deleted
# On pressing backspace or delete key, the selected link gets deleted
if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace:
self._deleteActionSlot()
return
@@ -274,10 +288,10 @@ class LinkItem(QtGui.QGraphicsPathItem):
ports[port] = [self._destination_item.node(), self._destination_port, dlt]
if not ports:
QtGui.QMessageBox.critical(self._main_window, "Packet capture", "Packet capture is not supported on this link")
QtWidgets.QMessageBox.critical(self._main_window, "Packet capture", "Packet capture is not supported on this link")
return
selection, ok = QtGui.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", list(ports.keys()), 0, False)
selection, ok = QtWidgets.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", list(ports.keys()), 0, False)
if ok:
if selection in ports:
node, port, dlt = ports[selection]
@@ -295,7 +309,7 @@ class LinkItem(QtGui.QGraphicsPathItem):
ports[source_port] = [self._source_item.node(), self._source_port]
destination_port = "{} port {}".format(self._destination_item.node().name(), self._destination_port.name())
ports[destination_port] = [self._destination_item.node(), self._destination_port]
selection, ok = QtGui.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", list(ports.keys()), 0, False)
selection, ok = QtWidgets.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", list(ports.keys()), 0, False)
if ok:
if selection in ports:
node, port = ports[selection]
@@ -315,18 +329,18 @@ class LinkItem(QtGui.QGraphicsPathItem):
if self._source_port.capturing() and self._destination_port.capturing():
ports = ["{} port {}".format(self._source_item.node().name(), self._source_port.name()),
"{} port {}".format(self._destination_item.node().name(), self._destination_port.name())]
selection, ok = QtGui.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", ports, 0, False)
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:
QtGui.QMessageBox.critical(self._main_window, "Packet capture", "Cannot start Wireshark: {}".format(e))
QtWidgets.QMessageBox.critical(self._main_window, "Packet capture", "Cannot start Wireshark: {}".format(e))
def _analyzeCaptureActionSlot(self):
"""
@@ -338,7 +352,7 @@ class LinkItem(QtGui.QGraphicsPathItem):
if self._source_port.capturing() and self._destination_port.capturing():
ports = ["{} port {}".format(self._source_item.node().name(), self._source_port.name()),
"{} port {}".format(self._destination_item.node().name(), self._destination_port.name())]
selection, ok = QtGui.QInputDialog.getItem(self._main_window, "Capture analyzer", "Please select a port:", ports, 0, False)
selection, ok = QtWidgets.QInputDialog.getItem(self._main_window, "Capture analyzer", "Please select a port:", ports, 0, False)
if ok:
if selection.endswith(self._source_port.name()):
self._source_port.startPacketCaptureAnalyzer()
@@ -349,7 +363,7 @@ class LinkItem(QtGui.QGraphicsPathItem):
elif self._destination_port.capturing():
self._destination_port.startPacketCaptureAnalyzer()
except OSError as e:
QtGui.QMessageBox.critical(self._main_window, "Capture analyzer", "Cannot start the packet capture analyzer program: {}".format(e))
QtWidgets.QMessageBox.critical(self._main_window, "Capture analyzer", "Cannot start the packet capture analyzer program: {}".format(e))
def setHovered(self, value):
"""

View File

@@ -19,25 +19,24 @@
Graphical representation of a node on the QGraphicsScene.
"""
from ..qt import QtCore, QtGui, QtSvg
from ..qt import QtCore, QtGui, QtWidgets
from .note_item import NoteItem
import logging
log = logging.getLogger(__name__)
class NodeItem(QtSvg.QGraphicsSvgItem):
class NodeItem():
"""
Node for the scene.
:param node: Node instance
:param default_symbol: Default symbol for the node representation on the scene
:param hover_symbol: Hover symbol when the node is hovered on the scene
"""
show_layer = False
def __init__(self, node, default_symbol=None, hover_symbol=None):
QtSvg.QGraphicsSvgItem.__init__(self)
def __init__(self, node):
# attached node
self._node = node
@@ -48,28 +47,23 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# link items connected to this node item.
self._links = []
# set graphical settings for this node
self.setFlag(QtSvg.QGraphicsSvgItem.ItemIsMovable)
self.setFlag(QtSvg.QGraphicsSvgItem.ItemIsSelectable)
self.setFlag(QtSvg.QGraphicsSvgItem.ItemIsFocusable)
self.setFlag(QtSvg.QGraphicsSvgItem.ItemSendsGeometryChanges)
self.setAcceptsHoverEvents(True)
self.setZValue(1)
effect = QtWidgets.QGraphicsColorizeEffect()
effect.setColor(QtGui.QColor("black"))
effect.setStrength(0.8)
#effect = QtWidgets.QGraphicsDropShadowEffect()
# effect.setColor(QtGui.QColor("darkGray"))
# effect.setBlurRadius(0)
#effect.setOffset(3, 3)
self.setGraphicsEffect(effect)
self.graphicsEffect().setEnabled(False)
# create renderers using symbols paths/resources
if default_symbol:
self._default_renderer = QtSvg.QSvgRenderer(default_symbol)
if default_symbol != node.defaultSymbol():
self._default_renderer.setObjectName(default_symbol)
else:
self._default_renderer = QtSvg.QSvgRenderer(node.defaultSymbol())
if hover_symbol:
self._hover_renderer = QtSvg.QSvgRenderer(hover_symbol)
if hover_symbol != node.hoverSymbol():
self._hover_renderer.setObjectName(hover_symbol)
else:
self._hover_renderer = QtSvg.QSvgRenderer(node.hoverSymbol())
self.setSharedRenderer(self._default_renderer)
# set graphical settings for this node
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
self.setAcceptHoverEvents(True)
self.setZValue(1)
# connect signals to know about some events
# e.g. when the node has been started, stopped or suspended etc.
@@ -98,43 +92,6 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._main_window = MainWindow.instance()
self._settings = self._main_window.uiGraphicsView.settings()
def defaultRenderer(self):
"""
Returns the default QSvgRenderer.
:return: QSvgRenderer instance
"""
return self._default_renderer
def setDefaultRenderer(self, default_renderer):
"""
Sets new default QSvgRenderer.
:param default_renderer: QSvgRenderer instance
"""
self._default_renderer = default_renderer
self.setSharedRenderer(self._default_renderer)
def hoverRenderer(self):
"""
Returns the hover QSvgRenderer.
:return: QSvgRenderer instance
"""
return self._hover_renderer
def setHoverRenderer(self, hover_renderer):
"""
Sets new hover QSvgRenderer.
:param hover_renderer: QSvgRenderer instance
"""
self._hover_renderer = hover_renderer
def setUnsavedState(self):
"""
Indicates the project is in a unsaved state.
@@ -229,6 +186,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
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())
@@ -255,6 +214,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
when the node has been deleted.
"""
if self is None:
return
self._node.removeAllocatedName()
if self in self.scene().items():
self.scene().removeItem(self)
@@ -269,7 +230,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
: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):
"""
@@ -280,7 +242,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
:param message: error message
"""
self._last_error = "{message}".format(message=message)
if self:
self._last_error = "{message}".format(message=message)
def setCustomToolTip(self):
"""
@@ -348,10 +311,10 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
self._selected_port = None
menu = QtGui.QMenu()
menu = QtWidgets.QMenu()
ports = self._node.ports()
if not ports:
QtGui.QMessageBox.critical(self.scene().parent(), "Link", "No port available, please configure this device")
QtWidgets.QMessageBox.critical(self.scene().parent(), "Link", "No port available, please configure this device")
return None
# sort the ports
@@ -361,7 +324,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# make the port number unique (special case with WICs).
port_number = port.portNumber()
if port_number >= 16:
port_number *= 4
port_number *= 8
ports_dict[(port.adapterNumber() * 16) + port_number] = port
elif port.portNumber()is not None:
ports_dict[port.portNumber()] = port
@@ -376,6 +339,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# show a contextual menu for the user to choose a port
for port in ports:
port_object = ports_dict[port]
log.debug("Node '{}' Port {} Type {}".format(self.node(), port_object.name(), type(port_object.name())))
if port in unavailable_ports:
# this port cannot be chosen by the user (grayed out)
action = menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
@@ -414,19 +378,19 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
# dynamically change the renderer when this node item is selected/unselected.
if change == QtSvg.QGraphicsSvgItem.ItemSelectedChange:
if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
if value:
self.setSharedRenderer(self._hover_renderer)
self.graphicsEffect().setEnabled(True)
else:
self.setSharedRenderer(self._default_renderer)
self.graphicsEffect().setEnabled(False)
# adjust link item positions when this node is moving or has changed.
if change == QtSvg.QGraphicsSvgItem.ItemPositionChange or change == QtSvg.QGraphicsSvgItem.ItemPositionHasChanged:
if change == QtWidgets.QGraphicsItem.ItemPositionChange or change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
self.setUnsavedState()
for link in self._links:
link.adjust()
return QtGui.QGraphicsItem.itemChange(self, change, value)
return QtWidgets.QGraphicsItem.itemChange(self, change, value)
def paint(self, painter, option, widget=None):
"""
@@ -439,8 +403,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# don't show the selection rectangle
if not self._settings["draw_rectangle_selected_item"]:
option.state = QtGui.QStyle.State_None
QtSvg.QGraphicsSvgItem.paint(self, painter, option, widget)
option.state = QtWidgets.QStyle.State_None
super().paint(painter, option, widget)
if not self._initialized or self.show_layer:
brect = self.boundingRect()
@@ -464,7 +428,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
:param value: Z value
"""
QtSvg.QGraphicsSvgItem.setZValue(self, value)
super().setZValue(value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)
@@ -488,13 +452,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
self.setCustomToolTip()
# dynamically change the renderer when this node item is hovered.
if not self.isSelected():
self.setSharedRenderer(self._hover_renderer)
# effect = QtGui.QGraphicsColorizeEffect()
# effect.setColor(QtGui.QColor("black"))
# effect.setStrength(0.8)
# self.setGraphicsEffect(effect)
self.graphicsEffect().setEnabled(True)
def hoverLeaveEvent(self, event):
"""
@@ -503,7 +462,5 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
:param event: QGraphicsSceneHoverEvent instance
"""
# dynamically change the renderer back to the default when this node item is not hovered anymore.
if not self.isSelected():
self.setSharedRenderer(self._default_renderer)
# self.graphicsEffect().setEnabled(False)
self.graphicsEffect().setEnabled(False)

View File

@@ -19,11 +19,10 @@
Graphical representation of a note on the QGraphicsScene.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtWidgets, QtGui
class NoteItem(QtGui.QGraphicsTextItem):
class NoteItem(QtWidgets.QGraphicsTextItem):
"""
Text note for the QGraphicsView.
@@ -34,9 +33,10 @@ class NoteItem(QtGui.QGraphicsTextItem):
def __init__(self, parent=None):
QtGui.QGraphicsTextItem.__init__(self, parent)
super().__init__(parent)
from ..main_window import MainWindow
main_window = MainWindow.instance()
view_settings = main_window.uiGraphicsView.settings()
qt_font = QtGui.QFont()
@@ -59,6 +59,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
self.scene().removeItem(self)
from ..topology import Topology
Topology.instance().removeNote(self)
def editable(self):
@@ -101,7 +102,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
else:
QtGui.QGraphicsTextItem.keyPressEvent(self, event)
super().keyPressEvent(event)
def editText(self):
"""
@@ -132,7 +133,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
:param event: QFocusEvent instance
"""
self.setFlag(QtGui.QGraphicsItem.ItemIsFocusable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
cursor = self.textCursor()
if cursor.hasSelection():
cursor.clearSelection()
@@ -142,7 +143,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
# delete the note if empty
self.delete()
return
return QtGui.QGraphicsTextItem.focusOutEvent(self, event)
return super().focusOutEvent(event)
def paint(self, painter, option, widget=None):
"""
@@ -153,7 +154,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
:param widget: QWidget instance
"""
QtGui.QGraphicsTextItem.paint(self, painter, option, widget)
super().paint(painter, option, widget)
if self.show_layer is False or self.parentItem():
return
@@ -178,7 +179,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
:param value: Z value
"""
QtGui.QGraphicsTextItem.setZValue(self, value)
super().setZValue(value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)
@@ -198,7 +199,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
"y": self.y()}
note_info["font"] = self.font().toString()
note_info["color"] = self.defaultTextColor().name()
note_info["color"] = self.defaultTextColor().name(QtGui.QColor.HexArgb)
if self.rotation() != 0:
note_info["rotation"] = self.rotation()
if self.zValue() != 2:

View File

@@ -0,0 +1,47 @@
# -*- 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 image on the QGraphicsScene.
"""
from ..qt import QtCore, QtWidgets
from .image_item import ImageItem
class PixmapImageItem(ImageItem, QtWidgets.QGraphicsPixmapItem):
"""
Class to insert an pixmap image on the scene.
"""
def __init__(self, pixmap, image_path, pos=None):
QtWidgets.QGraphicsPixmapItem.__init__(self, pixmap)
ImageItem.__init__(self, image_path, pos)
self.setTransformationMode(QtCore.Qt.SmoothTransformation)
def duplicate(self):
"""
Duplicates this image item.
:return: PixmapImageItem instance
"""
image_item = PixmapImageItem(self.pixmap(), self._image_path, QtCore.QPointF(self.x() + 20, self.y() + 20))
image_item.setZValue(self.zValue())
return image_item

View File

@@ -19,11 +19,11 @@
Graphical representation of a rectangle on the QGraphicsScene.
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
from .shape_item import ShapeItem
class RectangleItem(ShapeItem, QtGui.QGraphicsRectItem):
class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
"""
Class to draw a rectangle on the scene.
@@ -31,8 +31,8 @@ class RectangleItem(ShapeItem, QtGui.QGraphicsRectItem):
def __init__(self, pos=None, width=200, height=100):
QtGui.QGraphicsRectItem.__init__(self, 0, 0, width, height)
ShapeItem.__init__(self)
super().__init__()
self.setRect(0, 0, width, height)
pen = QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
self.setPen(pen)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255, 255)) # default color is white and not transparent
@@ -58,7 +58,7 @@ class RectangleItem(ShapeItem, QtGui.QGraphicsRectItem):
:param widget: QWidget instance
"""
QtGui.QGraphicsRectItem.paint(self, painter, option, widget)
super().paint(painter, option, widget)
self.drawLayerInfo(painter)
def duplicate(self):

View File

@@ -20,7 +20,7 @@ Graphical representation of a Serial link on the QGraphicsScene.
"""
import math
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
from .link_item import LinkItem
from .note_item import NoteItem
from ..ports.port import Port
@@ -42,7 +42,7 @@ class SerialLinkItem(LinkItem):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
LinkItem.__init__(self, source_item, source_port, destination_item, destination_port, link, adding_flag, multilink)
super().__init__(source_item, source_port, destination_item, destination_port, link, adding_flag, multilink)
def adjust(self):
"""
@@ -89,7 +89,7 @@ class SerialLinkItem(LinkItem):
:returns: QPainterPath instance
"""
path = QtGui.QGraphicsPathItem.shape(self)
path = QtWidgets.QGraphicsPathItem.shape(self)
offset = self._point_size / 2
point = self.source
path.addEllipse(point.x() - offset, point.y() - offset, self._point_size, self._point_size)
@@ -106,7 +106,7 @@ class SerialLinkItem(LinkItem):
:param widget: QWidget instance.
"""
QtGui.QGraphicsPathItem.paint(self, painter, option, widget)
QtWidgets.QGraphicsPathItem.paint(self, painter, option, widget)
if not self._adding_flag and self._settings["draw_link_status_points"]:

View File

@@ -19,7 +19,7 @@
Base class for shape items (Rectangle, ellipse etc.).
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
class ShapeItem:
@@ -30,10 +30,10 @@ class ShapeItem:
show_layer = False
def __init__(self):
def __init__(self, **kws):
self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsFocusable | QtGui.QGraphicsItem.ItemIsSelectable)
self.setAcceptsHoverEvents(True)
self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsFocusable | QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setAcceptHoverEvents(True)
self._border = 5
self._edge = None
@@ -58,7 +58,7 @@ class ShapeItem:
if self.rotation() < 360.0:
self.setRotation(self.rotation() + 1)
else:
QtGui.QGraphicsItem.keyPressEvent(self, event)
QtWidgets.QGraphicsItem.keyPressEvent(self, event)
def mousePressEvent(self, event):
"""
@@ -69,22 +69,22 @@ class ShapeItem:
self.update()
if event.pos().x() > (self.rect().right() - self._border):
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "right"
elif event.pos().x() < (self.rect().left() + self._border):
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "left"
elif event.pos().y() < (self.rect().top() + self._border):
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "top"
elif event.pos().y() > (self.rect().bottom() - self._border):
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, False)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
self._edge = "bottom"
QtGui.QGraphicsItem.mousePressEvent(self, event)
QtWidgets.QGraphicsItem.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
"""
@@ -94,9 +94,9 @@ class ShapeItem:
"""
self.update()
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self._edge = None
QtGui.QGraphicsItem.mouseReleaseEvent(self, event)
QtWidgets.QGraphicsItem.mouseReleaseEvent(self, event)
def mouseMoveEvent(self, event):
"""
@@ -145,7 +145,7 @@ class ShapeItem:
self.setPos(scenePos.x(), self.y())
self._edge = "left"
QtGui.QGraphicsItem.mouseMoveEvent(self, event)
QtWidgets.QGraphicsItem.mouseMoveEvent(self, event)
def hoverMoveEvent(self, event):
"""
@@ -208,7 +208,7 @@ class ShapeItem:
:param value: Z value
"""
QtGui.QGraphicsItem.setZValue(self, value)
QtWidgets.QGraphicsItem.setZValue(self, value)
if self.zValue() < 0:
self.setFlag(self.ItemIsSelectable, False)
self.setFlag(self.ItemIsMovable, False)

View File

@@ -0,0 +1,47 @@
# -*- 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 SVG image on the QGraphicsScene.
"""
from ..qt import QtCore, QtSvg
from .image_item import ImageItem
class SvgImageItem(ImageItem, QtSvg.QGraphicsSvgItem):
"""
Class to insert a SVG image on the scene.
"""
def __init__(self, renderer, image_path, pos=None):
QtSvg.QGraphicsSvgItem.__init__(self)
ImageItem.__init__(self, image_path, pos)
self.setSharedRenderer(renderer)
def duplicate(self):
"""
Duplicates this image item.
:return: SvgImageItem instance
"""
image_item = SvgImageItem(self.renderer(), self._image_path, QtCore.QPointF(self.x() + 20, self.y() + 20))
image_item.setZValue(self.zValue())
return image_item

View File

@@ -0,0 +1,51 @@
# -*- 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 SVG node on the QGraphicsScene.
"""
from ..qt import QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .node_item import NodeItem
import logging
log = logging.getLogger(__name__)
class SvgNodeItem(NodeItem, QtSvg.QGraphicsSvgItem):
"""
SVG node for the scene.
:param node: Node instance
:param symbol: symbol for the node representation on the scene
"""
def __init__(self, node, symbol=None):
QtSvg.QGraphicsSvgItem.__init__(self)
NodeItem.__init__(self, node)
# create renderer using symbols path/resource
if symbol:
renderer = QImageSvgRenderer(symbol)
if symbol != node.defaultSymbol():
renderer.setObjectName(symbol)
else:
renderer = QImageSvgRenderer(node.defaultSymbol())
self.setSharedRenderer(renderer)

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__)
@@ -48,7 +49,7 @@ class Link(QtCore.QObject):
def __init__(self, source_node, source_port, destination_node, destination_port):
super(Link, self).__init__()
super().__init__()
log.info("adding link from {} {} to {} {}".format(source_node.name(),
source_port.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,10 +207,9 @@ 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
laddr = self._source_node.server().host()
self._source_udp = (lport, laddr)
# disconnect the signal has we don't expect new source UDP info for this link.
self._source_node.allocate_udp_nio_signal.disconnect(self.UDPPortAllocatedSlot)
@@ -217,7 +220,7 @@ class Link(QtCore.QObject):
# check that the node is connected to this link as a destination
elif node_id == self._destination_node.id() and port_id == self._destination_port.id():
laddr = self._destination_node.server().host
laddr = self._destination_node.server().host()
self._destination_udp = (lport, laddr)
# disconnect the signal has we don't expect new source UDP info for this link.
self._destination_node.allocate_udp_nio_signal.disconnect(self.UDPPortAllocatedSlot)
@@ -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
@@ -257,6 +283,10 @@ class Link(QtCore.QObject):
:param port_id: port identifier
"""
# in very rare cases link is already deleted
if self is None:
return
# 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():
self._source_nio_active = True
@@ -338,21 +368,19 @@ class Link(QtCore.QObject):
"""
if not self._stub:
if self._source_node.id() != node_id:
try:
# the destination node has canceled its NIO allocation
self._destination_node.nio_signal.disconnect(self.newNIOSlot)
except TypeError:
# ignore TypeError: 'method' object is not connected
pass
try:
# the destination node has canceled its NIO allocation
self._destination_node.nio_signal.disconnect(self.newNIOSlot)
except TypeError:
# ignore TypeError: 'method' object is not connected
pass
elif self._destination_node.id() != node_id:
try:
# the source node has canceled its NIO allocation
self._source_node.nio_signal.disconnect(self.newNIOSlot)
except TypeError:
# ignore TypeError: 'method' object is not connected
pass
try:
# the source node has canceled its NIO allocation
self._source_node.nio_signal.disconnect(self.newNIOSlot)
except TypeError:
# ignore TypeError: 'method' object is not connected
pass
self._source_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
self._destination_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
@@ -380,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

@@ -18,56 +18,59 @@
import sys
import os
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 LocalConfig:
class LocalConfig(QtCore.QObject):
"""
Handles the local GUI settings.
"""
def __init__(self):
config_changed_signal = QtCore.Signal()
def __init__(self, config_file=None):
super().__init__()
self._settings = {}
self._last_config_changed = None
if sys.platform.startswith("win"):
filename = "gns3_gui.ini"
else:
filename = "gns3_gui.conf"
if sys.platform.startswith("darwin"):
appname = "gns3.net"
else:
appname = "GNS3"
self._migrateOldConfigPath()
appname = "GNS3"
if sys.platform.startswith("win"):
# On windows, the system wide configuration file location is %COMMON_APPDATA%/GNS3/gns3_gui.conf
common_appdata = os.path.expandvars("%COMMON_APPDATA%")
system_wide_config_file = os.path.join(common_appdata, appname, filename)
# On windows, the user specific configuration file location is %APPDATA%/GNS3/gns3_gui.conf
appdata = os.path.expandvars("%APPDATA%")
self._config_file = os.path.join(appdata, appname, filename)
else:
# On UNIX-like platforms, the system wide configuration file location is /etc/xdg/GNS3/gns3_gui.conf
system_wide_config_file = os.path.join("/etc/xdg", appname, filename)
# On UNIX-like platforms, the user specific configuration file location is /etc/xdg/GNS3/gns3_gui.conf
home = os.path.expanduser("~")
self._config_file = os.path.join(home, ".config", appname, filename)
if config_file:
self._config_file = config_file
else:
self._config_file = os.path.join(LocalConfig.configDirectory(), filename)
# First load system wide settings
if os.path.exists(system_wide_config_file):
self._settings = self._readConfig(system_wide_config_file)
if not self._settings:
log.warning("No system wide settings loaded from {}".format(system_wide_config_file))
self._readConfig(system_wide_config_file)
config_file_in_cwd = os.path.join(os.getcwd(), filename)
if os.path.exists(config_file_in_cwd):
@@ -85,19 +88,92 @@ class LocalConfig:
user_settings = self._readConfig(self._config_file)
# overwrite system wide settings with user specific ones
self._settings.update(user_settings)
self._migrateOldConfig()
self._writeConfig()
@staticmethod
def configDirectory():
"""
Get the configuration directory
"""
if sys.platform.startswith("win"):
appdata = os.path.expandvars("%APPDATA%")
path = os.path.join(appdata, "GNS3")
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3")
return os.path.normpath(path)
def _migrateOldConfigPath(self):
"""
Migrate pre 1.4 config path
"""
# In < 1.4 on Mac the config was in a gns3.net directory
# We have move to same location as Linux
if sys.platform.startswith("darwin"):
old_path = os.path.join(os.path.expanduser("~"), ".config", "gns3.net")
new_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3")
if os.path.exists(old_path) and not os.path.exists(new_path):
try:
shutil.copytree(old_path, new_path)
except OSError as e:
print("Can't copy the old config: %s", str(e))
def _migrateOldConfig(self):
"""
Migrate pre 1.4 config
"""
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.0alpha1"):
servers = self._settings.get("Servers", {})
if "LocalServer" in self._settings:
servers["local_server"] = copy.copy(self._settings["LocalServer"])
# We migrate the server binary for OSX due to the change from py2app to CX freeze
if servers["local_server"]["path"] == "/Applications/GNS3.app/Contents/Resources/server/Contents/MacOS/gns3server":
servers["local_server"]["path"] = "/Applications/GNS3.app/Contents/MacOS/gns3server"
if "RemoteServers" in self._settings:
servers["remote_servers"] = copy.copy(self._settings["RemoteServers"])
self._settings["Servers"] = servers
if "GUI" in self._settings:
main_window = self._settings.get("MainWindow", {})
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.
"""
log.info("Load config from %s", config_path)
try:
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
self._last_config_changed = os.stat(config_path).st_mtime
config = json.load(f)
self._settings.update(config)
except (ValueError, OSError) as e:
log.error("Could not read the config file {}: {}".format(self._config_file, e))
# Update already loaded section
for section in self._settings.keys():
if isinstance(self._settings[section], dict):
self.loadSectionSettings(section, self._settings[section])
return dict()
def _writeConfig(self):
@@ -107,11 +183,25 @@ class LocalConfig:
self._settings["version"] = __version__
try:
with open(self._config_file, "w", encoding="utf-8") as f:
temporary = os.path.join(os.path.dirname(self._config_file), "gns3_gui.tmp")
with open(temporary, "w", encoding="utf-8") as f:
json.dump(self._settings, f, sort_keys=True, indent=4)
shutil.move(temporary, self._config_file)
log.info("Configuration save to %s", self._config_file)
self._last_config_changed = os.stat(self._config_file).st_mtime
except (ValueError, OSError) as e:
log.error("Could not write the config file {}: {}".format(self._config_file, e))
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...")
self._readConfig(self._config_file)
self.config_changed_signal.emit()
except OSError as e:
log.error("Error when checking for changes {}: {}".format(self._config_file, str(e)))
def configFilePath(self):
"""
Returns the config file path.
@@ -128,8 +218,8 @@ class LocalConfig:
:returns: path to the config file.
"""
self._settings = self._readConfig(self._config_file)
self._config_file = config_file
self._readConfig(self._config_file)
def settings(self):
"""
@@ -138,7 +228,7 @@ class LocalConfig:
:returns: settings (dict)
"""
return self._readConfig(self._config_file)
return copy.deepcopy(self._settings)
def setSettings(self, settings):
"""
@@ -147,8 +237,9 @@ class LocalConfig:
:param settings: settings to save (dict)
"""
self._settings.update(settings)
self._writeConfig()
if self._settings != settings:
self._settings.update(settings)
self._writeConfig()
def loadSectionSettings(self, section, default_settings):
"""
@@ -160,16 +251,32 @@ class LocalConfig:
"""
settings = self.settings().get(section, dict())
changed = False
# use default values for missing settings
for name, value in default_settings.items():
if name not in settings:
settings[name] = value
def _copySettings(local, default):
"""
Copy only existing settings, ignore the other.
Add default values if require.
"""
nonlocal changed
if section not in self._settings:
self._settings[section] = {}
self._settings[section].update(settings)
return settings
# 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):
"""
@@ -181,17 +288,68 @@ class LocalConfig:
if section not in self._settings:
self._settings[section] = {}
self._settings[section].update(settings)
self._writeConfig()
if self._settings[section] != 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():
def instance(config_file=None):
"""
Singleton to return only on instance of LocalConfig.
:returns: instance of LocalConfig
"""
if not hasattr(LocalConfig, "_instance"):
LocalConfig._instance = LocalConfig()
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
@@ -33,12 +32,21 @@ class LocalServerConfig:
def __init__(self):
appname = "GNS3"
self._config = configparser.RawConfigParser()
if sys.platform.startswith("win"):
filename = "gns3_server.ini"
else:
filename = "gns3_server.conf"
self._config_file = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), filename)
if sys.platform.startswith("win"):
appdata = os.path.expandvars("%APPDATA%")
self._config_file = os.path.join(appdata, appname, filename)
else:
home = os.path.expanduser("~")
self._config_file = os.path.join(home, ".config", appname, filename)
try:
# create the config file if it doesn't exist
open(self._config_file, "a").close()
@@ -52,8 +60,8 @@ class LocalServerConfig:
"""
try:
self._config.read(self._config_file)
except (OSError, configparser.Error) as e:
self._config.read(self._config_file, encoding="utf-8")
except (OSError, configparser.Error, UnicodeEncodeError, UnicodeDecodeError) as e:
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
def writeConfig(self):

View File

@@ -21,6 +21,7 @@
import logging
import sys
import os
class ColouredFormatter(logging.Formatter):
@@ -81,7 +82,7 @@ class ColouredStreamHandler(logging.StreamHandler):
self.handleError(record)
def init_logger(level, quiet=False):
def init_logger(level, logfile, quiet=False):
if sys.platform.startswith("win"):
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
@@ -89,5 +90,20 @@ def init_logger(level, quiet=False):
stream_handler = ColouredStreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{")
logging.basicConfig(level=level, handlers=[stream_handler])
logging.getLogger().addHandler(stream_handler)
log = logging.getLogger()
log.addHandler(stream_handler)
try:
try:
os.makedirs(os.path.dirname(logfile))
except FileExistsError:
pass
handler = logging.FileHandler(logfile, "w")
handler.formatter = logging.Formatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
log.addHandler(handler)
except OSError as e:
log.warn("could not log to {}: {}".format(logfile, e))
log.info('Log level: {}'.format(logging.getLevelName(level)))
return logging.getLogger()

View File

@@ -16,6 +16,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
# Try to install updates & restart application if an update is installed
try:
import gns3.update_manager
if gns3.update_manager.UpdateManager().installDownloadedUpdates():
print("Update installed restart the application")
python = sys.executable
os.execl(python, *sys.argv)
except Exception as e:
print("Fail update installation: {}".format(str(e)))
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
@@ -27,28 +40,28 @@ from gns3.utils.get_resource import get_resource
import datetime
import sys
import os
import traceback
import time
import locale
import argparse
import signal
import re
try:
from gns3.qt import QtCore, QtGui, QtWidgets, DEFAULT_BINDING
except ImportError:
raise SystemExit("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
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__)
try:
from gns3.qt import QtCore, QtGui, DEFAULT_BINDING
except ImportError:
raise RuntimeError("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
from gns3.main_window import MainWindow
from gns3.version import __version__
@@ -96,15 +109,45 @@ def main():
Entry point for GNS3 GUI.
"""
# Sometimes (for example at first launch) the OSX app service launcher add
# an extra argument starting with -psn_. We filter it
if sys.platform.startswith("darwin"):
sys.argv = [a for a in sys.argv if not a.startswith("-psn_")]
parser = argparse.ArgumentParser()
parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
parser.add_argument("--version", help="show the version", action="version", version=__version__)
parser.add_argument("--debug", help="print out debug messages", action="store_true", default=False)
parser.add_argument("--config", help="Configuration file")
options = parser.parse_args()
exception_file_path = "exceptions.log"
if options.project and hasattr(sys, "frozen"):
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
if options.config:
LocalConfig.instance(config_file=options.config)
else:
LocalConfig.instance()
if hasattr(sys, "frozen"):
# We add to the path where the OS search executable our binary location starting by GNS3
# packaged binary
frozen_dir = os.path.dirname(os.path.abspath(sys.executable))
if sys.platform.startswith("darwin"):
frozen_dirs = [
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, '..', 'Resources'))
]
elif sys.platform.startswith("win"):
frozen_dirs = [
frozen_dir,
os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
os.path.normpath(os.path.join(frozen_dir, 'vpcs'))
]
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):
@@ -113,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")
@@ -141,24 +184,23 @@ def main():
print("GNS3 GUI version {}".format(__version__))
print("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
# we only support Python 2 version >= 2.7 and Python 3 version >= 3.3
if sys.version_info < (2, 7):
raise RuntimeError("Python 2.7 or higher is required")
elif sys.version_info[0] == 3 and sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or higher is required")
# we only support Python 3 version >= 3.4
if sys.version_info < (3, 4):
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 RuntimeError("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 == "PyQt" and version(QtCore.BINDING_VERSION_STR) < version("4.8.3"):
raise RuntimeError("Requirement is PyQt version 4.8.3 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
if DEFAULT_BINDING == "PyQt4" and version(QtCore.BINDING_VERSION_STR) < version("4.8.3"):
raise SystemExit("Requirement is PyQt version 4.8.3 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
if DEFAULT_BINDING == "PySide" and version(QtCore.BINDING_VERSION_STR) < version("1.0"):
raise RuntimeError("Requirement is PySide version 1.0 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
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)
@@ -180,7 +222,7 @@ def main():
import win32con
import win32gui
except ImportError:
raise RuntimeError("Python for Windows extensions must be installed.")
raise SystemExit("Python for Windows extensions must be installed.")
if not options.debug:
try:
@@ -190,45 +232,48 @@ def main():
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
app = QtGui.QApplication(sys.argv)
global app
app = Application(sys.argv)
# this info is necessary for QSettings
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
datefmt="%y%m%d %H:%M:%S")
# save client logging info to a file
logfile = os.path.join(LocalConfig.configDirectory(), "gns3_gui.log")
# on debug enable logging to stdout
if options.debug:
root_logger = init_logger(logging.DEBUG)
root_logger = init_logger(logging.DEBUG, logfile)
else:
root_logger = init_logger(logging.INFO)
# save client logging info to a file
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "gns3_gui.log")
try:
try:
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
except FileExistsError:
pass
handler = logging.FileHandler(logfile, "w")
root_logger.addHandler(handler)
except OSError as e:
log.warn("could not log to {}: {}".format(logfile, e))
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
root_logger = init_logger(logging.INFO, logfile)
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), exception_file_path)
exception_file_path = os.path.join(LocalConfig.configDirectory(), exception_file_path)
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):
log.info("Signal received exiting the application")
mainwindow.setSoftExit(False)
app.closeAllWindows()
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigint_handler)
mainwindow = MainWindow(options.project)
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
# conditions
import gc
gc.collect()
sys.exit(exit_code)

File diff suppressed because it is too large Load Diff

View File

@@ -21,5 +21,6 @@ from gns3.modules.iou import IOU
from gns3.modules.vpcs import VPCS
from gns3.modules.virtualbox import VirtualBox
from gns3.modules.qemu import Qemu
from gns3.modules.vmware import VMware
MODULES = [VPCS, Dynamips, IOU, VirtualBox, Qemu, Builtin]
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Builtin]

View File

@@ -19,10 +19,8 @@
Built-in module implementation.
"""
from gns3.qt import QtGui
from gns3.servers import Servers
from gns3.qt import QtWidgets
from ..module import Module
from ..module_error import ModuleError
from .cloud import Cloud
from .host import Host
@@ -38,10 +36,13 @@ class Builtin(Module):
"""
def __init__(self):
Module.__init__(self)
super().__init__()
self._nodes = []
def configChangedSlot(self):
pass
def addNode(self, node):
"""
Adds a node to this module.
@@ -69,57 +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()
if not all(using_local_server) and len(remote_servers):
# a module is not using a local server
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))
server_list = []
server_list.append("Local server ({}:{})".format(local_server.host, local_server.port))
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) = QtGui.QInputDialog.getItem(mainwindow, "Server", "Please choose a server", server_list, 0, False)
if ok:
if selection.startswith("Local server"):
return local_server
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.
@@ -156,15 +106,15 @@ class Builtin(Module):
available_interfaces.append(interface["name"])
if available_interfaces:
selection, ok = QtGui.QInputDialog.getItem(mainwindow,
"Cloud interfaces", "Interface {} could not be found\nPlease select an alternative from your existing interfaces:".format(missing_interface),
available_interfaces, 0, False)
selection, ok = QtWidgets.QInputDialog.getItem(mainwindow,
"Cloud interfaces", "Interface {} could not be found\nPlease select an alternative from your existing interfaces:".format(missing_interface),
available_interfaces, 0, False)
if ok:
return selection
QtGui.QMessageBox.warning(mainwindow, "Cloud interface", "No alternative interface chosen to replace {} on this host, this may lead to issues".format(missing_interface))
QtWidgets.QMessageBox.warning(mainwindow, "Cloud interface", "No alternative interface chosen to replace {} on this host, this may lead to issues".format(missing_interface))
return None
else:
QtGui.QMessageBox.critical(mainwindow, "Cloud interface", "Could not find interface {} on this host".format(missing_interface))
QtWidgets.QMessageBox.critical(mainwindow, "Cloud interface", "Could not find interface {} on this host".format(missing_interface))
return missing_interface
@staticmethod
@@ -201,8 +151,7 @@ class Builtin(Module):
{"class": node_class.__name__,
"name": node_class.symbolName(),
"categories": node_class.categories(),
"default_symbol": node_class.defaultSymbol(),
"hover_symbol": node_class.hoverSymbol()}
"symbol": node_class.defaultSymbol()}
)
return nodes

View File

@@ -17,7 +17,6 @@
"""
NIO implementation on the client side (in the form of a pseudo node represented as a cloud).
Asynchronously sends JSON messages to the GNS3 server and receives responses with callbacks.
"""
import re
@@ -49,7 +48,8 @@ class Cloud(Node):
_name_instance_count = 1
def __init__(self, module, server, project):
Node.__init__(self, module, server, project)
super().__init__(module, server, project)
log.info("cloud is being created")
# create an unique id and name
@@ -58,8 +58,6 @@ class Cloud(Node):
name = "Cloud {}".format(self._name_id)
self.setStatus(Node.started) # this is an always-on node
self._defaults = {}
self._ports = []
self._initial_settings = None
self._settings = {"name": name,
"interfaces": {},
@@ -74,7 +72,7 @@ class Cloud(Node):
self.delete_links_signal.emit()
self.deleted_signal.emit()
def setup(self, name=None, initial_settings={}):
def setup(self, name=None, additional_settings={}):
"""
Setups this cloud.
@@ -84,8 +82,8 @@ class Cloud(Node):
if name:
self._settings["name"] = name
if initial_settings:
self._initial_settings = initial_settings
if additional_settings and "nios" in additional_settings:
self._settings["nios"] = additional_settings["nios"]
self._server.get("/interfaces", self._setupCallback)
@@ -104,13 +102,16 @@ class Cloud(Node):
else:
self._settings["interfaces"] = result.copy()
if self._initial_settings and "nios" in self._initial_settings and self._initial_settings["nios"]:
self._initial_settings["interfaces"] = {}
self.update(self._initial_settings)
if self._settings["nios"]:
self._addPorts(self._settings["nios"])
if self._loading:
self.loaded_signal.emit()
else:
log.info("cloud {} has been created".format(self.name()))
self.setInitialized(True)
log.info("cloud {} has been created".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
def _createNIOUDP(self, nio):
"""
@@ -220,6 +221,56 @@ class Cloud(Node):
return NIONull(identifier)
return None
def _allocateNIO(self, nio):
"""
Allocate a new NIO object.
:param nio: NIO description
:returns: NIO instance
"""
nio_object = None
if nio.lower().startswith("nio_udp"):
nio_object = self._createNIOUDP(nio)
if nio.lower().startswith("nio_gen_eth"):
nio_object = self._createNIOGenericEthernet(nio)
if nio.lower().startswith("nio_gen_linux"):
nio_object = self._createNIOLinuxEthernet(nio)
if nio.lower().startswith("nio_nat"):
nio_object = self._createNIONAT(nio)
if nio.lower().startswith("nio_tap"):
nio_object = self._createNIOTAP(nio)
if nio.lower().startswith("nio_unix"):
nio_object = self._createNIOUNIX(nio)
if nio.lower().startswith("nio_vde"):
nio_object = self._createNIOVDE(nio)
if nio.lower().startswith("nio_null"):
nio_object = self._createNIONull(nio)
if nio_object is None:
log.error("Could not create NIO object from {}".format(nio))
return nio_object
def _addPorts(self, nios, ignore_existing_nio=False):
"""
Adds adapters.
:param adapters: number of adapters
"""
# add ports
for nio in nios:
if ignore_existing_nio and nio in self._settings["nios"]:
# port already created for this NIO
continue
nio_object = self._allocateNIO(nio)
if nio_object is None:
continue
port = Port(nio, nio_object, stub=True)
port.setStatus(Port.started)
self._ports.append(port)
log.debug("port {} has been added".format(nio))
def update(self, new_settings):
"""
Updates the settings for this cloud.
@@ -230,37 +281,8 @@ class Cloud(Node):
updated = False
if "nios" in new_settings:
nios = new_settings["nios"]
# add ports
for nio in nios:
if nio in self._settings["nios"]:
# port already created for this NIO
continue
nio_object = None
if nio.lower().startswith("nio_udp"):
nio_object = self._createNIOUDP(nio)
if nio.lower().startswith("nio_gen_eth"):
nio_object = self._createNIOGenericEthernet(nio)
if nio.lower().startswith("nio_gen_linux"):
nio_object = self._createNIOLinuxEthernet(nio)
if nio.lower().startswith("nio_nat"):
nio_object = self._createNIONAT(nio)
if nio.lower().startswith("nio_tap"):
nio_object = self._createNIOTAP(nio)
if nio.lower().startswith("nio_unix"):
nio_object = self._createNIOUNIX(nio)
if nio.lower().startswith("nio_vde"):
nio_object = self._createNIOVDE(nio)
if nio.lower().startswith("nio_null"):
nio_object = self._createNIONull(nio)
if nio_object is None:
log.error("Could not create NIO object from {}".format(nio))
continue
port = Port(nio, nio_object, stub=True)
port.setStatus(Port.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(nio))
self._addPorts(nios, ignore_existing_nio=True)
updated = True
# delete ports
for nio in self._settings["nios"]:
@@ -328,8 +350,7 @@ This is a pseudo-device for external connections
"description": str(self),
"properties": {"name": self.name(),
"nios": self._settings["nios"]},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
# add the ports
if self._ports:
@@ -347,22 +368,25 @@ This is a pseudo-device for external connections
:param node_info: representation of the node (dictionary)
"""
self.node_info = node_info
settings = node_info["properties"]
name = settings.pop("name")
self.updated_signal.connect(self._updatePortSettings)
log.info("cloud {} is loading".format(name))
self.setup(name, settings)
self.setName(name)
self._loading = True
self._node_info = node_info
self.loaded_signal.connect(self._updatePortSettings)
self.setup(name, additional_settings=settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.updated_signal.disconnect(self._updatePortSettings)
self.loaded_signal.disconnect(self._updatePortSettings)
# update the port with the correct IDs
if "ports" in self.node_info:
ports = self.node_info["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["name"] == port.name():
@@ -381,12 +405,18 @@ This is a pseudo-device for external connections
if topology_port["name"] in self._settings["nios"]:
self._settings["nios"].remove(topology_port["name"])
topology_port["name"] = topology_port["name"].replace(topology_port_name, alternative_interface)
nio = self._allocateNIO(topology_port["name"])
port.setDefaultNio(nio)
port.setName(topology_port["name"])
self._settings["nios"].append(topology_port["name"])
log.info("cloud {} has been created".format(self.name()))
# now we can set the node as initialized and trigger the created signal
self.setInitialized(True)
log.info("cloud {} has been loaded".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
self._loading = False
self._node_info = None
def name(self):
"""
@@ -417,7 +447,7 @@ This is a pseudo-device for external connections
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -433,17 +463,7 @@ This is a pseudo-device for external connections
:returns: symbol path (or resource).
"""
return ":/symbols/cloud.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when the cloud is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/cloud.selected.svg"
return ":/symbols/cloud.svg"
@staticmethod
def symbolName():

View File

@@ -36,7 +36,7 @@ class Host(Cloud):
_name_instance_count = 1
def __init__(self, module, server, project):
Cloud.__init__(self, module, server, project)
super().__init__(module, server, project)
log.info("host is being created")
# create an unique id and name
@@ -46,7 +46,7 @@ class Host(Cloud):
name = "Host{}".format(self._name_id)
self._settings["name"] = name
def setup(self, name=None, initial_settings={}):
def setup(self, name=None, additional_settings={}):
"""
Setups this host.
@@ -56,8 +56,8 @@ class Host(Cloud):
if name:
self._settings["name"] = name
if initial_settings:
self._initial_settings = initial_settings
if additional_settings and "nios" in additional_settings:
self._settings["nios"] = additional_settings["nios"]
else:
self.created_signal.connect(self._autoConfigure)
@@ -76,7 +76,6 @@ class Host(Cloud):
new_settings["nios"].append("nio_tap:{}".format(interface["name"]))
else:
new_settings["nios"].append("nio_gen_eth:{}".format(interface["name"]))
self.update(new_settings)
@staticmethod
@@ -87,17 +86,7 @@ class Host(Cloud):
:returns: symbol path (or resource).
"""
return ":/symbols/computer.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when the host is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/computer.selected.svg"
return ":/symbols/computer.svg"
@staticmethod
def symbolName():

View File

@@ -20,11 +20,11 @@ Configuration page for clouds.
"""
import re
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtWidgets
from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
"""
QWidget configuration page for clouds.
@@ -32,7 +32,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._nios = []
@@ -128,7 +128,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiGenericEthernetListWidget.takeItem(self.uiGenericEthernetListWidget.currentRow())
@@ -177,7 +177,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiLinuxEthernetListWidget.takeItem(self.uiLinuxEthernetListWidget.currentRow())
@@ -231,7 +231,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIONATListWidget.takeItem(self.uiNIONATListWidget.currentRow())
@@ -293,7 +293,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIOUDPListWidget.takeItem(self.uiNIOUDPListWidget.currentRow())
@@ -347,7 +347,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIOTAPListWidget.takeItem(self.uiNIOTAPListWidget.currentRow())
@@ -404,7 +404,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIOUNIXListWidget.takeItem(self.uiNIOUNIXListWidget.currentRow())
@@ -460,7 +460,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIOVDEListWidget.takeItem(self.uiNIOVDEListWidget.currentRow())
@@ -514,7 +514,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIONullListWidget.takeItem(self.uiNIONullListWidget.currentRow())
@@ -545,7 +545,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiGenericEthernetComboBox.addItem(interface["name"])
self.uiGenericEthernetComboBox.setItemData(index, interface["id"], QtCore.Qt.ToolTipRole)
index += 1
self.uiGenericEthernetComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.uiGenericEthernetComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
# load all network interfaces
self.uiLinuxEthernetComboBox.clear()
@@ -555,7 +555,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiLinuxEthernetComboBox.addItem(interface["name"])
self.uiLinuxEthernetComboBox.setItemData(index, interface["id"], QtCore.Qt.ToolTipRole)
index += 1
self.uiLinuxEthernetComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.uiLinuxEthernetComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
# populate the NIO lists
self.nios = []

View File

@@ -1,418 +1,405 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created: Tue May 5 21:08:19 2015
# by: PyQt4 UI code generator 4.10.4
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtGui, QtWidgets
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName(_fromUtf8("cloudConfigPageWidget"))
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
cloudConfigPageWidget.resize(653, 478)
self.vboxlayout = QtGui.QVBoxLayout(cloudConfigPageWidget)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.uiNIOsTabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
self.uiNIOsTabWidget.setObjectName(_fromUtf8("uiNIOsTabWidget"))
self.NIOEthernetTab = QtGui.QWidget()
self.NIOEthernetTab.setObjectName(_fromUtf8("NIOEthernetTab"))
self.vboxlayout1 = QtGui.QVBoxLayout(self.NIOEthernetTab)
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
self.uiGenericEthernetGroupBox.setObjectName(_fromUtf8("uiGenericEthernetGroupBox"))
self.gridlayout = QtGui.QGridLayout(self.uiGenericEthernetGroupBox)
self.gridlayout.setObjectName(_fromUtf8("gridlayout"))
self.uiGenericEthernetComboBox = QtGui.QComboBox(self.uiGenericEthernetGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.vboxlayout = QtWidgets.QVBoxLayout(cloudConfigPageWidget)
self.vboxlayout.setObjectName("vboxlayout")
self.uiNIOsTabWidget = QtWidgets.QTabWidget(cloudConfigPageWidget)
self.uiNIOsTabWidget.setObjectName("uiNIOsTabWidget")
self.NIOEthernetTab = QtWidgets.QWidget()
self.NIOEthernetTab.setObjectName("NIOEthernetTab")
self.vboxlayout1 = QtWidgets.QVBoxLayout(self.NIOEthernetTab)
self.vboxlayout1.setObjectName("vboxlayout1")
self.uiGenericEthernetGroupBox = QtWidgets.QGroupBox(self.NIOEthernetTab)
self.uiGenericEthernetGroupBox.setObjectName("uiGenericEthernetGroupBox")
self.gridlayout = QtWidgets.QGridLayout(self.uiGenericEthernetGroupBox)
self.gridlayout.setObjectName("gridlayout")
self.uiGenericEthernetComboBox = QtWidgets.QComboBox(self.uiGenericEthernetGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiGenericEthernetComboBox.sizePolicy().hasHeightForWidth())
self.uiGenericEthernetComboBox.setSizePolicy(sizePolicy)
self.uiGenericEthernetComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.uiGenericEthernetComboBox.setObjectName(_fromUtf8("uiGenericEthernetComboBox"))
self.uiGenericEthernetComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.uiGenericEthernetComboBox.setObjectName("uiGenericEthernetComboBox")
self.gridlayout.addWidget(self.uiGenericEthernetComboBox, 0, 0, 1, 3)
self.uiGenericEthernetLineEdit = QtGui.QLineEdit(self.uiGenericEthernetGroupBox)
self.uiGenericEthernetLineEdit.setObjectName(_fromUtf8("uiGenericEthernetLineEdit"))
self.uiGenericEthernetLineEdit = QtWidgets.QLineEdit(self.uiGenericEthernetGroupBox)
self.uiGenericEthernetLineEdit.setObjectName("uiGenericEthernetLineEdit")
self.gridlayout.addWidget(self.uiGenericEthernetLineEdit, 1, 0, 1, 1)
self.uiAddGenericEthernetPushButton = QtGui.QPushButton(self.uiGenericEthernetGroupBox)
self.uiAddGenericEthernetPushButton.setObjectName(_fromUtf8("uiAddGenericEthernetPushButton"))
self.uiAddGenericEthernetPushButton = QtWidgets.QPushButton(self.uiGenericEthernetGroupBox)
self.uiAddGenericEthernetPushButton.setObjectName("uiAddGenericEthernetPushButton")
self.gridlayout.addWidget(self.uiAddGenericEthernetPushButton, 1, 1, 1, 1)
self.uiDeleteGenericEthernetPushButton = QtGui.QPushButton(self.uiGenericEthernetGroupBox)
self.uiDeleteGenericEthernetPushButton = QtWidgets.QPushButton(self.uiGenericEthernetGroupBox)
self.uiDeleteGenericEthernetPushButton.setEnabled(False)
self.uiDeleteGenericEthernetPushButton.setObjectName(_fromUtf8("uiDeleteGenericEthernetPushButton"))
self.uiDeleteGenericEthernetPushButton.setObjectName("uiDeleteGenericEthernetPushButton")
self.gridlayout.addWidget(self.uiDeleteGenericEthernetPushButton, 1, 2, 1, 1)
self.uiGenericEthernetListWidget = QtGui.QListWidget(self.uiGenericEthernetGroupBox)
self.uiGenericEthernetListWidget.setObjectName(_fromUtf8("uiGenericEthernetListWidget"))
self.uiGenericEthernetListWidget = QtWidgets.QListWidget(self.uiGenericEthernetGroupBox)
self.uiGenericEthernetListWidget.setObjectName("uiGenericEthernetListWidget")
self.gridlayout.addWidget(self.uiGenericEthernetListWidget, 2, 0, 1, 3)
self.vboxlayout1.addWidget(self.uiGenericEthernetGroupBox)
self.uiLinuxEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
self.uiLinuxEthernetGroupBox = QtWidgets.QGroupBox(self.NIOEthernetTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLinuxEthernetGroupBox.sizePolicy().hasHeightForWidth())
self.uiLinuxEthernetGroupBox.setSizePolicy(sizePolicy)
self.uiLinuxEthernetGroupBox.setObjectName(_fromUtf8("uiLinuxEthernetGroupBox"))
self.gridlayout1 = QtGui.QGridLayout(self.uiLinuxEthernetGroupBox)
self.gridlayout1.setObjectName(_fromUtf8("gridlayout1"))
self.uiLinuxEthernetComboBox = QtGui.QComboBox(self.uiLinuxEthernetGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiLinuxEthernetGroupBox.setObjectName("uiLinuxEthernetGroupBox")
self.gridlayout1 = QtWidgets.QGridLayout(self.uiLinuxEthernetGroupBox)
self.gridlayout1.setObjectName("gridlayout1")
self.uiLinuxEthernetComboBox = QtWidgets.QComboBox(self.uiLinuxEthernetGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLinuxEthernetComboBox.sizePolicy().hasHeightForWidth())
self.uiLinuxEthernetComboBox.setSizePolicy(sizePolicy)
self.uiLinuxEthernetComboBox.setObjectName(_fromUtf8("uiLinuxEthernetComboBox"))
self.uiLinuxEthernetComboBox.setObjectName("uiLinuxEthernetComboBox")
self.gridlayout1.addWidget(self.uiLinuxEthernetComboBox, 0, 0, 1, 3)
self.uiLinuxEthernetLineEdit = QtGui.QLineEdit(self.uiLinuxEthernetGroupBox)
self.uiLinuxEthernetLineEdit.setObjectName(_fromUtf8("uiLinuxEthernetLineEdit"))
self.uiLinuxEthernetLineEdit = QtWidgets.QLineEdit(self.uiLinuxEthernetGroupBox)
self.uiLinuxEthernetLineEdit.setObjectName("uiLinuxEthernetLineEdit")
self.gridlayout1.addWidget(self.uiLinuxEthernetLineEdit, 1, 0, 1, 1)
self.uiAddLinuxEthernetPushButton = QtGui.QPushButton(self.uiLinuxEthernetGroupBox)
self.uiAddLinuxEthernetPushButton.setObjectName(_fromUtf8("uiAddLinuxEthernetPushButton"))
self.uiAddLinuxEthernetPushButton = QtWidgets.QPushButton(self.uiLinuxEthernetGroupBox)
self.uiAddLinuxEthernetPushButton.setObjectName("uiAddLinuxEthernetPushButton")
self.gridlayout1.addWidget(self.uiAddLinuxEthernetPushButton, 1, 1, 1, 1)
self.uiDeleteLinuxEthernetPushButton = QtGui.QPushButton(self.uiLinuxEthernetGroupBox)
self.uiDeleteLinuxEthernetPushButton = QtWidgets.QPushButton(self.uiLinuxEthernetGroupBox)
self.uiDeleteLinuxEthernetPushButton.setEnabled(False)
self.uiDeleteLinuxEthernetPushButton.setObjectName(_fromUtf8("uiDeleteLinuxEthernetPushButton"))
self.uiDeleteLinuxEthernetPushButton.setObjectName("uiDeleteLinuxEthernetPushButton")
self.gridlayout1.addWidget(self.uiDeleteLinuxEthernetPushButton, 1, 2, 1, 1)
self.uiLinuxEthernetListWidget = QtGui.QListWidget(self.uiLinuxEthernetGroupBox)
self.uiLinuxEthernetListWidget.setObjectName(_fromUtf8("uiLinuxEthernetListWidget"))
self.uiLinuxEthernetListWidget = QtWidgets.QListWidget(self.uiLinuxEthernetGroupBox)
self.uiLinuxEthernetListWidget.setObjectName("uiLinuxEthernetListWidget")
self.gridlayout1.addWidget(self.uiLinuxEthernetListWidget, 2, 0, 1, 3)
self.vboxlayout1.addWidget(self.uiLinuxEthernetGroupBox)
spacerItem = QtGui.QSpacerItem(21, 16, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
spacerItem = QtWidgets.QSpacerItem(21, 16, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
self.vboxlayout1.addItem(spacerItem)
self.uiNIOsTabWidget.addTab(self.NIOEthernetTab, _fromUtf8(""))
self.NIONATTab = QtGui.QWidget()
self.NIONATTab.setObjectName(_fromUtf8("NIONATTab"))
self.gridLayout_2 = QtGui.QGridLayout(self.NIONATTab)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiNIONATSettingsGroupBox = QtGui.QGroupBox(self.NIONATTab)
self.uiNIONATSettingsGroupBox.setObjectName(_fromUtf8("uiNIONATSettingsGroupBox"))
self._2 = QtGui.QGridLayout(self.uiNIONATSettingsGroupBox)
self._2.setObjectName(_fromUtf8("_2"))
self.uiNIONATIdentifierLabel = QtGui.QLabel(self.uiNIONATSettingsGroupBox)
self.uiNIONATIdentifierLabel.setObjectName(_fromUtf8("uiNIONATIdentifierLabel"))
self.uiNIOsTabWidget.addTab(self.NIOEthernetTab, "")
self.NIONATTab = QtWidgets.QWidget()
self.NIONATTab.setObjectName("NIONATTab")
self.gridLayout_2 = QtWidgets.QGridLayout(self.NIONATTab)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiNIONATSettingsGroupBox = QtWidgets.QGroupBox(self.NIONATTab)
self.uiNIONATSettingsGroupBox.setObjectName("uiNIONATSettingsGroupBox")
self._2 = QtWidgets.QGridLayout(self.uiNIONATSettingsGroupBox)
self._2.setObjectName("_2")
self.uiNIONATIdentifierLabel = QtWidgets.QLabel(self.uiNIONATSettingsGroupBox)
self.uiNIONATIdentifierLabel.setObjectName("uiNIONATIdentifierLabel")
self._2.addWidget(self.uiNIONATIdentifierLabel, 0, 0, 1, 1)
self.uiNIONATIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONATSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiNIONATIdentiferLineEdit = QtWidgets.QLineEdit(self.uiNIONATSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONATIdentiferLineEdit.sizePolicy().hasHeightForWidth())
self.uiNIONATIdentiferLineEdit.setSizePolicy(sizePolicy)
self.uiNIONATIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONATIdentiferLineEdit"))
self.uiNIONATIdentiferLineEdit.setObjectName("uiNIONATIdentiferLineEdit")
self._2.addWidget(self.uiNIONATIdentiferLineEdit, 1, 0, 1, 1)
self.gridLayout_2.addWidget(self.uiNIONATSettingsGroupBox, 0, 0, 1, 2)
self.uiNIONATListGroupBox = QtGui.QGroupBox(self.NIONATTab)
self.uiNIONATListGroupBox.setObjectName(_fromUtf8("uiNIONATListGroupBox"))
self._3 = QtGui.QVBoxLayout(self.uiNIONATListGroupBox)
self._3.setObjectName(_fromUtf8("_3"))
self.uiNIONATListWidget = QtGui.QListWidget(self.uiNIONATListGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.uiNIONATListGroupBox = QtWidgets.QGroupBox(self.NIONATTab)
self.uiNIONATListGroupBox.setObjectName("uiNIONATListGroupBox")
self._3 = QtWidgets.QVBoxLayout(self.uiNIONATListGroupBox)
self._3.setObjectName("_3")
self.uiNIONATListWidget = QtWidgets.QListWidget(self.uiNIONATListGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONATListWidget.sizePolicy().hasHeightForWidth())
self.uiNIONATListWidget.setSizePolicy(sizePolicy)
self.uiNIONATListWidget.setObjectName(_fromUtf8("uiNIONATListWidget"))
self.uiNIONATListWidget.setObjectName("uiNIONATListWidget")
self._3.addWidget(self.uiNIONATListWidget)
self.gridLayout_2.addWidget(self.uiNIONATListGroupBox, 0, 2, 3, 1)
self.uiAddNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
self.uiAddNIONATPushButton.setObjectName(_fromUtf8("uiAddNIONATPushButton"))
self.uiAddNIONATPushButton = QtWidgets.QPushButton(self.NIONATTab)
self.uiAddNIONATPushButton.setObjectName("uiAddNIONATPushButton")
self.gridLayout_2.addWidget(self.uiAddNIONATPushButton, 1, 0, 1, 1)
self.uiDeleteNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
self.uiDeleteNIONATPushButton = QtWidgets.QPushButton(self.NIONATTab)
self.uiDeleteNIONATPushButton.setEnabled(False)
self.uiDeleteNIONATPushButton.setObjectName(_fromUtf8("uiDeleteNIONATPushButton"))
self.uiDeleteNIONATPushButton.setObjectName("uiDeleteNIONATPushButton")
self.gridLayout_2.addWidget(self.uiDeleteNIONATPushButton, 1, 1, 1, 1)
spacerItem1 = QtGui.QSpacerItem(20, 294, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem1 = QtWidgets.QSpacerItem(20, 294, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem1, 2, 0, 2, 1)
spacerItem2 = QtGui.QSpacerItem(20, 194, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem2 = QtWidgets.QSpacerItem(20, 194, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem2, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIONATTab, _fromUtf8(""))
self.NIOUDPTab = QtGui.QWidget()
self.NIOUDPTab.setObjectName(_fromUtf8("NIOUDPTab"))
self.gridlayout2 = QtGui.QGridLayout(self.NIOUDPTab)
self.gridlayout2.setObjectName(_fromUtf8("gridlayout2"))
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUDPSettingsGroupBox"))
self.gridlayout3 = QtGui.QGridLayout(self.uiNIOUDPSettingsGroupBox)
self.gridlayout3.setObjectName(_fromUtf8("gridlayout3"))
self.uiLocalPortLabel = QtGui.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiLocalPortLabel.setObjectName(_fromUtf8("uiLocalPortLabel"))
self.uiNIOsTabWidget.addTab(self.NIONATTab, "")
self.NIOUDPTab = QtWidgets.QWidget()
self.NIOUDPTab.setObjectName("NIOUDPTab")
self.gridlayout2 = QtWidgets.QGridLayout(self.NIOUDPTab)
self.gridlayout2.setObjectName("gridlayout2")
self.uiNIOUDPSettingsGroupBox = QtWidgets.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPSettingsGroupBox.setObjectName("uiNIOUDPSettingsGroupBox")
self.gridlayout3 = QtWidgets.QGridLayout(self.uiNIOUDPSettingsGroupBox)
self.gridlayout3.setObjectName("gridlayout3")
self.uiLocalPortLabel = QtWidgets.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiLocalPortLabel.setObjectName("uiLocalPortLabel")
self.gridlayout3.addWidget(self.uiLocalPortLabel, 0, 0, 1, 1)
self.uiLocalPortSpinBox = QtGui.QSpinBox(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiLocalPortSpinBox = QtWidgets.QSpinBox(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLocalPortSpinBox.sizePolicy().hasHeightForWidth())
self.uiLocalPortSpinBox.setSizePolicy(sizePolicy)
self.uiLocalPortSpinBox.setMaximum(65535)
self.uiLocalPortSpinBox.setProperty("value", 30000)
self.uiLocalPortSpinBox.setObjectName(_fromUtf8("uiLocalPortSpinBox"))
self.uiLocalPortSpinBox.setObjectName("uiLocalPortSpinBox")
self.gridlayout3.addWidget(self.uiLocalPortSpinBox, 0, 1, 1, 1)
self.uiRemoteHostLabel = QtGui.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiRemoteHostLabel.setObjectName(_fromUtf8("uiRemoteHostLabel"))
self.uiRemoteHostLabel = QtWidgets.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiRemoteHostLabel.setObjectName("uiRemoteHostLabel")
self.gridlayout3.addWidget(self.uiRemoteHostLabel, 1, 0, 1, 1)
self.uiRemoteHostLineEdit = QtGui.QLineEdit(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiRemoteHostLineEdit = QtWidgets.QLineEdit(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteHostLineEdit.sizePolicy().hasHeightForWidth())
self.uiRemoteHostLineEdit.setSizePolicy(sizePolicy)
self.uiRemoteHostLineEdit.setMinimumSize(QtCore.QSize(80, 0))
self.uiRemoteHostLineEdit.setObjectName(_fromUtf8("uiRemoteHostLineEdit"))
self.uiRemoteHostLineEdit.setObjectName("uiRemoteHostLineEdit")
self.gridlayout3.addWidget(self.uiRemoteHostLineEdit, 1, 1, 1, 1)
self.uiRemotePortLabel = QtGui.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiRemotePortLabel.setObjectName(_fromUtf8("uiRemotePortLabel"))
self.uiRemotePortLabel = QtWidgets.QLabel(self.uiNIOUDPSettingsGroupBox)
self.uiRemotePortLabel.setObjectName("uiRemotePortLabel")
self.gridlayout3.addWidget(self.uiRemotePortLabel, 2, 0, 1, 1)
self.uiRemotePortSpinBox = QtGui.QSpinBox(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiRemotePortSpinBox = QtWidgets.QSpinBox(self.uiNIOUDPSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemotePortSpinBox.sizePolicy().hasHeightForWidth())
self.uiRemotePortSpinBox.setSizePolicy(sizePolicy)
self.uiRemotePortSpinBox.setMaximum(65535)
self.uiRemotePortSpinBox.setProperty("value", 20000)
self.uiRemotePortSpinBox.setObjectName(_fromUtf8("uiRemotePortSpinBox"))
self.uiRemotePortSpinBox.setObjectName("uiRemotePortSpinBox")
self.gridlayout3.addWidget(self.uiRemotePortSpinBox, 2, 1, 1, 1)
self.gridlayout2.addWidget(self.uiNIOUDPSettingsGroupBox, 0, 0, 1, 2)
self.uiNIOUDPListGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPListGroupBox.setObjectName(_fromUtf8("uiNIOUDPListGroupBox"))
self.vboxlayout2 = QtGui.QVBoxLayout(self.uiNIOUDPListGroupBox)
self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2"))
self.uiNIOUDPListWidget = QtGui.QListWidget(self.uiNIOUDPListGroupBox)
self.uiNIOUDPListWidget.setObjectName(_fromUtf8("uiNIOUDPListWidget"))
self.uiNIOUDPListGroupBox = QtWidgets.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPListGroupBox.setObjectName("uiNIOUDPListGroupBox")
self.vboxlayout2 = QtWidgets.QVBoxLayout(self.uiNIOUDPListGroupBox)
self.vboxlayout2.setObjectName("vboxlayout2")
self.uiNIOUDPListWidget = QtWidgets.QListWidget(self.uiNIOUDPListGroupBox)
self.uiNIOUDPListWidget.setObjectName("uiNIOUDPListWidget")
self.vboxlayout2.addWidget(self.uiNIOUDPListWidget)
self.gridlayout2.addWidget(self.uiNIOUDPListGroupBox, 0, 2, 2, 1)
self.uiAddNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
self.uiAddNIOUDPPushButton.setObjectName(_fromUtf8("uiAddNIOUDPPushButton"))
self.uiAddNIOUDPPushButton = QtWidgets.QPushButton(self.NIOUDPTab)
self.uiAddNIOUDPPushButton.setObjectName("uiAddNIOUDPPushButton")
self.gridlayout2.addWidget(self.uiAddNIOUDPPushButton, 1, 0, 1, 1)
self.uiDeleteNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
self.uiDeleteNIOUDPPushButton = QtWidgets.QPushButton(self.NIOUDPTab)
self.uiDeleteNIOUDPPushButton.setEnabled(False)
self.uiDeleteNIOUDPPushButton.setObjectName(_fromUtf8("uiDeleteNIOUDPPushButton"))
self.uiDeleteNIOUDPPushButton.setObjectName("uiDeleteNIOUDPPushButton")
self.gridlayout2.addWidget(self.uiDeleteNIOUDPPushButton, 1, 1, 1, 1)
spacerItem3 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem3 = QtWidgets.QSpacerItem(20, 211, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout2.addItem(spacerItem3, 2, 1, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOUDPTab, _fromUtf8(""))
self.NIOTAPTab = QtGui.QWidget()
self.NIOTAPTab.setObjectName(_fromUtf8("NIOTAPTab"))
self.vboxlayout3 = QtGui.QVBoxLayout(self.NIOTAPTab)
self.vboxlayout3.setObjectName(_fromUtf8("vboxlayout3"))
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.NIOTAPTab)
self.uiNIOTAPGroupBox.setObjectName(_fromUtf8("uiNIOTAPGroupBox"))
self.gridlayout4 = QtGui.QGridLayout(self.uiNIOTAPGroupBox)
self.gridlayout4.setObjectName(_fromUtf8("gridlayout4"))
self.uiNIOTAPLineEdit = QtGui.QLineEdit(self.uiNIOTAPGroupBox)
self.uiNIOTAPLineEdit.setObjectName(_fromUtf8("uiNIOTAPLineEdit"))
self.uiNIOsTabWidget.addTab(self.NIOUDPTab, "")
self.NIOTAPTab = QtWidgets.QWidget()
self.NIOTAPTab.setObjectName("NIOTAPTab")
self.vboxlayout3 = QtWidgets.QVBoxLayout(self.NIOTAPTab)
self.vboxlayout3.setObjectName("vboxlayout3")
self.uiNIOTAPGroupBox = QtWidgets.QGroupBox(self.NIOTAPTab)
self.uiNIOTAPGroupBox.setObjectName("uiNIOTAPGroupBox")
self.gridlayout4 = QtWidgets.QGridLayout(self.uiNIOTAPGroupBox)
self.gridlayout4.setObjectName("gridlayout4")
self.uiNIOTAPLineEdit = QtWidgets.QLineEdit(self.uiNIOTAPGroupBox)
self.uiNIOTAPLineEdit.setObjectName("uiNIOTAPLineEdit")
self.gridlayout4.addWidget(self.uiNIOTAPLineEdit, 0, 0, 1, 1)
self.uiAddNIOTAPPushButton = QtGui.QPushButton(self.uiNIOTAPGroupBox)
self.uiAddNIOTAPPushButton.setObjectName(_fromUtf8("uiAddNIOTAPPushButton"))
self.uiAddNIOTAPPushButton = QtWidgets.QPushButton(self.uiNIOTAPGroupBox)
self.uiAddNIOTAPPushButton.setObjectName("uiAddNIOTAPPushButton")
self.gridlayout4.addWidget(self.uiAddNIOTAPPushButton, 0, 1, 1, 1)
self.uiDeleteNIOTAPPushButton = QtGui.QPushButton(self.uiNIOTAPGroupBox)
self.uiDeleteNIOTAPPushButton = QtWidgets.QPushButton(self.uiNIOTAPGroupBox)
self.uiDeleteNIOTAPPushButton.setEnabled(False)
self.uiDeleteNIOTAPPushButton.setObjectName(_fromUtf8("uiDeleteNIOTAPPushButton"))
self.uiDeleteNIOTAPPushButton.setObjectName("uiDeleteNIOTAPPushButton")
self.gridlayout4.addWidget(self.uiDeleteNIOTAPPushButton, 0, 2, 1, 1)
self.uiNIOTAPListWidget = QtGui.QListWidget(self.uiNIOTAPGroupBox)
self.uiNIOTAPListWidget.setObjectName(_fromUtf8("uiNIOTAPListWidget"))
self.uiNIOTAPListWidget = QtWidgets.QListWidget(self.uiNIOTAPGroupBox)
self.uiNIOTAPListWidget.setObjectName("uiNIOTAPListWidget")
self.gridlayout4.addWidget(self.uiNIOTAPListWidget, 1, 0, 1, 3)
self.vboxlayout3.addWidget(self.uiNIOTAPGroupBox)
spacerItem4 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem4 = QtWidgets.QSpacerItem(20, 191, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.vboxlayout3.addItem(spacerItem4)
self.uiNIOsTabWidget.addTab(self.NIOTAPTab, _fromUtf8(""))
self.NIOUnixTab = QtGui.QWidget()
self.NIOUnixTab.setObjectName(_fromUtf8("NIOUnixTab"))
self.gridlayout5 = QtGui.QGridLayout(self.NIOUnixTab)
self.gridlayout5.setObjectName(_fromUtf8("gridlayout5"))
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUNIXSettingsGroupBox"))
self.gridlayout6 = QtGui.QGridLayout(self.uiNIOUNIXSettingsGroupBox)
self.gridlayout6.setObjectName(_fromUtf8("gridlayout6"))
self.gridlayout7 = QtGui.QGridLayout()
self.gridlayout7.setObjectName(_fromUtf8("gridlayout7"))
self.uiLocalFileLabel = QtGui.QLabel(self.uiNIOUNIXSettingsGroupBox)
self.uiLocalFileLabel.setObjectName(_fromUtf8("uiLocalFileLabel"))
self.uiNIOsTabWidget.addTab(self.NIOTAPTab, "")
self.NIOUnixTab = QtWidgets.QWidget()
self.NIOUnixTab.setObjectName("NIOUnixTab")
self.gridlayout5 = QtWidgets.QGridLayout(self.NIOUnixTab)
self.gridlayout5.setObjectName("gridlayout5")
self.uiNIOUNIXSettingsGroupBox = QtWidgets.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXSettingsGroupBox.setObjectName("uiNIOUNIXSettingsGroupBox")
self.gridlayout6 = QtWidgets.QGridLayout(self.uiNIOUNIXSettingsGroupBox)
self.gridlayout6.setObjectName("gridlayout6")
self.gridlayout7 = QtWidgets.QGridLayout()
self.gridlayout7.setObjectName("gridlayout7")
self.uiLocalFileLabel = QtWidgets.QLabel(self.uiNIOUNIXSettingsGroupBox)
self.uiLocalFileLabel.setObjectName("uiLocalFileLabel")
self.gridlayout7.addWidget(self.uiLocalFileLabel, 0, 0, 1, 1)
self.uiLocalFileLineEdit = QtGui.QLineEdit(self.uiNIOUNIXSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiLocalFileLineEdit = QtWidgets.QLineEdit(self.uiNIOUNIXSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLocalFileLineEdit.sizePolicy().hasHeightForWidth())
self.uiLocalFileLineEdit.setSizePolicy(sizePolicy)
self.uiLocalFileLineEdit.setObjectName(_fromUtf8("uiLocalFileLineEdit"))
self.uiLocalFileLineEdit.setObjectName("uiLocalFileLineEdit")
self.gridlayout7.addWidget(self.uiLocalFileLineEdit, 1, 0, 1, 1)
self.gridlayout6.addLayout(self.gridlayout7, 0, 0, 1, 1)
self.gridlayout8 = QtGui.QGridLayout()
self.gridlayout8.setObjectName(_fromUtf8("gridlayout8"))
self.uiRemoteFileLabel = QtGui.QLabel(self.uiNIOUNIXSettingsGroupBox)
self.uiRemoteFileLabel.setObjectName(_fromUtf8("uiRemoteFileLabel"))
self.gridlayout8 = QtWidgets.QGridLayout()
self.gridlayout8.setObjectName("gridlayout8")
self.uiRemoteFileLabel = QtWidgets.QLabel(self.uiNIOUNIXSettingsGroupBox)
self.uiRemoteFileLabel.setObjectName("uiRemoteFileLabel")
self.gridlayout8.addWidget(self.uiRemoteFileLabel, 0, 0, 1, 1)
self.uiRemoteFileLineEdit = QtGui.QLineEdit(self.uiNIOUNIXSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiRemoteFileLineEdit = QtWidgets.QLineEdit(self.uiNIOUNIXSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteFileLineEdit.sizePolicy().hasHeightForWidth())
self.uiRemoteFileLineEdit.setSizePolicy(sizePolicy)
self.uiRemoteFileLineEdit.setObjectName(_fromUtf8("uiRemoteFileLineEdit"))
self.uiRemoteFileLineEdit.setObjectName("uiRemoteFileLineEdit")
self.gridlayout8.addWidget(self.uiRemoteFileLineEdit, 1, 0, 1, 1)
self.gridlayout6.addLayout(self.gridlayout8, 1, 0, 1, 1)
self.gridlayout5.addWidget(self.uiNIOUNIXSettingsGroupBox, 0, 0, 1, 2)
self.uiNIOUNIXListGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXListGroupBox.setObjectName(_fromUtf8("uiNIOUNIXListGroupBox"))
self.vboxlayout4 = QtGui.QVBoxLayout(self.uiNIOUNIXListGroupBox)
self.vboxlayout4.setObjectName(_fromUtf8("vboxlayout4"))
self.uiNIOUNIXListWidget = QtGui.QListWidget(self.uiNIOUNIXListGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.uiNIOUNIXListGroupBox = QtWidgets.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXListGroupBox.setObjectName("uiNIOUNIXListGroupBox")
self.vboxlayout4 = QtWidgets.QVBoxLayout(self.uiNIOUNIXListGroupBox)
self.vboxlayout4.setObjectName("vboxlayout4")
self.uiNIOUNIXListWidget = QtWidgets.QListWidget(self.uiNIOUNIXListGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIOUNIXListWidget.sizePolicy().hasHeightForWidth())
self.uiNIOUNIXListWidget.setSizePolicy(sizePolicy)
self.uiNIOUNIXListWidget.setObjectName(_fromUtf8("uiNIOUNIXListWidget"))
self.uiNIOUNIXListWidget.setObjectName("uiNIOUNIXListWidget")
self.vboxlayout4.addWidget(self.uiNIOUNIXListWidget)
self.gridlayout5.addWidget(self.uiNIOUNIXListGroupBox, 0, 2, 3, 1)
self.uiAddNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
self.uiAddNIOUNIXPushButton.setObjectName(_fromUtf8("uiAddNIOUNIXPushButton"))
self.uiAddNIOUNIXPushButton = QtWidgets.QPushButton(self.NIOUnixTab)
self.uiAddNIOUNIXPushButton.setObjectName("uiAddNIOUNIXPushButton")
self.gridlayout5.addWidget(self.uiAddNIOUNIXPushButton, 1, 0, 1, 1)
self.uiDeleteNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
self.uiDeleteNIOUNIXPushButton = QtWidgets.QPushButton(self.NIOUnixTab)
self.uiDeleteNIOUNIXPushButton.setEnabled(False)
self.uiDeleteNIOUNIXPushButton.setObjectName(_fromUtf8("uiDeleteNIOUNIXPushButton"))
self.uiDeleteNIOUNIXPushButton.setObjectName("uiDeleteNIOUNIXPushButton")
self.gridlayout5.addWidget(self.uiDeleteNIOUNIXPushButton, 1, 1, 1, 1)
spacerItem5 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
spacerItem5 = QtWidgets.QSpacerItem(160, 190, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
self.gridlayout5.addItem(spacerItem5, 2, 0, 2, 2)
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem6 = QtWidgets.QSpacerItem(196, 132, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout5.addItem(spacerItem6, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOUnixTab, _fromUtf8(""))
self.NIOVDETab = QtGui.QWidget()
self.NIOVDETab.setObjectName(_fromUtf8("NIOVDETab"))
self.gridlayout9 = QtGui.QGridLayout(self.NIOVDETab)
self.gridlayout9.setObjectName(_fromUtf8("gridlayout9"))
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.NIOVDETab)
self.uiNIOVDESettingsGroupBox.setObjectName(_fromUtf8("uiNIOVDESettingsGroupBox"))
self.gridlayout10 = QtGui.QGridLayout(self.uiNIOVDESettingsGroupBox)
self.gridlayout10.setObjectName(_fromUtf8("gridlayout10"))
self.gridlayout11 = QtGui.QGridLayout()
self.gridlayout11.setObjectName(_fromUtf8("gridlayout11"))
self.uiVDEControlFileLabel = QtGui.QLabel(self.uiNIOVDESettingsGroupBox)
self.uiVDEControlFileLabel.setObjectName(_fromUtf8("uiVDEControlFileLabel"))
self.uiNIOsTabWidget.addTab(self.NIOUnixTab, "")
self.NIOVDETab = QtWidgets.QWidget()
self.NIOVDETab.setObjectName("NIOVDETab")
self.gridlayout9 = QtWidgets.QGridLayout(self.NIOVDETab)
self.gridlayout9.setObjectName("gridlayout9")
self.uiNIOVDESettingsGroupBox = QtWidgets.QGroupBox(self.NIOVDETab)
self.uiNIOVDESettingsGroupBox.setObjectName("uiNIOVDESettingsGroupBox")
self.gridlayout10 = QtWidgets.QGridLayout(self.uiNIOVDESettingsGroupBox)
self.gridlayout10.setObjectName("gridlayout10")
self.gridlayout11 = QtWidgets.QGridLayout()
self.gridlayout11.setObjectName("gridlayout11")
self.uiVDEControlFileLabel = QtWidgets.QLabel(self.uiNIOVDESettingsGroupBox)
self.uiVDEControlFileLabel.setObjectName("uiVDEControlFileLabel")
self.gridlayout11.addWidget(self.uiVDEControlFileLabel, 0, 0, 1, 1)
self.uiVDEControlFileLineEdit = QtGui.QLineEdit(self.uiNIOVDESettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiVDEControlFileLineEdit = QtWidgets.QLineEdit(self.uiNIOVDESettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiVDEControlFileLineEdit.sizePolicy().hasHeightForWidth())
self.uiVDEControlFileLineEdit.setSizePolicy(sizePolicy)
self.uiVDEControlFileLineEdit.setObjectName(_fromUtf8("uiVDEControlFileLineEdit"))
self.uiVDEControlFileLineEdit.setObjectName("uiVDEControlFileLineEdit")
self.gridlayout11.addWidget(self.uiVDEControlFileLineEdit, 1, 0, 1, 1)
self.gridlayout10.addLayout(self.gridlayout11, 0, 0, 1, 1)
self.gridlayout12 = QtGui.QGridLayout()
self.gridlayout12.setObjectName(_fromUtf8("gridlayout12"))
self.uiVDELocalFileLabel = QtGui.QLabel(self.uiNIOVDESettingsGroupBox)
self.uiVDELocalFileLabel.setObjectName(_fromUtf8("uiVDELocalFileLabel"))
self.gridlayout12 = QtWidgets.QGridLayout()
self.gridlayout12.setObjectName("gridlayout12")
self.uiVDELocalFileLabel = QtWidgets.QLabel(self.uiNIOVDESettingsGroupBox)
self.uiVDELocalFileLabel.setObjectName("uiVDELocalFileLabel")
self.gridlayout12.addWidget(self.uiVDELocalFileLabel, 0, 0, 1, 1)
self.uiVDELocalFileLineEdit = QtGui.QLineEdit(self.uiNIOVDESettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiVDELocalFileLineEdit = QtWidgets.QLineEdit(self.uiNIOVDESettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiVDELocalFileLineEdit.sizePolicy().hasHeightForWidth())
self.uiVDELocalFileLineEdit.setSizePolicy(sizePolicy)
self.uiVDELocalFileLineEdit.setObjectName(_fromUtf8("uiVDELocalFileLineEdit"))
self.uiVDELocalFileLineEdit.setObjectName("uiVDELocalFileLineEdit")
self.gridlayout12.addWidget(self.uiVDELocalFileLineEdit, 1, 0, 1, 1)
self.gridlayout10.addLayout(self.gridlayout12, 1, 0, 1, 1)
self.gridlayout9.addWidget(self.uiNIOVDESettingsGroupBox, 0, 0, 1, 2)
self.uiNIOVDEListGroupBox = QtGui.QGroupBox(self.NIOVDETab)
self.uiNIOVDEListGroupBox.setObjectName(_fromUtf8("uiNIOVDEListGroupBox"))
self.vboxlayout5 = QtGui.QVBoxLayout(self.uiNIOVDEListGroupBox)
self.vboxlayout5.setObjectName(_fromUtf8("vboxlayout5"))
self.uiNIOVDEListWidget = QtGui.QListWidget(self.uiNIOVDEListGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.uiNIOVDEListGroupBox = QtWidgets.QGroupBox(self.NIOVDETab)
self.uiNIOVDEListGroupBox.setObjectName("uiNIOVDEListGroupBox")
self.vboxlayout5 = QtWidgets.QVBoxLayout(self.uiNIOVDEListGroupBox)
self.vboxlayout5.setObjectName("vboxlayout5")
self.uiNIOVDEListWidget = QtWidgets.QListWidget(self.uiNIOVDEListGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIOVDEListWidget.sizePolicy().hasHeightForWidth())
self.uiNIOVDEListWidget.setSizePolicy(sizePolicy)
self.uiNIOVDEListWidget.setObjectName(_fromUtf8("uiNIOVDEListWidget"))
self.uiNIOVDEListWidget.setObjectName("uiNIOVDEListWidget")
self.vboxlayout5.addWidget(self.uiNIOVDEListWidget)
self.gridlayout9.addWidget(self.uiNIOVDEListGroupBox, 0, 2, 3, 1)
self.uiAddNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
self.uiAddNIOVDEPushButton.setObjectName(_fromUtf8("uiAddNIOVDEPushButton"))
self.uiAddNIOVDEPushButton = QtWidgets.QPushButton(self.NIOVDETab)
self.uiAddNIOVDEPushButton.setObjectName("uiAddNIOVDEPushButton")
self.gridlayout9.addWidget(self.uiAddNIOVDEPushButton, 1, 0, 1, 1)
self.uiDeleteNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
self.uiDeleteNIOVDEPushButton = QtWidgets.QPushButton(self.NIOVDETab)
self.uiDeleteNIOVDEPushButton.setEnabled(False)
self.uiDeleteNIOVDEPushButton.setObjectName(_fromUtf8("uiDeleteNIOVDEPushButton"))
self.uiDeleteNIOVDEPushButton.setObjectName("uiDeleteNIOVDEPushButton")
self.gridlayout9.addWidget(self.uiDeleteNIOVDEPushButton, 1, 1, 1, 1)
spacerItem7 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
spacerItem7 = QtWidgets.QSpacerItem(161, 201, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
self.gridlayout9.addItem(spacerItem7, 2, 0, 2, 2)
spacerItem8 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem8 = QtWidgets.QSpacerItem(196, 132, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout9.addItem(spacerItem8, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOVDETab, _fromUtf8(""))
self.NIONullTab = QtGui.QWidget()
self.NIONullTab.setObjectName(_fromUtf8("NIONullTab"))
self.gridlayout13 = QtGui.QGridLayout(self.NIONullTab)
self.gridlayout13.setObjectName(_fromUtf8("gridlayout13"))
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.NIONullTab)
self.uiNIONullSettingsGroupBox.setObjectName(_fromUtf8("uiNIONullSettingsGroupBox"))
self.gridlayout14 = QtGui.QGridLayout(self.uiNIONullSettingsGroupBox)
self.gridlayout14.setObjectName(_fromUtf8("gridlayout14"))
self.uiNIONullIdentifierLabel = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
self.uiNIONullIdentifierLabel.setObjectName(_fromUtf8("uiNIONullIdentifierLabel"))
self.uiNIOsTabWidget.addTab(self.NIOVDETab, "")
self.NIONullTab = QtWidgets.QWidget()
self.NIONullTab.setObjectName("NIONullTab")
self.gridlayout13 = QtWidgets.QGridLayout(self.NIONullTab)
self.gridlayout13.setObjectName("gridlayout13")
self.uiNIONullSettingsGroupBox = QtWidgets.QGroupBox(self.NIONullTab)
self.uiNIONullSettingsGroupBox.setObjectName("uiNIONullSettingsGroupBox")
self.gridlayout14 = QtWidgets.QGridLayout(self.uiNIONullSettingsGroupBox)
self.gridlayout14.setObjectName("gridlayout14")
self.uiNIONullIdentifierLabel = QtWidgets.QLabel(self.uiNIONullSettingsGroupBox)
self.uiNIONullIdentifierLabel.setObjectName("uiNIONullIdentifierLabel")
self.gridlayout14.addWidget(self.uiNIONullIdentifierLabel, 0, 0, 1, 1)
self.uiNIONullIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONullSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiNIONullIdentiferLineEdit = QtWidgets.QLineEdit(self.uiNIONullSettingsGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONullIdentiferLineEdit.sizePolicy().hasHeightForWidth())
self.uiNIONullIdentiferLineEdit.setSizePolicy(sizePolicy)
self.uiNIONullIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONullIdentiferLineEdit"))
self.uiNIONullIdentiferLineEdit.setObjectName("uiNIONullIdentiferLineEdit")
self.gridlayout14.addWidget(self.uiNIONullIdentiferLineEdit, 1, 0, 1, 1)
self.gridlayout13.addWidget(self.uiNIONullSettingsGroupBox, 0, 0, 1, 2)
self.uiNIONullListGroupBox = QtGui.QGroupBox(self.NIONullTab)
self.uiNIONullListGroupBox.setObjectName(_fromUtf8("uiNIONullListGroupBox"))
self.vboxlayout6 = QtGui.QVBoxLayout(self.uiNIONullListGroupBox)
self.vboxlayout6.setObjectName(_fromUtf8("vboxlayout6"))
self.uiNIONullListWidget = QtGui.QListWidget(self.uiNIONullListGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.uiNIONullListGroupBox = QtWidgets.QGroupBox(self.NIONullTab)
self.uiNIONullListGroupBox.setObjectName("uiNIONullListGroupBox")
self.vboxlayout6 = QtWidgets.QVBoxLayout(self.uiNIONullListGroupBox)
self.vboxlayout6.setObjectName("vboxlayout6")
self.uiNIONullListWidget = QtWidgets.QListWidget(self.uiNIONullListGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONullListWidget.sizePolicy().hasHeightForWidth())
self.uiNIONullListWidget.setSizePolicy(sizePolicy)
self.uiNIONullListWidget.setObjectName(_fromUtf8("uiNIONullListWidget"))
self.uiNIONullListWidget.setObjectName("uiNIONullListWidget")
self.vboxlayout6.addWidget(self.uiNIONullListWidget)
self.gridlayout13.addWidget(self.uiNIONullListGroupBox, 0, 2, 3, 1)
self.uiAddNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
self.uiAddNIONullPushButton.setObjectName(_fromUtf8("uiAddNIONullPushButton"))
self.uiAddNIONullPushButton = QtWidgets.QPushButton(self.NIONullTab)
self.uiAddNIONullPushButton.setObjectName("uiAddNIONullPushButton")
self.gridlayout13.addWidget(self.uiAddNIONullPushButton, 1, 0, 1, 1)
self.uiDeleteNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
self.uiDeleteNIONullPushButton = QtWidgets.QPushButton(self.NIONullTab)
self.uiDeleteNIONullPushButton.setEnabled(False)
self.uiDeleteNIONullPushButton.setObjectName(_fromUtf8("uiDeleteNIONullPushButton"))
self.uiDeleteNIONullPushButton.setObjectName("uiDeleteNIONullPushButton")
self.gridlayout13.addWidget(self.uiDeleteNIONullPushButton, 1, 1, 1, 1)
spacerItem9 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem9 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem9, 2, 0, 2, 2)
spacerItem10 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem10 = QtWidgets.QSpacerItem(20, 181, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem10, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIONullTab, _fromUtf8(""))
self.MiscTab = QtGui.QWidget()
self.MiscTab.setObjectName(_fromUtf8("MiscTab"))
self.gridLayout = QtGui.QGridLayout(self.MiscTab)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiNameLabel = QtGui.QLabel(self.MiscTab)
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
self.uiNIOsTabWidget.addTab(self.NIONullTab, "")
self.MiscTab = QtWidgets.QWidget()
self.MiscTab.setObjectName("MiscTab")
self.gridLayout = QtWidgets.QGridLayout(self.MiscTab)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.MiscTab)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtGui.QLineEdit(self.MiscTab)
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
self.uiNameLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
spacerItem11 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem11 = QtWidgets.QSpacerItem(20, 399, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem11, 1, 1, 1, 1)
self.uiNIOsTabWidget.addTab(self.MiscTab, _fromUtf8(""))
self.uiNIOsTabWidget.addTab(self.MiscTab, "")
self.vboxlayout.addWidget(self.uiNIOsTabWidget)
self.retranslateUi(cloudConfigPageWidget)
@@ -420,53 +407,53 @@ class Ui_cloudConfigPageWidget(object):
QtCore.QMetaObject.connectSlotsByName(cloudConfigPageWidget)
def retranslateUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setWindowTitle(_translate("cloudConfigPageWidget", "Cloud configuration", None))
self.uiGenericEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Generic Ethernet NIO (Administrator or root access required)", None))
self.uiAddGenericEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteGenericEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiLinuxEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Linux Ethernet NIO (Linux only, root access required)", None))
self.uiAddLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOEthernetTab), _translate("cloudConfigPageWidget", "Ethernet", None))
self.uiNIONATSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiNIONATIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
self.uiNIONATListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONATTab), _translate("cloudConfigPageWidget", "NAT", None))
self.uiNIOUDPSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiLocalPortLabel.setText(_translate("cloudConfigPageWidget", "Local port:", None))
self.uiRemoteHostLabel.setText(_translate("cloudConfigPageWidget", "Remote host:", None))
self.uiRemoteHostLineEdit.setText(_translate("cloudConfigPageWidget", "127.0.0.1", None))
self.uiRemotePortLabel.setText(_translate("cloudConfigPageWidget", "Remote port:", None))
self.uiNIOUDPListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUDPTab), _translate("cloudConfigPageWidget", "UDP", None))
self.uiNIOTAPGroupBox.setTitle(_translate("cloudConfigPageWidget", "TAP interface (require root access)", None))
self.uiAddNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOTAPTab), _translate("cloudConfigPageWidget", "TAP", None))
self.uiNIOUNIXSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiLocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
self.uiRemoteFileLabel.setText(_translate("cloudConfigPageWidget", "Remote file:", None))
self.uiNIOUNIXListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUnixTab), _translate("cloudConfigPageWidget", "UNIX", None))
self.uiNIOVDESettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiVDEControlFileLabel.setText(_translate("cloudConfigPageWidget", "Control file:", None))
self.uiVDELocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
self.uiNIOVDEListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOVDETab), _translate("cloudConfigPageWidget", "VDE", None))
self.uiNIONullSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiNIONullIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
self.uiNIONullListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONullTab), _translate("cloudConfigPageWidget", "NULL", None))
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc.", None))
_translate = QtCore.QCoreApplication.translate
cloudConfigPageWidget.setWindowTitle(_translate("cloudConfigPageWidget", "Cloud configuration"))
self.uiGenericEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Generic Ethernet NIO (Administrator or root access required)"))
self.uiAddGenericEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteGenericEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiLinuxEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Linux Ethernet NIO (Linux only, root access required)"))
self.uiAddLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOEthernetTab), _translate("cloudConfigPageWidget", "Ethernet"))
self.uiNIONATSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings"))
self.uiNIONATIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Local identifier:"))
self.uiNIONATListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs"))
self.uiAddNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONATTab), _translate("cloudConfigPageWidget", "NAT"))
self.uiNIOUDPSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings"))
self.uiLocalPortLabel.setText(_translate("cloudConfigPageWidget", "Local port:"))
self.uiRemoteHostLabel.setText(_translate("cloudConfigPageWidget", "Remote host:"))
self.uiRemoteHostLineEdit.setText(_translate("cloudConfigPageWidget", "127.0.0.1"))
self.uiRemotePortLabel.setText(_translate("cloudConfigPageWidget", "Remote port:"))
self.uiNIOUDPListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs"))
self.uiAddNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUDPTab), _translate("cloudConfigPageWidget", "UDP"))
self.uiNIOTAPGroupBox.setTitle(_translate("cloudConfigPageWidget", "TAP interface (require root access)"))
self.uiAddNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOTAPTab), _translate("cloudConfigPageWidget", "TAP"))
self.uiNIOUNIXSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings"))
self.uiLocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:"))
self.uiRemoteFileLabel.setText(_translate("cloudConfigPageWidget", "Remote file:"))
self.uiNIOUNIXListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs"))
self.uiAddNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUnixTab), _translate("cloudConfigPageWidget", "UNIX"))
self.uiNIOVDESettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings"))
self.uiVDEControlFileLabel.setText(_translate("cloudConfigPageWidget", "Control file:"))
self.uiVDELocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:"))
self.uiNIOVDEListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs"))
self.uiAddNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOVDETab), _translate("cloudConfigPageWidget", "VDE"))
self.uiNIONullSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings"))
self.uiNIONullIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Local identifier:"))
self.uiNIONullListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs"))
self.uiAddNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiDeleteNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))
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

@@ -19,13 +19,13 @@
Dynamips module implementation.
"""
import sys
import os
import shutil
import hashlib
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from gns3.image_manager import ImageManager
from gns3.local_server_config import LocalServerConfig
from ..module import Module
@@ -43,9 +43,9 @@ from .nodes.ethernet_switch import EthernetSwitch
from .nodes.ethernet_hub import EthernetHub
from .nodes.frame_relay_switch import FrameRelaySwitch
from .nodes.atm_switch import ATMSwitch
from .settings import DYNAMIPS_SETTINGS, DYNAMIPS_SETTING_TYPES
from .settings import IOS_ROUTER_SETTINGS, IOS_ROUTER_SETTING_TYPES
from .settings import PLATFORMS_DEFAULT_RAM
from .settings import DYNAMIPS_SETTINGS
from .settings import IOS_ROUTER_SETTINGS
from .settings import DEFAULT_IDLEPC
PLATFORM_TO_CLASS = {
"c1700": C1700,
@@ -68,61 +68,63 @@ class Dynamips(Module):
"""
def __init__(self):
Module.__init__(self)
super().__init__()
self._settings = {}
self._ios_routers = {}
self._nodes = []
self._ios_images_cache = {}
self.configChangedSlot()
def configChangedSlot(self):
# load the settings and IOS images.
self._loadSettings()
self._loadIOSRouters()
@staticmethod
def _findDynamips():
def getDefaultIdlePC(path):
"""
Finds the Dynamips path.
:return: path to Dynamips
Return the default IDLE PC for an image if the image
exists or None otherwise
"""
if not os.path.isfile(path):
path = os.path.join(ImageManager.instance().getDirectoryForType("DYNAMIPS"), path)
if not os.path.isfile(path):
return None
try:
md5sum = Dynamips._md5sum(path)
log.debug("Get idlePC for %s. md5sum %s", path, md5sum)
if md5sum in DEFAULT_IDLEPC:
log.debug("IDLEPC found for %s", path)
return DEFAULT_IDLEPC[md5sum]
except OSError:
return None
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
dynamips_path = os.path.join(os.getcwd(), "dynamips", "dynamips.exe")
elif sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
dynamips_path = os.path.join(os.getcwd(), "dynamips")
else:
dynamips_path = shutil.which("dynamips")
if dynamips_path is None:
return ""
return dynamips_path
@staticmethod
def _md5sum(path):
with open(path, "rb") as fd:
m = hashlib.md5()
while True:
data = fd.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
local_config = LocalConfig.instance()
# restore the Dynamips settings from QSettings (for backward compatibility)
legacy_settings = {}
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name in DYNAMIPS_SETTINGS.keys():
if settings.contains(name):
legacy_settings[name] = settings.value(name, type=DYNAMIPS_SETTING_TYPES[name])
settings.remove("")
settings.endGroup()
if legacy_settings:
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
self._settings = local_config.loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
if not os.path.exists(self._settings["dynamips_path"]):
self._settings["dynamips_path"] = self._findDynamips()
dynamips_path = shutil.which("dynamips")
if dynamips_path:
self._settings["dynamips_path"] = os.path.abspath(dynamips_path)
else:
self._settings["dynamips_path"] = ""
# keep the config file sync
self._saveSettings()
self._loadIOSRouters()
def _saveSettings(self):
"""
@@ -150,90 +152,30 @@ class Dynamips(Module):
Load the IOS routers from the persistent settings file.
"""
local_config = LocalConfig.instance()
# restore the Dynamips VM settings from QSettings (for backward compatibility)
ios_routers = []
# load the settings
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
# load the VMs
size = settings.beginReadArray("ios_router")
for index in range(0, size):
settings.setArrayIndex(index)
router = {}
for setting_name, default_value in IOS_ROUTER_SETTINGS.items():
router[setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if settings.contains(slot):
router[slot] = settings.value(slot, "")
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if settings.contains(wic):
router[wic] = settings.value(wic, "")
platform = router["platform"]
chassis = router["chassis"]
if platform == "c7200":
router["midplane"] = settings.value("midplane", "vxr")
router["npe"] = settings.value("npe", "npe-400")
router["slot0"] = settings.value("slot0", "C7200-IO-FE")
else:
router["iomem"] = 5
if platform in ("c3725", "c3725", "c2691"):
router["slot0"] = settings.value("slot0", "GT96100-FE")
elif platform == "c3600" and chassis == "3660":
router["slot0"] = settings.value("slot0", "Leopard-2FE")
elif platform == "c2600" and chassis == "2610":
router["slot0"] = settings.value("slot0", "C2600-MB-1E")
elif platform == "c2600" and chassis == "2611":
router["slot0"] = settings.value("slot0", "C2600-MB-2E")
elif platform == "c2600" and chassis in ("2620", "2610XM", "2620XM", "2650XM"):
router["slot0"] = settings.value("slot0", "C2600-MB-1FE")
elif platform == "c2600" and chassis in ("2621", "2611XM", "2621XM", "2651XM"):
router["slot0"] = settings.value("slot0", "C2600-MB-2FE")
elif platform == "c1700" and chassis in ("1720", "1721", "1750", "1751", "1760"):
router["slot0"] = settings.value("slot0", "C1700-MB-1FE")
elif platform == "c1700" and chassis in ("1751", "1760"):
router["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
ios_routers.append(router)
settings.endArray()
settings.remove("")
settings.endGroup()
if ios_routers:
local_config.saveSectionSettings(self.__class__.__name__, {"routers": ios_routers})
settings = local_config.settings()
settings = LocalConfig.instance().settings()
if "routers" in settings.get(self.__class__.__name__, {}):
for router in settings[self.__class__.__name__]["routers"]:
name = router.get("name")
server = router.get("server")
router["image"] = router.get("path", router["image"]) # for compatibility before version 1.3
router["image"] = router.get("path", router["image"]) # for backward compatibility before version 1.3
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers or not name or not server:
continue
router_settings = IOS_ROUTER_SETTINGS.copy()
router_settings.update(router)
# for backward compatibility before version 1.4
if "symbol" not in router_settings:
router_settings["symbol"] = router_settings["default_symbol"]
router_settings["symbol"] = router_settings["symbol"][:-11] + ".svg" if router_settings["symbol"].endswith("normal.svg") else router_settings["symbol"]
self._ios_routers[key] = router_settings
# keep things sync
self._saveIOSRouters()
def _saveIOSRouters(self):
"""
Saves the IOS routers to the persistent settings file.
"""
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, {"routers": list(self._ios_routers.values())})
self._settings["routers"] = list(self._ios_routers.values())
self._saveSettings()
def addNode(self, node):
"""
@@ -252,9 +194,11 @@ class Dynamips(Module):
"""
if node in self._nodes:
if "ram" in node.settings():
node.server().decreaseAllocatedRAM(node.settings()["ram"])
self._nodes.remove(node)
def iosRouters(self):
def VMs(self):
"""
Returns IOS routers settings.
@@ -263,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.
@@ -273,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
@@ -292,34 +241,6 @@ class Dynamips(Module):
self._settings.update(settings)
self._saveSettings()
def allocateServer(self, node_class, use_cloud=False):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (HTTPClient instance)
"""
# allocate a server for the node
servers = Servers.instance()
if use_cloud:
from ...topology import Topology
topology = Topology.instance()
top_instance = topology.anyInstance()
server = servers.getCloudServer(top_instance.host, top_instance.port, top_instance.ssl_ca_file)
else:
if self._settings["use_local_server"]:
# use the local server
server = servers.localServer()
else:
# pick up a remote server (round-robin method)
server = next(iter(servers))
if not server:
raise ModuleError("No remote server is configured")
return server
def createNode(self, node_class, server, project):
"""
Creates a new node.
@@ -360,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:
@@ -377,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()
@@ -393,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):
"""
@@ -441,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,
@@ -453,11 +373,10 @@ class Dynamips(Module):
candidate_ios_images[ios_router["image"]] = ios_router
if candidate_ios_images:
selection, ok = QtGui.QInputDialog.getItem(mainwindow,
"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)
selection, ok = QtWidgets.QInputDialog.getItem(mainwindow,
"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"]
@@ -465,9 +384,11 @@ class Dynamips(Module):
return alternative_image
# no registered IOS image is used, let's just ask for an IOS image path
QtGui.QMessageBox.critical(mainwindow, "IOS image", "Could not find the {} IOS image \nPlease select a similar IOS image!".format(image))
msg = "Could not find the {} IOS image \nPlease select a similar IOS image!".format(image)
log.error(msg)
QtWidgets.QMessageBox.critical(mainwindow, "IOS image", msg)
from .pages.ios_router_preferences_page import IOSRouterPreferencesPage
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow, None)
if image_path:
alternative_image["image"] = image_path
self._ios_images_cache[image] = alternative_image
@@ -501,22 +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"]:
# pick up a remote server (round-robin method)
remote_server = next(iter(Servers.instance()))
if remote_server:
server = "{}:{}".format(remote_server.host, remote_server.port)
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(),
"default_symbol": node_class.defaultSymbol(),
"hover_symbol": node_class.hoverSymbol()}
"symbol": node_class.defaultSymbol(),
"builtin": True}
)
for ios_router in self._ios_routers.values():
@@ -524,9 +437,9 @@ class Dynamips(Module):
nodes.append(
{"class": node_class.__name__,
"name": ios_router["name"],
"ram": ios_router["ram"],
"server": ios_router["server"],
"default_symbol": ios_router["default_symbol"],
"hover_symbol": ios_router["hover_symbol"],
"symbol": ios_router["symbol"],
"categories": [ios_router["category"]]}
)

View File

@@ -19,21 +19,19 @@
Wizard for IOS routers.
"""
import sys
import os
import re
import hashlib
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.servers import Servers
from gns3.node import Node
from gns3.utils.run_in_terminal import RunInTerminal
from gns3.utils.get_resource import get_resource
from gns3.utils.get_default_base_config import get_default_base_config
from gns3.dialogs.vm_with_images_wizard import VMWithImagesWizard
from ....settings import ENABLE_CLOUD
from ..ui.ios_router_wizard_ui import Ui_IOSRouterWizard
from ..settings import PLATFORMS_DEFAULT_RAM, PLATFORMS_DEFAULT_NVRAM, DEFAULT_IDLEPC, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
from ..settings import PLATFORMS_DEFAULT_RAM, PLATFORMS_DEFAULT_NVRAM, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
from .. import Dynamips
from ..nodes.c1700 import C1700
from ..nodes.c2600 import C2600
@@ -57,7 +55,7 @@ import logging
log = logging.getLogger(__name__)
class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
"""
Wizard to create an IOS router.
@@ -68,17 +66,9 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
def __init__(self, ios_routers, parent):
QtGui.QWizard.__init__(self, parent)
self.setupUi(self)
self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/router.normal.svg"))
self.setWizardStyle(QtGui.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
# we want to see the cancel button on OSX
self.setOptions(QtGui.QWizard.NoDefaultButton)
super().__init__(ios_routers, Dynamips.instance().settings()["use_local_server"], parent)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/router.svg"))
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
self.uiIOSImageToolButton.clicked.connect(self._iosImageBrowserSlot)
self.uiTestIOSImagePushButton.clicked.connect(self._testIOSImageSlot)
self.uiIdlePCFinderPushButton.clicked.connect(self._idlePCFinderSlot)
self.uiEtherSwitchCheckBox.stateChanged.connect(self._etherSwitchSlot)
@@ -118,38 +108,43 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
1: self.uiWic1comboBox,
2: self.uiWic2comboBox}
self._ios_routers = ios_routers
from ..pages.ios_router_preferences_page import IOSRouterPreferencesPage
self.addImageSelector(self.uiIOSExistingImageRadioButton, self.uiIOSImageListComboBox, self.uiIOSImageLineEdit, self.uiIOSImageToolButton, IOSRouterPreferencesPage.getIOSImage)
if Dynamips.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
def _prefillPlatform(self):
"""
Slot for when the remote server radio button is toggled.
:param checked: either the button is checked or not
Try to guess the platform based on image name
"""
# try to guess the platform
image = os.path.basename(self.uiIOSImageLineEdit.text())
match = re.match("^(c[0-9]+)p?\\-\w+", image.lower())
if not match:
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return
if checked:
self.uiRemoteServersGroupBox.setEnabled(True)
else:
self.uiRemoteServersGroupBox.setEnabled(False)
detected_platform = match.group(1)
detected_chassis = ""
# IOS images for the 3600 platform start with the chassis name (c3620 etc.)
for platform, chassis in CHASSIS.items():
if detected_platform[1:] in chassis:
detected_chassis = detected_platform[1:]
detected_platform = platform
break
def _loadBalanceToggledSlot(self, checked):
"""
Slot for when the load balance checkbox is toggled.
if detected_platform not in PLATFORMS_DEFAULT_RAM:
QtWidgets.QMessageBox.warning(self, "IOS image", "This IOS image is for the {} platform/chassis and is not supported by this application!".format(detected_platform))
return
:param checked: either the box is checked or not
"""
if image.lower().startswith("c7200p"):
QtWidgets.QMessageBox.warning(self, "IOS image", "This IOS image is for c7200 PowerPC routers and is not recommended. Please use an IOS image that do not start with c7200p.")
if checked:
self.uiRemoteServersComboBox.setEnabled(False)
else:
self.uiRemoteServersComboBox.setEnabled(True)
index = self.uiPlatformComboBox.findText(detected_platform)
if index != -1:
self.uiPlatformComboBox.setCurrentIndex(index)
index = self.uiChassisComboBox.findText(detected_chassis)
if index != -1:
self.uiChassisComboBox.setCurrentIndex(index)
def _platformChangedSlot(self, platform):
"""
@@ -177,7 +172,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
ios_image = self.uiIOSImageLineEdit.text()
dynamips = os.path.realpath(Dynamips.instance().settings()["image"])
if not os.path.exists(dynamips):
QtGui.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
QtWidgets.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
return
command = '"{path}" -P {platform} -r {ram} "{ios_image}"'.format(path=dynamips,
platform=platform[1:],
@@ -186,7 +181,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
try:
RunInTerminal(command)
except OSError as e:
QtGui.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
QtWidgets.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
def _idlePCValidateSlot(self):
"""
@@ -257,7 +252,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
:param message: error message from the server.
"""
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Could not create IOS router: {}".format(message))
QtWidgets.QMessageBox.critical(self, "Idle-PC finder", "Could not create IOS router: {}".format(message))
def _computeAutoIdlepcCallback(self, result, error=False, **kwargs):
"""
@@ -271,11 +266,11 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self._router.delete()
self._router = None
if error:
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: {}".format(result["message"]))
QtWidgets.QMessageBox.critical(self, "Idle-PC finder", "Error: {}".format(result["message"]))
else:
idlepc = result["idlepc"]
self.uiIdlepcLineEdit.setText(idlepc)
QtGui.QMessageBox.information(self, "Idle-PC finder", "Idle-PC value {} has been found suitable for your IOS image".format(idlepc))
QtWidgets.QMessageBox.information(self, "Idle-PC finder", "Idle-PC value {} has been found suitable for your IOS image".format(idlepc))
def _iosImageBrowserSlot(self):
"""
@@ -283,7 +278,8 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
"""
from ..pages.ios_router_preferences_page import IOSRouterPreferencesPage
path = IOSRouterPreferencesPage.getIOSImage(self)
server = Servers.instance().getServerFromString(self.getSettings()["server"])
path = IOSRouterPreferencesPage.getIOSImage(self, server)
if not path:
return
self.uiIOSImageLineEdit.clear()
@@ -293,7 +289,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
image = os.path.basename(path)
match = re.match("^(c[0-9]+)p?\\-\w+", image.lower())
if not match:
QtGui.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return
detected_platform = match.group(1)
@@ -306,11 +302,11 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
break
if detected_platform not in PLATFORMS_DEFAULT_RAM:
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the {} platform/chassis and is not supported by this application!".format(detected_platform))
QtWidgets.QMessageBox.warning(self, "IOS image", "This IOS image is for the {} platform/chassis and is not supported by this application!".format(detected_platform))
return
if image.lower().startswith("c7200p"):
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for c7200 PowerPC routers and is not recommended. Please use an IOS image that do not start with c7200p.")
QtWidgets.QMessageBox.warning(self, "IOS image", "This IOS image is for c7200 PowerPC routers and is not recommended. Please use an IOS image that do not start with c7200p.")
index = self.uiPlatformComboBox.findText(detected_platform)
if index != -1:
@@ -324,7 +320,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if self._router:
self._router.delete()
QtGui.QWizard.done(self, result)
super().done(result)
def _populateAdapters(self, platform, chassis):
"""
@@ -363,25 +359,14 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
wic_list = list(wics)
self._widget_wics[wic_number].addItems([""] + wic_list)
def _md5sum(self, filename):
with open(filename, "rb") as fd:
m = hashlib.md5()
while True:
data = fd.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
def initializePage(self, page_id):
if self.page(page_id) == self.uiServerWizardPage:
self.uiRemoteServersComboBox.clear()
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem("{}:{}".format(server.host, server.port), server)
super().initializePage(page_id)
if self.page(page_id) == self.uiIOSImageWizardPage:
self.loadImagesList("/dynamips/vms")
elif self.page(page_id) == self.uiNamePlatformWizardPage:
self._prefillPlatform()
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())
ios_image = self.uiIOSImageLineEdit.text()
self.setWindowTitle("New IOS router - {}".format(os.path.basename(ios_image)))
@@ -412,37 +397,26 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
elif self.page(page_id) == self.uiIdlePCWizardPage:
path = self.uiIOSImageLineEdit.text()
if os.path.isfile(path):
try:
md5sum = self._md5sum(path)
if md5sum in DEFAULT_IDLEPC:
self.uiIdlepcLineEdit.setText(DEFAULT_IDLEPC[md5sum])
except OSError:
pass
idle_pc = Dynamips.getDefaultIdlePC(path)
if idle_pc is not None:
self.uiIdlepcLineEdit.setText(idle_pc)
def validateCurrentPage(self):
"""
Validates the IOS name and checks validation state for Idle-PC value
"""
if self.currentPage() == self.uiNamePlatformWizardPage:
name = self.uiNameLineEdit.text()
for ios_router in self._ios_routers.values():
if ios_router["name"] == name:
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiMemoryWizardPage and self.uiPlatformComboBox.currentText() == "c7200":
if self.uiRamSpinBox.value() > 512:
QtGui.QMessageBox.critical(self, "c7200 RAM requirement", "c7200 routers with NPE-400 are limited to 512MB of RAM")
QtWidgets.QMessageBox.critical(self, "c7200 RAM requirement", "c7200 routers with NPE-400 are limited to 512MB of RAM")
return False
if self.currentPage() == self.uiIdlePCWizardPage:
if not self._idle_valid:
idle_pc = self.uiIdlepcLineEdit.text()
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
return False
if self.currentPage() == self.uiServerWizardPage and self.uiRemoteRadioButton.isChecked():
if not Servers.instance().remoteServers():
QtGui.QMessageBox.critical(self, "Remote server", "There is no remote server registered in Dynamips preferences")
QtWidgets.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
return False
return True
@@ -454,16 +428,15 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
"""
image = self.uiIOSImageLineEdit.text()
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
if self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiRemoteRadioButton.isChecked():
if self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
server = "{}:{}".format(server.host, server.port)
server = "load-balance"
else:
server = self.uiRemoteServersComboBox.currentText()
else: # Cloud is selected
server = "cloud"
elif self.uiVMRadioButton.isChecked():
server = "vm"
platform = self.uiPlatformComboBox.currentText()
settings = {
@@ -479,11 +452,13 @@ class IOSRouterWizard(QtGui.QWizard, 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["default_symbol"] = ":/symbols/multilayer_switch.normal.svg"
settings["hover_symbol"] = ":/symbols/multilayer_switch.selected.svg"
settings["symbol"] = ":/symbols/multilayer_switch.svg"
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat
settings["category"] = Node.switches
else:
settings["auto_delete_disks"] = True
image_file = os.path.basename(image)
if image_file.lower().startswith("c7200p"):
@@ -510,4 +485,4 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if platform not in WIC_MATRIX:
# skip the WIC modules page if the platform doesn't support any.
return self.uiNetworkAdaptersWizardPage.nextId() + 1
return QtGui.QWizard.nextId(self)
return QtWidgets.QWizard.nextId(self)

View File

@@ -41,7 +41,7 @@ class ATMSwitch(Device):
def __init__(self, module, server, project):
Device.__init__(self, module, server, project)
super().__init__(module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ports = []
self._settings = {"name": "",
@@ -199,8 +199,8 @@ class ATMSwitch(Device):
""".format(name=self.name(),
id=self.id(),
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
host=self._server.host(),
port=self._server.port())
port_info = ""
mapping = re.compile(r"""^([0-9]*):([0-9]*):([0-9]*)$""")
@@ -333,7 +333,7 @@ class ATMSwitch(Device):
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -349,17 +349,7 @@ class ATMSwitch(Device):
:returns: symbol path (or resource).
"""
return ":/symbols/atm_switch.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/atm_switch.selected.svg"
return ":/symbols/atm_switch.svg"
@staticmethod
def symbolName():

View File

@@ -34,7 +34,7 @@ class C1700(Router):
def __init__(self, module, server, project, chassis="1720"):
Router.__init__(self, module, server, project, platform="c1700")
super().__init__(module, server, project, platform="c1700")
c1700_settings = {"ram": 128,
"nvram": 32,
"disk0": 0,

View File

@@ -46,7 +46,7 @@ class C2600(Router):
def __init__(self, module, server, project, chassis="2610"):
Router.__init__(self, module, server, project, platform="c2600")
super().__init__(module, server, project, platform="c2600")
c2600_settings = {"ram": 128,
"nvram": 128,
"disk0": 0,

View File

@@ -34,7 +34,7 @@ class C2691(Router):
def __init__(self, module, server, project):
Router.__init__(self, module, server, project, platform="c2691")
super().__init__(module, server, project, platform="c2691")
c2691_settings = {"ram": 192,
"nvram": 112,
"disk0": 16,

View File

@@ -34,7 +34,7 @@ class C3600(Router):
def __init__(self, module, server, project, chassis="3640"):
Router.__init__(self, module, server, project, platform="c3600")
super().__init__(module, server, project, platform="c3600")
c3600_settings = {"ram": 192,
"nvram": 128,
"disk0": 0,

View File

@@ -34,7 +34,7 @@ class C3725(Router):
def __init__(self, module, server, project):
Router.__init__(self, module, server, project, platform="c3725")
super().__init__(module, server, project, platform="c3725")
c3725_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,

View File

@@ -34,7 +34,7 @@ class C3745(Router):
def __init__(self, module, server, project):
Router.__init__(self, module, server, project, platform="c3745")
super().__init__(module, server, project, platform="c3745")
c3745_settings = {"ram": 256,
"nvram": 304,
"disk0": 16,

View File

@@ -34,7 +34,7 @@ class C7200(Router):
def __init__(self, module, server, project, npe="npe-400"):
Router.__init__(self, module, server, project, platform="c7200")
super().__init__(module, server, project, platform="c7200")
c7200_settings = {"ram": 512,
"nvram": 128,
"disk0": 64,

View File

@@ -21,6 +21,7 @@ Base class for Device classes.
from gns3.node import Node
from gns3.packet_capture import PacketCapture
import logging
log = logging.getLogger(__name__)
@@ -103,7 +104,7 @@ class Device(Node):
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
log.error("error while adding a NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
self.nio_cancel_signal.emit(self.id())
else:
@@ -170,13 +171,7 @@ class Device(Node):
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["pcap_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
PacketCapture.instance().startCapture(self, context["port"], result["pcap_file_path"])
def stopPacketCapture(self, port):
"""
@@ -205,7 +200,4 @@ class Device(Node):
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
PacketCapture.instance().stopCapture(self, context["port"])

View File

@@ -40,7 +40,7 @@ class EthernetHub(Device):
def __init__(self, module, server, project):
Device.__init__(self, module, server, project)
super().__init__(module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ports = []
self._settings = {"name": "",
@@ -101,7 +101,7 @@ class EthernetHub(Device):
ports = new_settings["ports"]
for port_number in ports:
if port_number not in ports_to_create:
ports_to_create.append(port_number)
ports_to_create.append(str(port_number))
for port in self._ports.copy():
if port.isFree():
@@ -190,8 +190,8 @@ class EthernetHub(Device):
""".format(name=self.name(),
id=self.id(),
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
host=self._server.host(),
port=self._server.port())
port_info = ""
for port in self._ports:
@@ -279,7 +279,7 @@ class EthernetHub(Device):
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -295,17 +295,7 @@ class EthernetHub(Device):
:returns: symbol path (or resource).
"""
return ":/symbols/hub.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/hub.selected.svg"
return ":/symbols/hub.svg"
@staticmethod
def symbolName():

View File

@@ -40,7 +40,7 @@ class EthernetSwitch(Device):
def __init__(self, module, server, project):
Device.__init__(self, module, server, project)
super().__init__(module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ports = []
self._settings = {"name": "",
@@ -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(
@@ -208,8 +210,8 @@ class EthernetSwitch(Device):
""".format(name=self.name(),
id=self.id(),
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
host=self._server.host(),
port=self._server.port())
port_info = ""
for port in self._ports:
@@ -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)
@@ -311,7 +319,7 @@ class EthernetSwitch(Device):
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -327,17 +335,7 @@ class EthernetSwitch(Device):
:returns: symbol path (or resource).
"""
return ":/symbols/ethernet_switch.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/ethernet_switch.selected.svg"
return ":/symbols/ethernet_switch.svg"
@staticmethod
def symbolName():

View File

@@ -35,7 +35,7 @@ class EtherSwitchRouter(Router):
"""
def __init__(self, module, server, project):
Router.__init__(self, module, server, project, platform="c3725")
super().__init__(module, server, project, platform="c3725")
self._etherswitch_settings = {"ram": 128,
"nvram": 304,
@@ -56,17 +56,7 @@ class EtherSwitchRouter(Router):
:returns: symbol path (or resource).
"""
return ":/symbols/multilayer_switch.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/multilayer_switch.selected.svg"
return ":/symbols/multilayer_switch.svg"
@staticmethod
def categories():

View File

@@ -40,7 +40,7 @@ class FrameRelaySwitch(Device):
def __init__(self, module, server, project):
Device.__init__(self, module, server, project)
super().__init__(module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ports = []
self._settings = {"name": "",
@@ -201,8 +201,8 @@ class FrameRelaySwitch(Device):
""".format(name=self.name(),
id=self.id(),
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
host=self._server.host(),
port=self._server.port())
port_info = ""
for port in self._ports:
@@ -315,7 +315,7 @@ class FrameRelaySwitch(Device):
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -331,17 +331,7 @@ class FrameRelaySwitch(Device):
:returns: symbol path (or resource).
"""
return ":/symbols/frame_relay_switch.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/frame_relay_switch.selected.svg"
return ":/symbols/frame_relay_switch.svg"
@staticmethod
def symbolName():

View File

@@ -25,8 +25,9 @@ import re
from gns3.vm import VM
from gns3.node import Node
from gns3.ports.port import Port
from gns3.servers import Servers
from gns3.packet_capture import PacketCapture
from gns3.utils.normalize_filename import normalize_filename
from gns3.image_manager import ImageManager
from ..settings import PLATFORMS_DEFAULT_RAM
from ..adapters import ADAPTER_MATRIX
@@ -51,13 +52,13 @@ class Router(VM):
def __init__(self, module, server, project, platform="c7200"):
VM.__init__(self, module, server, project)
super().__init__(module, server, project)
log.info("Router {} is being created".format(platform))
self._ports = []
self._dynamips_id = None
self._settings = {"name": "",
"platform": platform,
"image": "",
"image_md5sum": "",
"startup_config": "",
"private_config": "",
"ram": 128,
@@ -71,6 +72,7 @@ class Router(VM):
"exec_area": 64,
"disk0": 0,
"disk1": 0,
"auto_delete_disks": False,
"console": None,
"aux": None,
"mac_addr": None,
@@ -216,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.
@@ -230,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")
@@ -256,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)
@@ -281,23 +281,11 @@ class Router(VM):
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
if not super()._setupCallback(result, error=error, **kwargs):
return
self._vm_id = result["vm_id"]
self._dynamips_id = result["dynamips_id"]
# update the settings using the defaults sent by the server
for name, value in result.items():
if name in self._settings and self._settings[name] != value:
log.info("Router {} setting up and updating {} from '{}' to '{}'".format(self.name(),
name,
self._settings[name],
value))
self._settings[name] = value
# create the ports on the client side
self._insertAdapters(self._settings)
@@ -309,6 +297,10 @@ class Router(VM):
self.created_signal.emit(self.id())
self._module.addNode(self)
# The image is missing on remote server
if "image_md5sum" not in result or result["image_md5sum"] is None or len(result["image_md5sum"]) == 0:
ImageManager.instance().addMissingImage(result["image"], self._server, "DYNAMIPS")
def update(self, new_settings):
"""
Updates the settings for this router.
@@ -322,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:
@@ -350,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():
@@ -453,13 +442,7 @@ class Router(VM):
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["pcap_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
PacketCapture.instance().startCapture(self, context["port"], result["pcap_file_path"])
def stopPacketCapture(self, port):
"""
@@ -488,10 +471,7 @@ class Router(VM):
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
PacketCapture.instance().stopCapture(self, context["port"])
def computeIdlepcs(self, callback):
"""
@@ -502,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):
"""
@@ -513,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):
"""
@@ -658,8 +640,8 @@ class Router(VM):
specific_info=router_specific_info,
ram=self._settings["ram"],
nvram=self._settings["nvram"],
host=self._server.host,
port=self._server.port,
host=self._server.host(),
port=self._server.port(),
console=self._settings["console"],
aux=self._settings["aux"],
image_name=os.path.basename(self._settings["image"]),
@@ -679,36 +661,17 @@ 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 _imageFilesDir(self):
"""
Returns the location of IOS images.
"""
servers = Servers.instance()
local_server = servers.localServerSettings()
return os.path.join(local_server["images_path"], "IOS")
def load(self, node_info):
"""
Loads a router representation
@@ -717,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:
@@ -729,11 +694,11 @@ 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
updated_image_path = os.path.join(self._imageFilesDir(), image)
updated_image_path = os.path.join(ImageManager.instance().getDirectoryForType("DYNAMIPS"), image)
if os.path.isfile(updated_image_path):
image = updated_image_path
elif not os.path.isfile(image):
@@ -746,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
@@ -905,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 = {}
@@ -967,7 +909,7 @@ class Router(VM):
def configPage(self):
"""
Returns the configuration page widget to be used by the node configurator.
Returns the configuration page widget to be used by the node properties dialog.
:returns: QWidget object
"""
@@ -1000,17 +942,7 @@ class Router(VM):
:returns: symbol path (or resource).
"""
return ":/symbols/router.normal.svg"
@staticmethod
def hoverSymbol():
"""
Returns the symbol to use when the router is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/router.selected.svg"
return ":/symbols/router.svg"
@staticmethod
def categories():

View File

@@ -20,11 +20,11 @@ Configuration page for Dynamips ATM bridges.
"""
import re
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtWidgets
from ..ui.atm_bridge_configuration_page_ui import Ui_atmBridgeConfigPageWidget
class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
class ATMBridgeConfigurationPage(QtWidgets.QWidget, Ui_atmBridgeConfigPageWidget):
"""
QWidget configuration page for ATM bridges.
@@ -32,7 +32,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._mapping = {}
@@ -91,7 +91,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
atm_vci = self.uiATMVCISpinBox.value()
if ethernet_port == atm_port:
QtGui.QMessageBox.critical(self, self._node.name(), "Same source and destination ports")
QtWidgets.QMessageBox.critical(self, self._node.name(), "Same source and destination ports")
return
destination = "{port}:{vpi}:{vci}".format(port=atm_port,
@@ -99,10 +99,10 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
vci=atm_vci)
if destination in self._mapping:
QtGui.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
QtWidgets.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
return
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, str(ethernet_port))
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -125,7 +125,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if (node_port.portNumber() == ethernet_port or node_port.portNumber() == atm_port) and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
return
del self.mapping[ethernet_port]
@@ -150,7 +150,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
self._node = node
for ethernet_port, destination in settings["mappings"].items():
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, ethernet_port)
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -172,7 +172,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "ATM bridge name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "ATM bridge name cannot be empty!")
else:
settings["name"] = name
else:

View File

@@ -20,11 +20,11 @@ Configuration page for Dynamips ATM switches.
"""
import re
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtWidgets
from ..ui.atm_switch_configuration_page_ui import Ui_atmSwitchConfigPageWidget
class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
class ATMSwitchConfigurationPage(QtWidgets.QWidget, Ui_atmSwitchConfigPageWidget):
"""
QWidget configuration page for ATM switches.
@@ -32,7 +32,7 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._mapping = {}
@@ -116,10 +116,10 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
destination = "{port}:{vpi}".format(port=destination_port, vpi=destination_vpi)
if source in self._mapping or destination in self._mapping:
QtGui.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
QtWidgets.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
return
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, source)
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -145,7 +145,7 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
node_ports = self._node.ports()
for node_port in node_ports:
if (node_port.portNumber() == source_port or node_port.portNumber() == destination_port) and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
return
del self._mapping[source]
@@ -170,7 +170,7 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
self._node = node
for source, destination in settings["mappings"].items():
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, source)
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -192,7 +192,7 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "ATM switch name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "ATM switch name cannot be empty!")
else:
settings["name"] = name
else:

View File

@@ -21,14 +21,14 @@ Configuration page for Dynamips preferences.
import os
import sys
from gns3.qt import QtGui
from gns3.servers import Servers
import shutil
from gns3.qt import QtWidgets
from .. import Dynamips
from ..ui.dynamips_preferences_page_ui import Ui_DynamipsPreferencesPageWidget
from ..settings import DYNAMIPS_SETTINGS
class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidget):
"""
QWidget preference page for Dynamips.
@@ -36,7 +36,7 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
# connect signals
@@ -53,15 +53,32 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
file_filter = ""
if sys.platform.startswith("win"):
file_filter = "Executable (*.exe);;All files (*.*)"
path = QtGui.QFileDialog.getOpenFileName(self, "Select Dynamips", ".", file_filter)
dynamips_path = shutil.which("dynamips")
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select Dynamips", dynamips_path, file_filter)
if not path:
return
if not os.access(path, os.X_OK):
QtGui.QMessageBox.critical(self, "Dynamips", "{} is not an executable".format(os.path.basename(path)))
return
if self._checkDynamipsPath(path):
self.uiDynamipsPathLineEdit.setText(path)
self.uiDynamipsPathLineEdit.setText(path)
def _checkDynamipsPath(self, path):
"""
Checks that the Dynamips path is valid.
:param path: Dynamips path
:returns: boolean
"""
if not os.path.exists(path):
QtWidgets.QMessageBox.critical(self, "Dynamips", '"{}" does not exist'.format(path))
return False
if not os.access(path, os.X_OK):
QtWidgets.QMessageBox.critical(self, "Dynamips", "{} is not an executable".format(os.path.basename(path)))
return False
return True
def _ghostIOSSupportSlot(self, state):
"""
@@ -128,11 +145,15 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
Saves the Dynamips preferences.
"""
new_settings = {}
new_settings["dynamips_path"] = self.uiDynamipsPathLineEdit.text()
new_settings["allocate_aux_console_ports"] = self.uiAllocateAuxConsolePortsCheckBox.isChecked()
new_settings["use_local_server"] = self.uiUseLocalServercheckBox.isChecked()
new_settings["ghost_ios_support"] = self.uiGhostIOSSupportCheckBox.isChecked()
new_settings["mmap_support"] = self.uiMmapSupportCheckBox.isChecked()
new_settings["sparse_memory_support"] = self.uiSparseMemorySupportCheckBox.isChecked()
dynamips_path = self.uiDynamipsPathLineEdit.text().strip()
if dynamips_path and self.uiUseLocalServercheckBox.isChecked() and not self._checkDynamipsPath(dynamips_path):
return
new_settings = {"dynamips_path": dynamips_path,
"allocate_aux_console_ports": self.uiAllocateAuxConsolePortsCheckBox.isChecked(),
"use_local_server": self.uiUseLocalServercheckBox.isChecked(),
"ghost_ios_support": self.uiGhostIOSSupportCheckBox.isChecked(),
"mmap_support": self.uiMmapSupportCheckBox.isChecked(),
"sparse_memory_support": self.uiSparseMemorySupportCheckBox.isChecked()}
Dynamips.instance().setSettings(new_settings)

View File

@@ -19,12 +19,12 @@
Configuration page for Dynamips Ethernet hubs.
"""
from gns3.qt import QtGui
from gns3.dialogs.node_configurator_dialog import ConfigurationError
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from ..ui.ethernet_hub_configuration_page_ui import Ui_ethernetHubConfigPageWidget
class EthernetHubConfigurationPage(QtGui.QWidget, Ui_ethernetHubConfigPageWidget):
class EthernetHubConfigurationPage(QtWidgets.QWidget, Ui_ethernetHubConfigPageWidget):
"""
QWidget configuration page for Ethernet hubs.
@@ -32,7 +32,7 @@ class EthernetHubConfigurationPage(QtGui.QWidget, Ui_ethernetHubConfigPageWidget
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
def loadSettings(self, settings, node, group=False):
@@ -66,7 +66,7 @@ class EthernetHubConfigurationPage(QtGui.QWidget, Ui_ethernetHubConfigPageWidget
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "Ethernet hub name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "Ethernet hub name cannot be empty!")
else:
settings["name"] = name
else:
@@ -80,7 +80,7 @@ class EthernetHubConfigurationPage(QtGui.QWidget, Ui_ethernetHubConfigPageWidget
for port in ports:
if not port.isFree() and port.portNumber() > nbports:
self.loadSettings(settings, node)
QtGui.QMessageBox.critical(self, node.name(), "A link is connected to port {}, please remove it first".format(port.name()))
QtWidgets.QMessageBox.critical(self, node.name(), "A link is connected to port {}, please remove it first".format(port.name()))
raise ConfigurationError()
settings["ports"] = []

View File

@@ -19,12 +19,12 @@
Configuration page for Dynamips Ethernet switches.
"""
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtWidgets
from ..utils.tree_widget_item import TreeWidgetItem
from ..ui.ethernet_switch_configuration_page_ui import Ui_ethernetSwitchConfigPageWidget
class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPageWidget):
class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfigPageWidget):
"""
QWidget configuration page for Ethernet switches.
@@ -32,7 +32,7 @@ class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPage
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._ports = {}
@@ -41,6 +41,7 @@ class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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)
@@ -114,7 +141,7 @@ class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPage
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.portNumber() == port and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
return
del self._ports[port]
self.uiPortsTreeWidget.takeTopLevelItem(self.uiPortsTreeWidget.indexOfTopLevelItem(item))
@@ -147,6 +174,7 @@ class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPage
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
@@ -168,7 +196,7 @@ class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPage
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "Ethernet switch name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "Ethernet switch name cannot be empty!")
else:
settings["name"] = name
else:

View File

@@ -19,11 +19,11 @@
Configuration page for Dynamips Frame Relay switches.
"""
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtWidgets
from ..ui.frame_relay_switch_configuration_page_ui import Ui_frameRelaySwitchConfigPageWidget
class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfigPageWidget):
class FrameRelaySwitchConfigurationPage(QtWidgets.QWidget, Ui_frameRelaySwitchConfigPageWidget):
"""
QWidget configuration page for Frame Relay switches.
@@ -31,7 +31,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._mapping = {}
@@ -82,17 +82,17 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
destination_dlci = self.uiDestinationDLCISpinBox.value()
if source_port == destination_port:
QtGui.QMessageBox.critical(self, self._node.name(), "Same source and destination ports")
QtWidgets.QMessageBox.critical(self, self._node.name(), "Same source and destination ports")
return
source = "{port}:{dlci}".format(port=source_port, dlci=source_dlci)
destination = "{port}:{dlci}".format(port=destination_port, dlci=destination_dlci)
if source in self._mapping or destination in self._mapping:
QtGui.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
QtWidgets.QMessageBox.critical(self, self._node.name(), "Mapping already defined")
return
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, source)
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -120,7 +120,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
node_ports = self._node.ports()
for node_port in node_ports:
if (node_port.portNumber() == source_port or node_port.portNumber() == destination_port) and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
QtWidgets.QMessageBox.critical(self, self._node.name(), "A link is connected to port {}, please remove it first".format(node_port.name()))
return
del self._mapping[source]
@@ -145,7 +145,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
self._node = node
for source, destination in settings["mappings"].items():
item = QtGui.QTreeWidgetItem(self.uiMappingTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiMappingTreeWidget)
item.setText(0, source)
item.setText(1, destination)
self.uiMappingTreeWidget.addTopLevelItem(item)
@@ -167,7 +167,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "Frame relay switch name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "Frame relay switch name cannot be empty!")
else:
settings["name"] = name
else:

View File

@@ -22,13 +22,16 @@ Configuration page for Dynamips IOS routers.
import os
import re
from gns3.qt import QtCore, QtGui
from gns3.dialogs.node_configurator_dialog import ConfigurationError
from gns3.servers import Servers
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node
from ..ui.ios_router_configuration_page_ui import Ui_iosRouterConfigPageWidget
from ..settings import CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget):
"""
QWidget configuration page for IOS routers.
@@ -36,7 +39,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._widget_slots = {0: self.uiSlot0comboBox,
@@ -53,14 +56,20 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiStartupConfigToolButton.clicked.connect(self._startupConfigBrowserSlot)
self.uiPrivateConfigToolButton.clicked.connect(self._privateConfigBrowserSlot)
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
self.uiIOSImageToolButton.clicked.connect(self._iosImageBrowserSlot)
self._server = None
self._idle_valid = False
idle_pc_rgx = QtCore.QRegExp("^(0x[0-9a-fA-F]{8})?$")
validator = QtGui.QRegExpValidator(idle_pc_rgx, self)
self.uiIdlepcLineEdit.setValidator(validator)
self.uiIdlepcLineEdit.textChanged.connect(self._idlePCValidateSlot)
self.uiIdlepcLineEdit.textChanged.emit(self.uiIdlepcLineEdit.text())
self._default_configs_dir = Servers.instance().localServerSettings()["configs_path"]
# add the categories
for name, category in Node.defaultCategories().items():
self.uiCategoryComboBox.addItem(name, category)
def _idlePCValidateSlot(self):
"""
@@ -82,11 +91,11 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
def _iosImageBrowserSlot(self):
"""
Slot to open a file browser and select an IOU image.
Slot to open a file browser and select an IOS image.
"""
from ..pages.ios_router_preferences_page import IOSRouterPreferencesPage
path = IOSRouterPreferencesPage.getIOSImage(self)
path = IOSRouterPreferencesPage.getIOSImage(self, self._server)
if not path:
return
self.uiIOSImageLineEdit.clear()
@@ -96,7 +105,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
image = os.path.basename(path)
match = re.match("^(c[0-9]+)\\-\w+", image)
if not match:
QtGui.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
QtWidgets.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return
detected_platform = match.group(1)
@@ -112,23 +121,23 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
chassis = self.uiChassisTextLabel.text()
if detected_platform != platform:
QtGui.QMessageBox.warning(self, "IOS image", "Using an IOS image made for another platform will likely not work!")
QtWidgets.QMessageBox.warning(self, "IOS image", "Using an IOS image made for another platform will likely not work!")
if detected_chassis and chassis and detected_chassis != chassis:
QtGui.QMessageBox.warning(self, "IOS image", "Using an IOS image made for another chassis will likely not work!")
QtWidgets.QMessageBox.warning(self, "IOS image", "Using an IOS image made for another chassis will likely not work!")
def _startupConfigBrowserSlot(self):
"""
Slot to open a file browser and select a startup-config file.
"""
config_dir = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "base_configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a startup configuration", config_dir)
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select a startup configuration", self._default_configs_dir)
if not path:
return
self._default_configs_dir = os.path.dirname(path)
if not os.access(path, os.R_OK):
QtGui.QMessageBox.critical(self, "Startup configuration", "Cannot read {}".format(path))
QtWidgets.QMessageBox.critical(self, "Startup configuration", "Cannot read {}".format(path))
return
self.uiStartupConfigLineEdit.clear()
@@ -139,18 +148,31 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
Slot to open a file browser and select a private-config file.
"""
config_dir = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "base_configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a private configuration", config_dir)
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select a private configuration", self._default_configs_dir)
if not path:
return
self._default_configs_dir = os.path.dirname(path)
if not os.access(path, os.R_OK):
QtGui.QMessageBox.critical(self, "Private configuration", "Cannot read {}".format(path))
QtWidgets.QMessageBox.critical(self, "Private configuration", "Cannot read {}".format(path))
return
self.uiPrivateConfigLineEdit.clear()
self.uiPrivateConfigLineEdit.setText(path)
def _symbolBrowserSlot(self):
"""
Slot to open the symbol browser and select a new symbol.
"""
symbol_path = self.uiSymbolLineEdit.text()
dialog = SymbolSelectionDialog(self, symbol=symbol_path)
dialog.show()
if dialog.exec_():
new_symbol_path = dialog.getSymbol()
self.uiSymbolLineEdit.setText(new_symbol_path)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(new_symbol_path))
def _loadAdapterConfig(self, platform, chassis, settings):
"""
Loads the adapter and WIC configuration.
@@ -214,6 +236,11 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
:param group: indicates the settings apply to a group of routers
"""
if node:
self._server = node.server()
else:
self._server = Servers.instance().getServerFromString(settings["server"])
if not group:
self.uiNameLineEdit.setText(settings["name"])
@@ -234,11 +261,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
# load the MAC address setting
self.uiBaseMACLineEdit.setInputMask("HHHH.HHHH.HHHH;_")
# regexp = QtCore.QRegExp("([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}")
# validator = QtGui.QRegExpValidator(regexp)
# self.uiBaseMACLineEdit.setValidator(validator)
if settings["mac_addr"]:
self.uiBaseMACLineEdit.setText(settings["mac_addr"])
else:
@@ -259,17 +281,43 @@ class IOSRouterConfigurationPage(QtGui.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"])
# load the private-config
self.uiPrivateConfigLineEdit.setText(settings["private_config"])
# load the symbol
self.uiSymbolLineEdit.setText(settings["symbol"])
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
# load the category
index = self.uiCategoryComboBox.findData(settings["category"])
if index != -1:
self.uiCategoryComboBox.setCurrentIndex(index)
else:
self.uiDefaultNameFormatLabel.hide()
self.uiDefaultNameFormatLineEdit.hide()
self.uiStartupConfigLabel.hide()
self.uiStartupConfigLineEdit.hide()
self.uiStartupConfigToolButton.hide()
self.uiPrivateConfigLabel.hide()
self.uiPrivateConfigLineEdit.hide()
self.uiPrivateConfigToolButton.hide()
self.uiSymbolLabel.hide()
self.uiSymbolLineEdit.hide()
self.uiSymbolToolButton.hide()
self.uiCategoryComboBox.hide()
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
# show the platform and chassis if applicable
platform = settings["platform"]
@@ -338,6 +386,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiNvramSpinBox.setValue(settings["nvram"])
self.uiDisk0SpinBox.setValue(settings["disk0"])
self.uiDisk1SpinBox.setValue(settings["disk1"])
self.uiAutoDeleteCheckBox.setChecked(settings["auto_delete_disks"])
# load all the slots with configured adapters
self._loadAdapterConfig(platform, chassis, settings)
@@ -398,8 +447,8 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
index = self._widget_slots[slot_number].findText(adapter)
if index != -1:
self._widget_slots[slot_number].setCurrentIndex(index)
QtGui.QMessageBox.critical(self, node.name(), "A link is connected to port {} on adapter {}, please remove it first".format(node_port.name(),
adapter))
QtWidgets.QMessageBox.critical(self, node.name(), "A link is connected to port {} on adapter {}, please remove it first".format(node_port.name(),
adapter))
raise ConfigurationError()
def _checkForLinkConnectedToWIC(self, wic_number, settings, node):
@@ -419,8 +468,8 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
index = self._widget_wics[wic_number].findText(wic)
if index != -1:
self._widget_wics[wic_number].setCurrentIndex(index)
QtGui.QMessageBox.critical(self, node.name(), "A link is connected to port {} on {}, please remove it first".format(node_port.name(),
wic))
QtWidgets.QMessageBox.critical(self, node.name(), "A link is connected to port {} on {}, please remove it first".format(node_port.name(),
wic))
raise ConfigurationError()
def saveSettings(self, settings, node=None, group=False):
@@ -437,15 +486,15 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
# Check if the Idle-PC value has been validated okay
if not self._idle_valid:
idle_pc = self.uiIdlepcLineEdit.text()
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
QtWidgets.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
raise ConfigurationError()
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
QtWidgets.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
elif node and not node.validateHostname(name):
QtGui.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
QtWidgets.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
else:
settings["name"] = name
@@ -456,11 +505,14 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
settings["aux"] = aux
# check and save the base MAC address
# mac = self.uiBaseMACLineEdit.text()
# if mac and not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
# QtGui.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hhhh.hhhh.hhhh)")
# elif mac != "":
# settings["mac_addr"] = mac
mac = self.uiBaseMACLineEdit.text()
if mac != "..":
if not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
QtWidgets.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hhhh.hhhh.hhhh)")
else:
settings["mac_addr"] = mac
elif not node:
settings["mac_addr"] = ""
# save the IOS image path
settings["image"] = self.uiIOSImageLineEdit.text()
@@ -477,23 +529,41 @@ class IOSRouterConfigurationPage(QtGui.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:
QtGui.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
private_config = self.uiPrivateConfigLineEdit.text().strip()
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:
QtGui.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
symbol_path = self.uiSymbolLineEdit.text()
pixmap = QtGui.QPixmap(symbol_path)
if pixmap.isNull():
QtWidgets.QMessageBox.critical(self, "Symbol", "Invalid file or format not supported")
else:
settings["symbol"] = symbol_path
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
# get the platform and chassis if applicable
platform = settings["platform"]
@@ -533,6 +603,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
settings["nvram"] = self.uiNvramSpinBox.value()
settings["disk0"] = self.uiDisk0SpinBox.value()
settings["disk1"] = self.uiDisk1SpinBox.value()
settings["auto_delete_disks"] = self.uiAutoDeleteCheckBox.isChecked()
# save the system ID (processor board ID in IOS) setting
settings["system_id"] = self.uiSystemIdLineEdit.text()
@@ -541,7 +612,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
settings["exec_area"] = self.uiExecAreaSpinBox.value()
# save the Idle-PC setting
# TODO: check the format?
settings["idlepc"] = self.uiIdlepcLineEdit.text()
# save the idlemax setting
@@ -585,3 +655,11 @@ class IOSRouterConfigurationPage(QtGui.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

@@ -26,13 +26,11 @@ import math
import zipfile
import logging
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.main_window import MainWindow
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.cloud.utils import UploadFilesThread
from gns3.utils.progress_dialog import ProgressDialog
from gns3.utils.file_copy_worker import FileCopyWorker
from gns3.image_manager import ImageManager
from .. import Dynamips
from ..settings import IOS_ROUTER_SETTINGS
@@ -46,14 +44,16 @@ from ..dialogs.ios_router_wizard import IOSRouterWizard
log = logging.getLogger(__name__)
class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget):
class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWidget):
"""
QWidget preference page for IOS routers.
"""
_default_images_dir = ""
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._main_window = MainWindow.instance()
@@ -63,28 +63,26 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self.uiNewIOSRouterPushButton.clicked.connect(self._iosRouterNewSlot)
self.uiEditIOSRouterPushButton.clicked.connect(self._iosRouterEditSlot)
self.uiDeleteIOSRouterPushButton.clicked.connect(self._iosRouterDeleteSlot)
self.uiIOSRoutersTreeWidget.currentItemChanged.connect(self._iosRouterChangedSlot)
self.uiIOSRoutersTreeWidget.itemPressed.connect(self._iosRouterPressedSlot)
self.uiIOSRoutersTreeWidget.itemSelectionChanged.connect(self._iosRouterChangedSlot)
self.uiDecompressIOSPushButton.clicked.connect(self._decompressIOSSlot)
def _iosRouterChangedSlot(self, current, previous):
def _iosRouterChangedSlot(self):
"""
Loads a selected an IOS router from the tree widget.
:param current: current QTreeWidgetItem instance
:param previous: ignored
"""
if not current:
self.uiIOSRouterInfoTreeWidget.clear()
return
selection = self.uiIOSRoutersTreeWidget.selectedItems()
self.uiDeleteIOSRouterPushButton.setEnabled(len(selection) != 0)
single_selected = len(selection) == 1
self.uiEditIOSRouterPushButton.setEnabled(single_selected)
self.uiDecompressIOSPushButton.setEnabled(single_selected)
self.uiEditIOSRouterPushButton.setEnabled(True)
self.uiDeleteIOSRouterPushButton.setEnabled(True)
self.uiDecompressIOSPushButton.setEnabled(True)
key = current.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
self._refreshInfo(ios_router)
if single_selected:
key = selection[0].data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
self._refreshInfo(ios_router)
else:
self.uiIOSRouterInfoTreeWidget.clear()
def _iosRouterNewSlot(self):
"""
@@ -101,34 +99,6 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._ios_routers[key] = IOS_ROUTER_SETTINGS.copy()
self._ios_routers[key].update(ios_settings)
if ios_settings["server"] == 'cloud':
import logging
log = logging.getLogger(__name__)
log.debug(ios_settings["image"])
# Start uploading the image to cloud files
self._upload_image_progress_dialog = QtGui.QProgressDialog("Uploading image file {}".format(ios_settings['image']), "Cancel", 0, 0, parent=self)
self._upload_image_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
self._upload_image_progress_dialog.show()
try:
upload_thread = UploadFilesThread(
self,
cloud_settings=MainWindow.instance().cloudSettings(),
files_to_upload=[(
self._ios_routers[key]["image"],
'images/' + os.path.relpath(self._ios_routers[key]["image"],
self._main_window.settings().imagesDirPath())
)]
)
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:
self._upload_image_progress_dialog.reject()
log.error(e)
QtGui.QMessageBox.critical(self, "IOS image upload", "Error uploading IOS image: {}".format(e))
if ios_settings["platform"] == "c7200":
self._ios_routers[key]["midplane"] = "vxr"
self._ios_routers[key]["npe"] = "npe-400"
@@ -146,18 +116,13 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._ios_routers[key][wic] = ios_settings[wic]
self._ios_routers[key].update(ios_settings)
item = QtGui.QTreeWidgetItem(self.uiIOSRoutersTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiIOSRoutersTreeWidget)
item.setText(0, self._ios_routers[key]["name"])
item.setIcon(0, QtGui.QIcon(self._ios_routers[key]["default_symbol"]))
item.setIcon(0, QtGui.QIcon(self._ios_routers[key]["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiIOSRoutersTreeWidget.setCurrentItem(item)
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():
return
self._upload_image_progress_dialog.accept()
def _iosRouterEditSlot(self):
"""
Edits an IOS router.
@@ -170,12 +135,14 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
dialog = ConfigurationDialog(ios_router["name"], ios_router, IOSRouterConfigurationPage(), parent=self)
dialog.show()
if dialog.exec_():
# update the icon
item.setIcon(0, QtGui.QIcon(ios_router["symbol"]))
if ios_router["name"] != item.text(0):
# rename the IOS router
new_key = "{server}:{name}".format(server=ios_router["server"], name=ios_router["name"])
if new_key in self._ios_routers:
QtGui.QMessageBox.critical(self, "IOS router", "IOS router name {} already exists for server {}".format(ios_router["name"],
ios_router["server"]))
QtWidgets.QMessageBox.critical(self, "IOS router", "IOS router name {} already exists for server {}".format(ios_router["name"],
ios_router["server"]))
ios_router["name"] = item.text(0)
return
self._ios_routers[new_key] = self._ios_routers[key]
@@ -190,66 +157,77 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
Deletes an IOS router.
"""
item = self.uiIOSRoutersTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
for item in self.uiIOSRoutersTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.UserRole)
del self._ios_routers[key]
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
if self._ios_routers == {}:
self.uiEditIOSRouterPushButton.setEnabled(False)
self.uiDeleteIOSRouterPushButton.setEnabled(False)
self.uiDecompressIOSPushButton.setEnabled(False)
del self._ios_routers[key]
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
if self._ios_routers == {}:
self.uiEditIOSRouterPushButton.setEnabled(False)
self.uiDeleteIOSRouterPushButton.setEnabled(False)
self.uiDecompressIOSPushButton.setEnabled(False)
def _imageUploadComplete(self):
if self._upload_image_progress_dialog.wasCanceled():
return
self._upload_image_progress_dialog.accept()
@staticmethod
def getIOSImage(parent):
def getImageDirectory():
return ImageManager.instance().getDirectoryForType("DYNAMIPS")
@classmethod
def getIOSImage(cls, parent, server):
"""
:param parent: parent widget
:param server: The server where the image is located
:return: path to the IOS image or None
"""
destination_directory = os.path.join(MainWindow.instance().imagesDirPath(), "IOS")
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(parent,
"Select an IOS image",
destination_directory,
"All files (*.*);;IOS image (*.bin *.image)",
"IOS image (*.bin *.image)")
if not cls._default_images_dir:
cls._default_images_dir = cls.getImageDirectory()
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
"Select an IOS image",
cls._default_images_dir,
"All files (*.*);;IOS image (*.bin *.image)",
"IOS image (*.bin *.image)")
if not path:
return
cls._default_images_dir = os.path.dirname(path)
if not os.access(path, os.R_OK):
QtGui.QMessageBox.critical(parent, "IOS image", "Cannot read {}".format(path))
QtWidgets.QMessageBox.critical(parent, "IOS image", "Cannot read {}".format(path))
return
if sys.platform.startswith('win'):
# Dynamips (Cygwin acutally) doesn't like non ascii paths on Windows
# Dynamips (Cygwin actually) doesn't like non ascii paths on Windows
try:
path.encode('ascii')
except UnicodeEncodeError:
QtGui.QMessageBox.warning(parent, "IOS image", "The IOS image filename should contains only ascii (English) characters.")
QtWidgets.QMessageBox.warning(parent, "IOS image", "The IOS image filename should contains only ascii (English) characters.")
try:
with open(path, "rb") as f:
# read the first 7 bytes of the file.
elf_header_start = f.read(7)
except OSError as e:
QtGui.QMessageBox.critical(parent, "IOS image", "Cannot read ELF magic number: {}".format(e))
QtWidgets.QMessageBox.critical(parent, "IOS image", "Cannot read ELF magic number: {}".format(e))
return
# file must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
if elf_header_start != b'\x7fELF\x01\x02\x01':
QtGui.QMessageBox.critical(parent, "IOS image", "Sorry, this is not a valid IOS image!")
QtWidgets.QMessageBox.critical(parent, "IOS image", "Sorry, this is not a valid IOS image!")
return
try:
os.makedirs(destination_directory)
except FileExistsError:
pass
os.makedirs(cls.getImageDirectory(), exist_ok=True)
except OSError as e:
QtGui.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
@@ -258,10 +236,9 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
except (OSError, ValueError):
pass # ignore errors if we cannot find out the IOS image is compressed.
if compressed:
reply = QtGui.QMessageBox.question(parent, "IOS image", "Would you like to decompress this IOS image?",
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
decompressed_image_path = os.path.join(destination_directory, os.path.basename(os.path.splitext(path)[0] + ".image"))
reply = QtWidgets.QMessageBox.question(parent, "IOS image", "Would you like to decompress this IOS image?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
decompressed_image_path = os.path.join(cls.getImageDirectory(), os.path.basename(os.path.splitext(path)[0] + ".image"))
worker = DecompressIOSWorker(path, decompressed_image_path)
progress_dialog = ProgressDialog(worker,
"IOS image",
@@ -271,24 +248,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
if progress_dialog.exec_() is not False:
path = decompressed_image_path
if os.path.normpath(os.path.dirname(path)) != destination_directory:
# the IOS image is not in the default images directory
reply = QtGui.QMessageBox.question(parent,
"IOS image",
"Would you like to copy {} to the default images directory".format(os.path.basename(path)),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
destination_path = os.path.join(destination_directory, os.path.basename(path))
worker = FileCopyWorker(path, destination_path)
progress_dialog = ProgressDialog(worker, "IOS image", "Copying {}".format(os.path.basename(path)), "Cancel", busy=True, parent=parent)
progress_dialog.show()
progress_dialog.exec_()
errors = progress_dialog.errors()
if errors:
QtGui.QMessageBox.critical(parent, "IOS image", "{}".format("".join(errors)))
else:
path = destination_path
path = ImageManager.instance().askCopyUploadImage(parent, path, server, "DYNAMIPS")
return path
@@ -310,7 +270,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
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
@@ -329,26 +289,26 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
ios_router = self._ios_routers[key]
path = ios_router["image"]
if not os.path.isfile(path):
QtGui.QMessageBox.critical(self, "IOS image", "IOS image file {} is does not exist".format(path))
QtWidgets.QMessageBox.critical(self, "IOS image", "IOS image file {} is does not exist".format(path))
return
try:
if not isIOSCompressed(path):
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
QtWidgets.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
return
except OSError as e:
# errno 22, invalid argument means the file system where the IOS image is located doesn't support mmap
if e.errno == 22:
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} cannot be memory mapped, most likely because the file system doesn't support it".format(os.path.basename(path)))
QtWidgets.QMessageBox.critical(self, "IOS image", "IOS image {} cannot be memory mapped, most likely because the file system doesn't support it".format(os.path.basename(path)))
else:
QtGui.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
QtWidgets.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
return
except ValueError as e:
QtGui.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
QtWidgets.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
return
decompressed_image_path = os.path.splitext(path)[0] + ".image"
if os.path.isfile(decompressed_image_path):
QtGui.QMessageBox.critical(self, "IOS image", "Decompressed IOS image {} already exist".format(os.path.basename(decompressed_image_path)))
QtWidgets.QMessageBox.critical(self, "IOS image", "Decompressed IOS image {} already exist".format(os.path.basename(decompressed_image_path)))
return
worker = DecompressIOSWorker(path, decompressed_image_path)
@@ -363,7 +323,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
def _createSectionItem(self, name):
section_item = QtGui.QTreeWidgetItem(self.uiIOSRouterInfoTreeWidget)
section_item = QtWidgets.QTreeWidgetItem(self.uiIOSRouterInfoTreeWidget)
section_item.setText(0, name)
font = section_item.font(0)
font.setBold(True)
@@ -376,37 +336,39 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
# fill out the General section
section_item = self._createSectionItem("General")
QtGui.QTreeWidgetItem(section_item, ["Name:", ios_router["name"]])
QtGui.QTreeWidgetItem(section_item, ["Server:", ios_router["server"]])
QtGui.QTreeWidgetItem(section_item, ["Platform:", ios_router["platform"]])
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"]:
QtGui.QTreeWidgetItem(section_item, ["Chassis:", ios_router["chassis"]])
QtGui.QTreeWidgetItem(section_item, ["Image:", ios_router["image"]])
QtWidgets.QTreeWidgetItem(section_item, ["Chassis:", ios_router["chassis"]])
QtWidgets.QTreeWidgetItem(section_item, ["Image:", ios_router["image"]])
if ios_router["idlepc"]:
QtGui.QTreeWidgetItem(section_item, ["Idle-PC:", ios_router["idlepc"]])
QtWidgets.QTreeWidgetItem(section_item, ["Idle-PC:", ios_router["idlepc"]])
if ios_router["startup_config"]:
QtGui.QTreeWidgetItem(section_item, ["Startup-config:", ios_router["startup_config"]])
QtWidgets.QTreeWidgetItem(section_item, ["Startup-config:", ios_router["startup_config"]])
if ios_router["private_config"]:
QtGui.QTreeWidgetItem(section_item, ["Private-config:", ios_router["private_config"]])
QtWidgets.QTreeWidgetItem(section_item, ["Private-config:", ios_router["private_config"]])
if ios_router["platform"] == "c7200":
QtGui.QTreeWidgetItem(section_item, ["Midplane:", ios_router["midplane"]])
QtGui.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")
QtGui.QTreeWidgetItem(section_item, ["RAM:", "{} MiB".format(ios_router["ram"])])
QtGui.QTreeWidgetItem(section_item, ["NVRAM:", "{} KiB".format(ios_router["nvram"])])
QtWidgets.QTreeWidgetItem(section_item, ["RAM:", "{} MiB".format(ios_router["ram"])])
QtWidgets.QTreeWidgetItem(section_item, ["NVRAM:", "{} KiB".format(ios_router["nvram"])])
if "iomem" in ios_router and ios_router["iomem"]:
QtGui.QTreeWidgetItem(section_item, ["I/O memory:", "{}%".format(ios_router["iomem"])])
QtGui.QTreeWidgetItem(section_item, ["PCMCIA disk0:", "{} MiB".format(ios_router["disk0"])])
QtGui.QTreeWidgetItem(section_item, ["PCMCIA disk1:", "{} MiB".format(ios_router["disk1"])])
QtWidgets.QTreeWidgetItem(section_item, ["I/O memory:", "{}%".format(ios_router["iomem"])])
QtWidgets.QTreeWidgetItem(section_item, ["PCMCIA disk0:", "{} MiB".format(ios_router["disk0"])])
QtWidgets.QTreeWidgetItem(section_item, ["PCMCIA disk1:", "{} MiB".format(ios_router["disk1"])])
QtWidgets.QTreeWidgetItem(section_item, ["Auto delete:", "{}".format(ios_router["auto_delete_disks"])])
# fill out the Adapters section
section_item = self._createSectionItem("Adapters")
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if slot in ios_router and ios_router[slot]:
QtGui.QTreeWidgetItem(section_item, ["Slot {}:".format(slot_id), ios_router[slot]])
QtWidgets.QTreeWidgetItem(section_item, ["Slot {}:".format(slot_id), ios_router[slot]])
if section_item.childCount() == 0:
self.uiIOSRouterInfoTreeWidget.takeTopLevelItem(self.uiIOSRouterInfoTreeWidget.indexOfTopLevelItem(section_item))
@@ -415,7 +377,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if wic in ios_router and ios_router[wic]:
QtGui.QTreeWidgetItem(section_item, ["WIC {}:".format(wic_id), ios_router[wic]])
QtWidgets.QTreeWidgetItem(section_item, ["WIC {}:".format(wic_id), ios_router[wic]])
if section_item.childCount() == 0:
self.uiIOSRouterInfoTreeWidget.takeTopLevelItem(self.uiIOSRouterInfoTreeWidget.indexOfTopLevelItem(section_item))
@@ -423,61 +385,19 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self.uiIOSRouterInfoTreeWidget.resizeColumnToContents(0)
self.uiIOSRouterInfoTreeWidget.resizeColumnToContents(1)
def _iosRouterPressedSlot(self, item, column):
"""
Slot for item pressed.
:param item: ignored
:param column: ignored
"""
if QtGui.QApplication.mouseButtons() & QtCore.Qt.RightButton:
self._showContextualMenu()
def _showContextualMenu(self):
"""
Contextual menu.
"""
menu = QtGui.QMenu()
change_symbol_action = QtGui.QAction("Change symbol", menu)
change_symbol_action.setIcon(QtGui.QIcon(":/icons/node_conception.svg"))
self.connect(change_symbol_action, QtCore.SIGNAL('triggered()'), self._changeSymbolSlot)
menu.addAction(change_symbol_action)
menu.exec_(QtGui.QCursor.pos())
def _changeSymbolSlot(self):
"""
Change a symbol for an IOS router.
"""
item = self.uiIOSRoutersTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
dialog = SymbolSelectionDialog(self, symbol=ios_router["default_symbol"], category=ios_router["category"])
dialog.show()
if dialog.exec_():
normal_symbol, selected_symbol = dialog.getSymbols()
category = dialog.getCategory()
item.setIcon(0, QtGui.QIcon(normal_symbol))
ios_router["default_symbol"] = normal_symbol
ios_router["hover_symbol"] = selected_symbol
ios_router["category"] = category
def loadPreferences(self):
"""
Loads the IOS router preferences.
"""
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():
item = QtGui.QTreeWidgetItem(self.uiIOSRoutersTreeWidget)
item = QtWidgets.QTreeWidgetItem(self.uiIOSRoutersTreeWidget)
item.setText(0, ios_router["name"])
item.setIcon(0, QtGui.QIcon(ios_router["default_symbol"]))
item.setIcon(0, QtGui.QIcon(ios_router["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
@@ -490,4 +410,4 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
Saves the IOS router preferences.
"""
Dynamips.instance().setIOSRouters(self._ios_routers)
Dynamips.instance().setVMs(self._ios_routers)

View File

@@ -30,20 +30,11 @@ DYNAMIPS_SETTINGS = {
"mmap_support": True,
}
DYNAMIPS_SETTING_TYPES = {
"dynamips_path": str,
"allocate_aux_console_ports": bool,
"use_local_server": bool,
"ghost_ios_support": bool,
"sparse_memory_support": bool,
"mmap_support": bool,
}
IOS_ROUTER_SETTINGS = {
"name": "",
"default_name_format": "R{0}",
"image": "",
"default_symbol": ":/symbols/router.normal.svg",
"hover_symbol": ":/symbols/router.selected.svg",
"symbol": ":/symbols/router.svg",
"category": Node.routers,
"startup_config": "",
"private_config": "",
@@ -60,35 +51,11 @@ IOS_ROUTER_SETTINGS = {
"mac_addr": "",
"disk0": 0,
"disk1": 0,
"auto_delete_disks": False,
"system_id": "FTX0945W0MY",
"server": "local"
}
IOS_ROUTER_SETTING_TYPES = {
"name": str,
"image": str,
"default_symbol": str,
"hover_symbol": str,
"category": int,
"startup_config": str,
"private_config": str,
"platform": str,
"chassis": str,
"idlepc": str,
"idlemax": int,
"idlesleep": int,
"exec_area": int,
"mmap": bool,
"sparsemem": bool,
"ram": int,
"nvram": int,
"mac_addr": str,
"disk0": int,
"disk1": int,
"system_id": str,
"server": str
}
# supported platforms with the default RAM value
PLATFORMS_DEFAULT_RAM = {"c1700": 160,
"c2600": 160,
@@ -107,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
@@ -121,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

@@ -1,65 +1,49 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/modules/dynamips/ui/atm_bridge_configuration_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_bridge_configuration_page.ui'
#
# Created: Sun Mar 16 11:16:57 2014
# by: PyQt4 UI code generator 4.10
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmBridgeConfigPageWidget(object):
def setupUi(self, atmBridgeConfigPageWidget):
atmBridgeConfigPageWidget.setObjectName(_fromUtf8("atmBridgeConfigPageWidget"))
atmBridgeConfigPageWidget.setObjectName("atmBridgeConfigPageWidget")
atmBridgeConfigPageWidget.resize(432, 358)
self.gridLayout_2 = QtGui.QGridLayout(atmBridgeConfigPageWidget)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiMappingGroupBox = QtGui.QGroupBox(atmBridgeConfigPageWidget)
self.uiMappingGroupBox.setObjectName(_fromUtf8("uiMappingGroupBox"))
self.vboxlayout = QtGui.QVBoxLayout(self.uiMappingGroupBox)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.uiMappingTreeWidget = QtGui.QTreeWidget(self.uiMappingGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.gridLayout_2 = QtWidgets.QGridLayout(atmBridgeConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiMappingGroupBox = QtWidgets.QGroupBox(atmBridgeConfigPageWidget)
self.uiMappingGroupBox.setObjectName("uiMappingGroupBox")
self.vboxlayout = QtWidgets.QVBoxLayout(self.uiMappingGroupBox)
self.vboxlayout.setObjectName("vboxlayout")
self.uiMappingTreeWidget = QtWidgets.QTreeWidget(self.uiMappingGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiMappingTreeWidget.sizePolicy().hasHeightForWidth())
self.uiMappingTreeWidget.setSizePolicy(sizePolicy)
self.uiMappingTreeWidget.setRootIsDecorated(False)
self.uiMappingTreeWidget.setObjectName(_fromUtf8("uiMappingTreeWidget"))
self.uiMappingTreeWidget.setObjectName("uiMappingTreeWidget")
self.vboxlayout.addWidget(self.uiMappingTreeWidget)
self.gridLayout_2.addWidget(self.uiMappingGroupBox, 0, 2, 3, 1)
self.uiEthernetGroupBox = QtGui.QGroupBox(atmBridgeConfigPageWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.uiEthernetGroupBox = QtWidgets.QGroupBox(atmBridgeConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiEthernetGroupBox.sizePolicy().hasHeightForWidth())
self.uiEthernetGroupBox.setSizePolicy(sizePolicy)
self.uiEthernetGroupBox.setObjectName(_fromUtf8("uiEthernetGroupBox"))
self.gridlayout = QtGui.QGridLayout(self.uiEthernetGroupBox)
self.gridlayout.setObjectName(_fromUtf8("gridlayout"))
self.uiEthernetPortLabel = QtGui.QLabel(self.uiEthernetGroupBox)
self.uiEthernetPortLabel.setObjectName(_fromUtf8("uiEthernetPortLabel"))
self.uiEthernetGroupBox.setObjectName("uiEthernetGroupBox")
self.gridlayout = QtWidgets.QGridLayout(self.uiEthernetGroupBox)
self.gridlayout.setObjectName("gridlayout")
self.uiEthernetPortLabel = QtWidgets.QLabel(self.uiEthernetGroupBox)
self.uiEthernetPortLabel.setObjectName("uiEthernetPortLabel")
self.gridlayout.addWidget(self.uiEthernetPortLabel, 0, 0, 1, 1)
self.uiEthernetPortSpinBox = QtGui.QSpinBox(self.uiEthernetGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiEthernetPortSpinBox = QtWidgets.QSpinBox(self.uiEthernetGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiEthernetPortSpinBox.sizePolicy().hasHeightForWidth())
@@ -67,23 +51,23 @@ class Ui_atmBridgeConfigPageWidget(object):
self.uiEthernetPortSpinBox.setMinimum(0)
self.uiEthernetPortSpinBox.setMaximum(65535)
self.uiEthernetPortSpinBox.setProperty("value", 1)
self.uiEthernetPortSpinBox.setObjectName(_fromUtf8("uiEthernetPortSpinBox"))
self.uiEthernetPortSpinBox.setObjectName("uiEthernetPortSpinBox")
self.gridlayout.addWidget(self.uiEthernetPortSpinBox, 0, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiEthernetGroupBox, 1, 0, 1, 2)
self.uiATMGroupBox = QtGui.QGroupBox(atmBridgeConfigPageWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.uiATMGroupBox = QtWidgets.QGroupBox(atmBridgeConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiATMGroupBox.sizePolicy().hasHeightForWidth())
self.uiATMGroupBox.setSizePolicy(sizePolicy)
self.uiATMGroupBox.setObjectName(_fromUtf8("uiATMGroupBox"))
self.gridlayout1 = QtGui.QGridLayout(self.uiATMGroupBox)
self.gridlayout1.setObjectName(_fromUtf8("gridlayout1"))
self.uiATMPortLabel = QtGui.QLabel(self.uiATMGroupBox)
self.uiATMPortLabel.setObjectName(_fromUtf8("uiATMPortLabel"))
self.uiATMGroupBox.setObjectName("uiATMGroupBox")
self.gridlayout1 = QtWidgets.QGridLayout(self.uiATMGroupBox)
self.gridlayout1.setObjectName("gridlayout1")
self.uiATMPortLabel = QtWidgets.QLabel(self.uiATMGroupBox)
self.uiATMPortLabel.setObjectName("uiATMPortLabel")
self.gridlayout1.addWidget(self.uiATMPortLabel, 0, 0, 1, 1)
self.uiATMPortSpinBox = QtGui.QSpinBox(self.uiATMGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiATMPortSpinBox = QtWidgets.QSpinBox(self.uiATMGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiATMPortSpinBox.sizePolicy().hasHeightForWidth())
@@ -91,14 +75,14 @@ class Ui_atmBridgeConfigPageWidget(object):
self.uiATMPortSpinBox.setMinimum(0)
self.uiATMPortSpinBox.setMaximum(65535)
self.uiATMPortSpinBox.setProperty("value", 10)
self.uiATMPortSpinBox.setObjectName(_fromUtf8("uiATMPortSpinBox"))
self.uiATMPortSpinBox.setObjectName("uiATMPortSpinBox")
self.gridlayout1.addWidget(self.uiATMPortSpinBox, 0, 1, 1, 1)
self.uiATMVPILabel = QtGui.QLabel(self.uiATMGroupBox)
self.uiATMVPILabel.setObjectName(_fromUtf8("uiATMVPILabel"))
self.uiATMVPILabel = QtWidgets.QLabel(self.uiATMGroupBox)
self.uiATMVPILabel.setObjectName("uiATMVPILabel")
self.gridlayout1.addWidget(self.uiATMVPILabel, 1, 0, 1, 1)
self.uiATMVPISpinBox = QtGui.QSpinBox(self.uiATMGroupBox)
self.uiATMVPISpinBox = QtWidgets.QSpinBox(self.uiATMGroupBox)
self.uiATMVPISpinBox.setEnabled(True)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiATMVPISpinBox.sizePolicy().hasHeightForWidth())
@@ -107,40 +91,40 @@ class Ui_atmBridgeConfigPageWidget(object):
self.uiATMVPISpinBox.setMaximum(65535)
self.uiATMVPISpinBox.setSingleStep(1)
self.uiATMVPISpinBox.setProperty("value", 0)
self.uiATMVPISpinBox.setObjectName(_fromUtf8("uiATMVPISpinBox"))
self.uiATMVPISpinBox.setObjectName("uiATMVPISpinBox")
self.gridlayout1.addWidget(self.uiATMVPISpinBox, 1, 1, 1, 1)
self.uiATMVCILabel = QtGui.QLabel(self.uiATMGroupBox)
self.uiATMVCILabel.setObjectName(_fromUtf8("uiATMVCILabel"))
self.uiATMVCILabel = QtWidgets.QLabel(self.uiATMGroupBox)
self.uiATMVCILabel.setObjectName("uiATMVCILabel")
self.gridlayout1.addWidget(self.uiATMVCILabel, 2, 0, 1, 1)
self.uiATMVCISpinBox = QtGui.QSpinBox(self.uiATMGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiATMVCISpinBox = QtWidgets.QSpinBox(self.uiATMGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiATMVCISpinBox.sizePolicy().hasHeightForWidth())
self.uiATMVCISpinBox.setSizePolicy(sizePolicy)
self.uiATMVCISpinBox.setMaximum(65535)
self.uiATMVCISpinBox.setProperty("value", 100)
self.uiATMVCISpinBox.setObjectName(_fromUtf8("uiATMVCISpinBox"))
self.uiATMVCISpinBox.setObjectName("uiATMVCISpinBox")
self.gridlayout1.addWidget(self.uiATMVCISpinBox, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiATMGroupBox, 2, 0, 1, 2)
self.uiAddPushButton = QtGui.QPushButton(atmBridgeConfigPageWidget)
self.uiAddPushButton.setObjectName(_fromUtf8("uiAddPushButton"))
self.uiAddPushButton = QtWidgets.QPushButton(atmBridgeConfigPageWidget)
self.uiAddPushButton.setObjectName("uiAddPushButton")
self.gridLayout_2.addWidget(self.uiAddPushButton, 3, 0, 1, 1)
self.uiDeletePushButton = QtGui.QPushButton(atmBridgeConfigPageWidget)
self.uiDeletePushButton = QtWidgets.QPushButton(atmBridgeConfigPageWidget)
self.uiDeletePushButton.setEnabled(False)
self.uiDeletePushButton.setObjectName(_fromUtf8("uiDeletePushButton"))
self.uiDeletePushButton.setObjectName("uiDeletePushButton")
self.gridLayout_2.addWidget(self.uiDeletePushButton, 3, 1, 1, 1)
spacerItem = QtGui.QSpacerItem(371, 121, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem = QtWidgets.QSpacerItem(371, 121, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 4, 0, 1, 3)
self.uiGeneralGroupBox = QtGui.QGroupBox(atmBridgeConfigPageWidget)
self.uiGeneralGroupBox.setObjectName(_fromUtf8("uiGeneralGroupBox"))
self.gridLayout = QtGui.QGridLayout(self.uiGeneralGroupBox)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiNameLabel = QtGui.QLabel(self.uiGeneralGroupBox)
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
self.uiGeneralGroupBox = QtWidgets.QGroupBox(atmBridgeConfigPageWidget)
self.uiGeneralGroupBox.setObjectName("uiGeneralGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiGeneralGroupBox)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralGroupBox)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtGui.QLineEdit(self.uiGeneralGroupBox)
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralGroupBox)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiGeneralGroupBox, 0, 0, 1, 2)
@@ -153,17 +137,18 @@ class Ui_atmBridgeConfigPageWidget(object):
atmBridgeConfigPageWidget.setTabOrder(self.uiAddPushButton, self.uiDeletePushButton)
def retranslateUi(self, atmBridgeConfigPageWidget):
atmBridgeConfigPageWidget.setWindowTitle(_translate("atmBridgeConfigPageWidget", "ATM Bridge", None))
self.uiMappingGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "Mapping", None))
self.uiMappingTreeWidget.headerItem().setText(0, _translate("atmBridgeConfigPageWidget", "Ethernet Port", None))
self.uiMappingTreeWidget.headerItem().setText(1, _translate("atmBridgeConfigPageWidget", "Port:VPI:VCI", None))
self.uiEthernetGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "Ethernet side", None))
self.uiEthernetPortLabel.setText(_translate("atmBridgeConfigPageWidget", "Port:", None))
self.uiATMGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "ATM side", None))
self.uiATMPortLabel.setText(_translate("atmBridgeConfigPageWidget", "Port:", None))
self.uiATMVPILabel.setText(_translate("atmBridgeConfigPageWidget", "VPI:", None))
self.uiATMVCILabel.setText(_translate("atmBridgeConfigPageWidget", "VCI:", None))
self.uiAddPushButton.setText(_translate("atmBridgeConfigPageWidget", "&Add", None))
self.uiDeletePushButton.setText(_translate("atmBridgeConfigPageWidget", "&Delete", None))
self.uiGeneralGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "General", None))
self.uiNameLabel.setText(_translate("atmBridgeConfigPageWidget", "Name:", None))
_translate = QtCore.QCoreApplication.translate
atmBridgeConfigPageWidget.setWindowTitle(_translate("atmBridgeConfigPageWidget", "ATM Bridge"))
self.uiMappingGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "Mapping"))
self.uiMappingTreeWidget.headerItem().setText(0, _translate("atmBridgeConfigPageWidget", "Ethernet Port"))
self.uiMappingTreeWidget.headerItem().setText(1, _translate("atmBridgeConfigPageWidget", "Port:VPI:VCI"))
self.uiEthernetGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "Ethernet side"))
self.uiEthernetPortLabel.setText(_translate("atmBridgeConfigPageWidget", "Port:"))
self.uiATMGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "ATM side"))
self.uiATMPortLabel.setText(_translate("atmBridgeConfigPageWidget", "Port:"))
self.uiATMVPILabel.setText(_translate("atmBridgeConfigPageWidget", "VPI:"))
self.uiATMVCILabel.setText(_translate("atmBridgeConfigPageWidget", "VCI:"))
self.uiAddPushButton.setText(_translate("atmBridgeConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("atmBridgeConfigPageWidget", "&Delete"))
self.uiGeneralGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("atmBridgeConfigPageWidget", "Name:"))

View File

@@ -1,88 +1,72 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/modules/dynamips/ui/atm_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_switch_configuration_page.ui'
#
# Created: Sun Mar 16 11:16:57 2014
# by: PyQt4 UI code generator 4.10
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName(_fromUtf8("atmSwitchConfigPageWidget"))
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
atmSwitchConfigPageWidget.resize(459, 419)
self.gridLayout_2 = QtGui.QGridLayout(atmSwitchConfigPageWidget)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiGeneralGroupBox = QtGui.QGroupBox(atmSwitchConfigPageWidget)
self.uiGeneralGroupBox.setObjectName(_fromUtf8("uiGeneralGroupBox"))
self.gridLayout = QtGui.QGridLayout(self.uiGeneralGroupBox)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiNameLabel = QtGui.QLabel(self.uiGeneralGroupBox)
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
self.gridLayout_2 = QtWidgets.QGridLayout(atmSwitchConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiGeneralGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
self.uiGeneralGroupBox.setObjectName("uiGeneralGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiGeneralGroupBox)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralGroupBox)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtGui.QLineEdit(self.uiGeneralGroupBox)
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralGroupBox)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiVPICheckBox = QtGui.QCheckBox(self.uiGeneralGroupBox)
self.uiVPICheckBox.setObjectName(_fromUtf8("uiVPICheckBox"))
self.uiVPICheckBox = QtWidgets.QCheckBox(self.uiGeneralGroupBox)
self.uiVPICheckBox.setObjectName("uiVPICheckBox")
self.gridLayout.addWidget(self.uiVPICheckBox, 1, 0, 1, 2)
self.gridLayout_2.addWidget(self.uiGeneralGroupBox, 0, 0, 1, 3)
self.uiMappingGroupBox = QtGui.QGroupBox(atmSwitchConfigPageWidget)
self.uiMappingGroupBox.setObjectName(_fromUtf8("uiMappingGroupBox"))
self.vboxlayout = QtGui.QVBoxLayout(self.uiMappingGroupBox)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.uiMappingTreeWidget = QtGui.QTreeWidget(self.uiMappingGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.uiMappingGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
self.uiMappingGroupBox.setObjectName("uiMappingGroupBox")
self.vboxlayout = QtWidgets.QVBoxLayout(self.uiMappingGroupBox)
self.vboxlayout.setObjectName("vboxlayout")
self.uiMappingTreeWidget = QtWidgets.QTreeWidget(self.uiMappingGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiMappingTreeWidget.sizePolicy().hasHeightForWidth())
self.uiMappingTreeWidget.setSizePolicy(sizePolicy)
self.uiMappingTreeWidget.setRootIsDecorated(False)
self.uiMappingTreeWidget.setObjectName(_fromUtf8("uiMappingTreeWidget"))
self.uiMappingTreeWidget.setObjectName("uiMappingTreeWidget")
self.vboxlayout.addWidget(self.uiMappingTreeWidget)
self.gridLayout_2.addWidget(self.uiMappingGroupBox, 0, 3, 3, 1)
self.uiAddPushButton = QtGui.QPushButton(atmSwitchConfigPageWidget)
self.uiAddPushButton.setObjectName(_fromUtf8("uiAddPushButton"))
self.uiAddPushButton = QtWidgets.QPushButton(atmSwitchConfigPageWidget)
self.uiAddPushButton.setObjectName("uiAddPushButton")
self.gridLayout_2.addWidget(self.uiAddPushButton, 3, 0, 1, 1)
self.uiDeletePushButton = QtGui.QPushButton(atmSwitchConfigPageWidget)
self.uiDeletePushButton = QtWidgets.QPushButton(atmSwitchConfigPageWidget)
self.uiDeletePushButton.setEnabled(False)
self.uiDeletePushButton.setObjectName(_fromUtf8("uiDeletePushButton"))
self.uiDeletePushButton.setObjectName("uiDeletePushButton")
self.gridLayout_2.addWidget(self.uiDeletePushButton, 3, 1, 1, 1)
spacerItem = QtGui.QSpacerItem(213, 31, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
spacerItem = QtWidgets.QSpacerItem(213, 31, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 4, 2, 1, 2)
self.uiSourceGroupBox = QtGui.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.uiSourceGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceGroupBox.sizePolicy().hasHeightForWidth())
self.uiSourceGroupBox.setSizePolicy(sizePolicy)
self.uiSourceGroupBox.setObjectName(_fromUtf8("uiSourceGroupBox"))
self.gridlayout = QtGui.QGridLayout(self.uiSourceGroupBox)
self.gridlayout.setObjectName(_fromUtf8("gridlayout"))
self.uiSourcePortLabel = QtGui.QLabel(self.uiSourceGroupBox)
self.uiSourcePortLabel.setObjectName(_fromUtf8("uiSourcePortLabel"))
self.uiSourceGroupBox.setObjectName("uiSourceGroupBox")
self.gridlayout = QtWidgets.QGridLayout(self.uiSourceGroupBox)
self.gridlayout.setObjectName("gridlayout")
self.uiSourcePortLabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourcePortLabel.setObjectName("uiSourcePortLabel")
self.gridlayout.addWidget(self.uiSourcePortLabel, 0, 0, 1, 1)
self.uiSourcePortSpinBox = QtGui.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiSourcePortSpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourcePortSpinBox.sizePolicy().hasHeightForWidth())
@@ -90,50 +74,50 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiSourcePortSpinBox.setMinimum(0)
self.uiSourcePortSpinBox.setMaximum(65535)
self.uiSourcePortSpinBox.setProperty("value", 1)
self.uiSourcePortSpinBox.setObjectName(_fromUtf8("uiSourcePortSpinBox"))
self.uiSourcePortSpinBox.setObjectName("uiSourcePortSpinBox")
self.gridlayout.addWidget(self.uiSourcePortSpinBox, 0, 1, 1, 1)
self.uiSourceVPILabel = QtGui.QLabel(self.uiSourceGroupBox)
self.uiSourceVPILabel.setObjectName(_fromUtf8("uiSourceVPILabel"))
self.uiSourceVPILabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourceVPILabel.setObjectName("uiSourceVPILabel")
self.gridlayout.addWidget(self.uiSourceVPILabel, 1, 0, 1, 1)
self.uiSourceVPISpinBox = QtGui.QSpinBox(self.uiSourceGroupBox)
self.uiSourceVPISpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
self.uiSourceVPISpinBox.setEnabled(True)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceVPISpinBox.sizePolicy().hasHeightForWidth())
self.uiSourceVPISpinBox.setSizePolicy(sizePolicy)
self.uiSourceVPISpinBox.setMaximum(65535)
self.uiSourceVPISpinBox.setProperty("value", 0)
self.uiSourceVPISpinBox.setObjectName(_fromUtf8("uiSourceVPISpinBox"))
self.uiSourceVPISpinBox.setObjectName("uiSourceVPISpinBox")
self.gridlayout.addWidget(self.uiSourceVPISpinBox, 1, 1, 1, 1)
self.uiSourceVCILabel = QtGui.QLabel(self.uiSourceGroupBox)
self.uiSourceVCILabel.setObjectName(_fromUtf8("uiSourceVCILabel"))
self.uiSourceVCILabel = QtWidgets.QLabel(self.uiSourceGroupBox)
self.uiSourceVCILabel.setObjectName("uiSourceVCILabel")
self.gridlayout.addWidget(self.uiSourceVCILabel, 2, 0, 1, 1)
self.uiSourceVCISpinBox = QtGui.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiSourceVCISpinBox = QtWidgets.QSpinBox(self.uiSourceGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiSourceVCISpinBox.sizePolicy().hasHeightForWidth())
self.uiSourceVCISpinBox.setSizePolicy(sizePolicy)
self.uiSourceVCISpinBox.setMaximum(65535)
self.uiSourceVCISpinBox.setProperty("value", 100)
self.uiSourceVCISpinBox.setObjectName(_fromUtf8("uiSourceVCISpinBox"))
self.uiSourceVCISpinBox.setObjectName("uiSourceVCISpinBox")
self.gridlayout.addWidget(self.uiSourceVCISpinBox, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiSourceGroupBox, 1, 0, 1, 3)
self.uiDestinationGroupBox = QtGui.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.uiDestinationGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationGroupBox.sizePolicy().hasHeightForWidth())
self.uiDestinationGroupBox.setSizePolicy(sizePolicy)
self.uiDestinationGroupBox.setObjectName(_fromUtf8("uiDestinationGroupBox"))
self.gridlayout1 = QtGui.QGridLayout(self.uiDestinationGroupBox)
self.gridlayout1.setObjectName(_fromUtf8("gridlayout1"))
self.uiDestinationPortLabel = QtGui.QLabel(self.uiDestinationGroupBox)
self.uiDestinationPortLabel.setObjectName(_fromUtf8("uiDestinationPortLabel"))
self.uiDestinationGroupBox.setObjectName("uiDestinationGroupBox")
self.gridlayout1 = QtWidgets.QGridLayout(self.uiDestinationGroupBox)
self.gridlayout1.setObjectName("gridlayout1")
self.uiDestinationPortLabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationPortLabel.setObjectName("uiDestinationPortLabel")
self.gridlayout1.addWidget(self.uiDestinationPortLabel, 0, 0, 1, 1)
self.uiDestinationPortSpinBox = QtGui.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiDestinationPortSpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationPortSpinBox.sizePolicy().hasHeightForWidth())
@@ -141,34 +125,34 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortSpinBox.setMinimum(0)
self.uiDestinationPortSpinBox.setMaximum(65535)
self.uiDestinationPortSpinBox.setProperty("value", 10)
self.uiDestinationPortSpinBox.setObjectName(_fromUtf8("uiDestinationPortSpinBox"))
self.uiDestinationPortSpinBox.setObjectName("uiDestinationPortSpinBox")
self.gridlayout1.addWidget(self.uiDestinationPortSpinBox, 0, 1, 1, 1)
self.uiDestinationVPILabel = QtGui.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVPILabel.setObjectName(_fromUtf8("uiDestinationVPILabel"))
self.uiDestinationVPILabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVPILabel.setObjectName("uiDestinationVPILabel")
self.gridlayout1.addWidget(self.uiDestinationVPILabel, 1, 0, 1, 1)
self.uiDestinationVPISpinBox = QtGui.QSpinBox(self.uiDestinationGroupBox)
self.uiDestinationVPISpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
self.uiDestinationVPISpinBox.setEnabled(True)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationVPISpinBox.sizePolicy().hasHeightForWidth())
self.uiDestinationVPISpinBox.setSizePolicy(sizePolicy)
self.uiDestinationVPISpinBox.setMaximum(65535)
self.uiDestinationVPISpinBox.setProperty("value", 0)
self.uiDestinationVPISpinBox.setObjectName(_fromUtf8("uiDestinationVPISpinBox"))
self.uiDestinationVPISpinBox.setObjectName("uiDestinationVPISpinBox")
self.gridlayout1.addWidget(self.uiDestinationVPISpinBox, 1, 1, 1, 1)
self.uiDestinationVCILabel = QtGui.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVCILabel.setObjectName(_fromUtf8("uiDestinationVCILabel"))
self.uiDestinationVCILabel = QtWidgets.QLabel(self.uiDestinationGroupBox)
self.uiDestinationVCILabel.setObjectName("uiDestinationVCILabel")
self.gridlayout1.addWidget(self.uiDestinationVCILabel, 2, 0, 1, 1)
self.uiDestinationVCISpinBox = QtGui.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
self.uiDestinationVCISpinBox = QtWidgets.QSpinBox(self.uiDestinationGroupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDestinationVCISpinBox.sizePolicy().hasHeightForWidth())
self.uiDestinationVCISpinBox.setSizePolicy(sizePolicy)
self.uiDestinationVCISpinBox.setMaximum(65535)
self.uiDestinationVCISpinBox.setProperty("value", 200)
self.uiDestinationVCISpinBox.setObjectName(_fromUtf8("uiDestinationVCISpinBox"))
self.uiDestinationVCISpinBox.setObjectName("uiDestinationVCISpinBox")
self.gridlayout1.addWidget(self.uiDestinationVCISpinBox, 2, 1, 1, 1)
self.gridLayout_2.addWidget(self.uiDestinationGroupBox, 2, 0, 1, 3)
@@ -184,20 +168,21 @@ class Ui_atmSwitchConfigPageWidget(object):
atmSwitchConfigPageWidget.setTabOrder(self.uiAddPushButton, self.uiDeletePushButton)
def retranslateUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setWindowTitle(_translate("atmSwitchConfigPageWidget", "ATM Switch", None))
self.uiGeneralGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "General", None))
self.uiNameLabel.setText(_translate("atmSwitchConfigPageWidget", "Name:", None))
self.uiVPICheckBox.setText(_translate("atmSwitchConfigPageWidget", "Use VPI only (VP tunnel)", None))
self.uiMappingGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Mapping", None))
self.uiMappingTreeWidget.headerItem().setText(0, _translate("atmSwitchConfigPageWidget", "Port:VPI:VCI", None))
self.uiMappingTreeWidget.headerItem().setText(1, _translate("atmSwitchConfigPageWidget", "Port:VPI:VCI", None))
self.uiAddPushButton.setText(_translate("atmSwitchConfigPageWidget", "&Add", None))
self.uiDeletePushButton.setText(_translate("atmSwitchConfigPageWidget", "&Delete", None))
self.uiSourceGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Source", None))
self.uiSourcePortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:", None))
self.uiSourceVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:", None))
self.uiSourceVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:", None))
self.uiDestinationGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Destination", None))
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:", None))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:", None))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:", None))
_translate = QtCore.QCoreApplication.translate
atmSwitchConfigPageWidget.setWindowTitle(_translate("atmSwitchConfigPageWidget", "ATM Switch"))
self.uiGeneralGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("atmSwitchConfigPageWidget", "Name:"))
self.uiVPICheckBox.setText(_translate("atmSwitchConfigPageWidget", "Use VPI only (VP tunnel)"))
self.uiMappingGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Mapping"))
self.uiMappingTreeWidget.headerItem().setText(0, _translate("atmSwitchConfigPageWidget", "Port:VPI:VCI"))
self.uiMappingTreeWidget.headerItem().setText(1, _translate("atmSwitchConfigPageWidget", "Port:VPI:VCI"))
self.uiAddPushButton.setText(_translate("atmSwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("atmSwitchConfigPageWidget", "&Delete"))
self.uiSourceGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Source"))
self.uiSourcePortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiSourceVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiSourceVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))
self.uiDestinationGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "Destination"))
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>430</width>
<height>539</height>
<width>435</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
@@ -154,8 +154,6 @@
</spacer>
</item>
</layout>
<zorder>spacer_2</zorder>
<zorder>uiMemoryUsageOptimisationGroupBox</zorder>
</widget>
</widget>
</item>

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