Compare commits

...

1354 Commits

Author SHA1 Message Date
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
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
grossmj
e0f03ec582 Allows VMware VMs to use vmnet interfaces for connections without using uBridge. 2015-09-05 14:38:44 -06:00
Julien Duponchelle
54139845ac Add a firewall symbol
Fix #631
2015-09-04 10:41:54 +02: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
Julien Duponchelle
858a59568e 1.3.6 2015-06-16 21:55:25 +02:00
grossmj
e0eabbebd1 Merge remote-tracking branch 'origin/master' 2015-06-16 12:39:47 -06:00
grossmj
1b5c675711 Ubridge is not in 1.3.x 2015-06-16 12:39:35 -06:00
Julien Duponchelle
bf4daff685 1.3.6dev1 2015-06-16 19:12:22 +02:00
Julien Duponchelle
d669b19906 1.3.5 2015-06-16 19:04:01 +02:00
grossmj
95e665a917 Prevent the local server to restart in Preferences if no settings have been changed. 2015-06-16 11:00:04 -06:00
grossmj
4557094716 Merge remote-tracking branch 'origin/master' 2015-06-16 10:49:11 -06:00
grossmj
eb8e585c8c Save server logs to a file. 2015-06-16 10:48:38 -06:00
Julien Duponchelle
5052d1263b Do not crash in a very rare case on Windows when stoping local server
Fix #430
2015-06-16 16:38:43 +02: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
4734d2645f Escape usage to glob
Fix #424
2015-06-12 15:09:13 +02:00
Julien Duponchelle
87a04193f9 Fix QIODevice::read: device not open
Fix #360
2015-06-12 14:22:52 +02:00
Julien Duponchelle
ea8326ca24 Fix QMessageBox.NoButton): argument 1 has unexpected type 'Servers'
Fix #423
2015-06-12 13:43:39 +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
Julien Duponchelle
c3cadf0db3 Allow to turn on/off auth 2015-06-10 23:13:45 +02:00
Jeremy
b57bf29247 Removes settings types (was used for QSettings). 2015-06-10 15:11:19 -06:00
Julien Duponchelle
c5f1289aee Turn on/off local server auth
Fix #418
2015-06-10 23:09:19 +02: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
d4ffc21a11 Fix 'ValueError' object has no attribute 'errno' in IOS decompress
Fix #412
2015-06-10 11:54:24 +02:00
Julien Duponchelle
aefb061f22 Fix error if communication with the update server is intercepted by a
third party.

Fix #413
2015-06-10 11:48:20 +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
675d161df0 Fix auth errors if you change the local server IP
Fix #409
2015-06-09 12:11:54 +02:00
Julien Duponchelle
fe45fd263b Support auth for local server
Fix https://github.com/GNS3/gns3-server/issues/222
2015-06-09 11:36:20 +02:00
Julien Duponchelle
2e7d8299a1 Ensure no colored log output on Windows 2015-06-09 10:04:18 +02: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
99d3925b6c Merge remote-tracking branch 'origin/master' 2015-06-05 14:57:20 -06:00
grossmj
aed7a6fbf3 Merge remote-tracking branch 'origin/master' into unstable 2015-06-05 14:56:15 -06:00
grossmj
1d584235e0 Fixes issue with default router settings for templates. 2015-06-05 14:55:43 -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
4e6c354ff9 Display a proper message if you use a remote server started with --local
Fix https://github.com/GNS3/gns3-server/issues/207
2015-06-05 11:51:34 +02:00
Julien Duponchelle
7203165d88 Catch zlib error when uncompress IOS
Fix #394
2015-06-05 11:32:43 +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
Julien Duponchelle
127ead0518 Raise error if we pass non string to Port name
Fix #393
2015-06-04 10:44:49 +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
Julien Duponchelle
5bf0f08f25 Add basic auth support for local server 2015-06-03 16:23:14 +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
Julien Duponchelle
8eb4c1e7c1 Update crash report key 2015-06-02 20:25:43 +02:00
Julien Duponchelle
3b4f0f67f7 1.3.5dev1 2015-06-02 20:04:58 +02:00
Julien Duponchelle
bf1436fdff Version 1.3.4.0 2015-06-02 19:49:19 +02:00
Julien Duponchelle
0bd47c8c72 1.3.4 2015-06-02 19:46:20 +02:00
Julien Duponchelle
7599ec6248 Catch OSError in restore snapshots
Fix #383
2015-06-02 16:19:29 +02:00
Julien Duponchelle
266df9bf71 Fix: _findDynamips() takes 0 positional arguments but 1 was given
Fix #382
2015-06-02 16:08:20 +02:00
Julien Duponchelle
69b937321c 1.3.4 Changelog 2015-06-02 14:48:24 +02: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
5751a5c7e5 Bump version to 1.3.4.dev2 2015-06-01 16:29:49 -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
Jeremy
f9550e93d3 Check if an IOS image is set in the IOS router template. 2015-06-01 16:00:48 -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
Julien Duponchelle
be7278294a Fix crash in autostart if no link is present
Fix #380
2015-06-01 10:52:15 +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
cd9bb16f79 Ensure the version number is written in configuration file 2015-05-28 17:57:40 +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
grossmj
0bb15cc6b8 Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs. 2015-05-27 13:56:27 -06:00
Julien Duponchelle
5b0ca03640 Fix tests 2015-05-27 18:48:18 +02:00
Julien Duponchelle
df5abad690 Fix resize issue in server page
Fix #375
2015-05-27 11:08:28 +02:00
Julien Duponchelle
549758d789 Fix segfault when starting OSX server with allow connection from
anywhere

Fix https://github.com/GNS3/gns3-server/issues/197
2015-05-27 10:29:32 +02:00
Jeremy
b49a850297 Fixes bug when editing c7200 templates. 2015-05-26 19:06:26 -06:00
Jeremy
5cc1c11f1d Merge remote-tracking branch 'origin/master' 2015-05-26 17:42:28 -06:00
Jeremy
1e5a596aae Fixes IOS decompression. Fixes #370. 2015-05-26 17:42:22 -06:00
Julien Duponchelle
c63d3a5b61 Topology auto start work for VPCS
Fix #371
2015-05-26 16:56:19 +02:00
grossmj
0b8f195249 Avoid moving .gns3_temporary files. 2015-05-26 09:54:17 +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
1b04a50836 Handles MemoryError. 2015-05-25 19:35:09 -06: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
Julien Duponchelle
54c1e64739 Typo 2015-05-18 16:35:43 +02:00
Julien Duponchelle
e6e88d2a2b Fix crash when a process listen on GNS3 port return an empty JSON
Fix #367
2015-05-18 12:32:15 +02:00
Julien Duponchelle
7cd229acf0 Another fix for the topology None error
Fix #366
2015-05-18 12:23:58 +02:00
Julien Duponchelle
891c540d60 Fix a rare crash in completion
Fix #364
2015-05-18 12:10:03 +02:00
Julien Duponchelle
ebd8f300d0 Fix crash when loading topology in rare conditions
Fix #365
2015-05-18 12:06:39 +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
52dee04e72 New crash report key 2015-05-14 19:25:36 +02:00
Julien Duponchelle
e34f030073 Bump to 1.3.4 2015-05-14 19:21:43 +02:00
Julien Duponchelle
2b86c24175 Version 1.3.3 2015-05-14 19:00:01 +02:00
Julien Duponchelle
298768f0cb Fix crash 2015-05-14 18:55:22 +02:00
grossmj
0ba01ddcdd New inline help text for the idle-pc dialog. 2015-05-14 10:13:05 -06:00
Jeremy
abc6ae2dca Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template. 2015-05-13 17:58:24 -06:00
Jeremy
e3d47e1e5d Adds name to the thank you section. 2015-05-13 17:19:51 -06:00
Jeremy
17dae79660 Prevent users to use VirtualBox linked clone VMs in temporary projects (for now). 2015-05-13 16:53:03 -06:00
Jeremy
d6336710b8 Merge remote-tracking branch 'origin/master' 2015-05-13 16:04:01 -06:00
Jeremy
ed25a4191d Fixes #337. 2015-05-13 16:03:56 -06:00
Julien Duponchelle
5723097cbe Fix crash 2015-05-13 23:27:09 +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
38b600520f Capture error if the command is invalid
Fix #358
2015-05-13 17:23:17 +02:00
Julien Duponchelle
0fae016d15 Cleanup egg cache when exit 2015-05-13 14:33:59 +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
Julien Duponchelle
74b4013002 Fix a crash in console when you used non UTF-8 terminal
Fix #351
2015-05-11 14:34:18 +02:00
Julien Duponchelle
eede8bdc2f Fix crash during save as
Fix #350
2015-05-11 14:28:20 +02:00
grossmj
7db0de7ccc Change title when exporting an IOS startup-config. 2015-05-10 20:39:10 -06: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
364cde1287 Updates bug reporting link. 2015-05-09 17:38:19 -06: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
c532d74f8b Removes analytics client on closing. 2015-05-08 18:35:18 -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
Jeremy
2303c54ac5 Bump version to 1.3.3.dev3 2015-05-07 11:52:17 -06:00
Julien Duponchelle
6762ce347d Release 1.3.3rc1 2015-05-07 16:00:32 +02:00
Julien Duponchelle
2e884339a8 Another fix for the link creation issue on OSX
https://community.gns3.com/message/26786
2015-05-07 15:43:13 +02:00
Julien Duponchelle
3bdb18a2b4 Another broken pipe error catched for OSX
Fix https://github.com/GNS3/gns3-server/issues/166
2015-05-07 15:35:15 +02:00
Julien Duponchelle
aaf2b7e206 Prevent a topology made for next version to be open in previous version
Fix #345
2015-05-07 10:50:40 +02:00
Julien Duponchelle
ef7a711f07 Check if the local server is really a local server
Fix #344
2015-05-07 10:33:26 +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
grossmj
dc95bad4aa Merge remote-tracking branch 'origin/master' 2015-05-06 14:59:35 -06:00
grossmj
327d0277fc NIO NAT support for QEMU VMs (user mode back-end is used). 2015-05-06 14:59:01 -06: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
72add39182 Fix PyQT4 download link 2015-05-06 13:40:00 +02:00
Julien Duponchelle
d02f0bf5b4 Merge branch 'master' into unstable 2015-05-06 13:39:20 +02:00
Vasil Rangelov
509a946c92 Modified version requirements, so that they require the dependency versions as minimums.
Added some more detailed instructions for compilation on Windows.

Conflicts:
	requirements.txt
2015-05-06 13:38:41 +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
b10946f0e8 PEP8 2015-05-06 13:21:17 +02:00
Julien Duponchelle
6bcb1ab113 Prevent user to enter a None port
I don't know how they force the None value in the input field...

Fix #334, #333
2015-05-06 11:06:50 +02:00
Julien Duponchelle
9dc22142ef Change crash report key
Fix #336
2015-05-06 10:58:43 +02:00
Julien Duponchelle
e23818b3b4 Typo 2015-05-06 10:52:29 +02:00
Julien Duponchelle
0eef72b6e3 Fix broken pipe error on OSX when frozen
Fix https://github.com/GNS3/gns3-server/issues/166
2015-05-06 10:47:03 +02:00
grossmj
00995e3a6a Merge remote-tracking branch 'origin/master' 2015-05-05 14:49:13 -06:00
grossmj
089dad31c0 Bump version to 1.3.3dev2 2015-05-05 14:49:03 -06:00
Jeremy Grossmann
b79d36e0eb Merge pull request #332 from GNS3/prevent_double_link
Prevent the same link to be created twice.
2015-05-05 14:37:34 -06:00
Julien Duponchelle
07a78b55cb Prevent the same link created twice 2015-05-05 22:27:34 +02:00
grossmj
7aa6f8fa9d Merge remote-tracking branch 'origin/master' 2015-05-05 12:29:05 -06:00
grossmj
47eaa1ac96 Project loading: names and IDs must be assigned to ports on the client side after nodes have been created. Fixes #326.
Fixes config updating for IOS router and IOU devices.
2015-05-05 12:28:56 -06:00
Julien Duponchelle
0866632b2f Fix a crash when dropping a .gns3
Fix #328
2015-05-05 19:58:20 +02:00
Julien Duponchelle
7500b602dc Merge branch 'master' into unstable 2015-05-05 14:21:34 +02:00
Julien Duponchelle
31b341cb68 Cleanup VPCS code
Fix #324
2015-05-05 14:16:07 +02:00
Julien Duponchelle
7c62945560 Turn off config parser interpolation
Fix #322
2015-05-05 11:50:05 +02:00
Julien Duponchelle
59fb3c1dbe Support unicode characters in regex 2015-05-05 10:21:05 +02:00
grossmj
ea86e2990b Fixes duplicate entries for "Recent files" on Windows. Fixes #316. 2015-05-04 22:59:46 -06:00
grossmj
b2e0ec4130 Fixes VPCS multi-host. Fixes #318. 2015-05-04 22:20:03 -06:00
grossmj
de8a73efb7 Fixes issues when importing configs for IOS, IOU and VPCS. Fixes #314. 2015-05-04 21:42:50 -06:00
grossmj
df95c2c85d Fixes issue when console setting present in IOS router templates. 2015-05-04 20:32:00 -06:00
Jeremy
df322ceacf Do not send empty settings when creating VMs. 2015-05-04 19:03:35 -06:00
Jeremy
78df3fc12c Do not set a default private-config when creating a new IOS router template. Fixes #317. 2015-05-04 18:47:56 -06:00
Jeremy
b1a1824522 Refactors how startup-config and private-config are handled for IOS routers. 2015-05-04 18:42:32 -06:00
Jeremy
ffab68e809 Fixes IOU and QEMU tests. 2015-05-04 16:07:10 -06: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
Jeremy
a4a242d58b Makes sure all IOS router settings are saved in the project file & simplify loading from a project. 2015-05-04 14:09:09 -06:00
Jeremy
b488764b79 Makes sure all VirtualBox VM settings are saved in the project file & simplify loading from a project. 2015-05-04 12:44:50 -06:00
Jeremy
38848f43e4 Makes sure all VPCS VM settings are saved in the project file & simplify loading from a project. 2015-05-04 12:05:35 -06:00
grossmj
9ffea5cadb Merge remote-tracking branch 'origin/master' 2015-05-04 11:20:08 -06:00
grossmj
9e05675823 Makes sure all IOU VM settings are saved in the project file & simplify loading from a project. 2015-05-04 11:20:08 -06:00
grossmj
3b4efc21a6 Makes sure all QEMU VM settings are saved in the project file & simplify loading from a project. 2015-05-04 11:19:30 -06:00
Julien Duponchelle
beaf31ae05 Fix save as by correctly renaming VM uuid project directory
Fix https://github.com/GNS3/gns3-server/issues/173
2015-05-04 17:23:29 +02:00
Julien Duponchelle
631e0b0741 Fix save as duplicate the .gns3 file
Fix #247
2015-05-04 13:29:14 +02: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
c11db9f0af Another assert topology fixe
If you can not create the initial project directory the GUI
the project is in an dirty state.

Fix #319
2015-05-04 11:47:33 +02:00
Julien Duponchelle
9ce91345a7 Drop debug 2015-05-04 11:19:14 +02:00
Julien Duponchelle
a6653a3419 Prevent user to enter bad hostname
Fix #311
2015-05-04 11:16:34 +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
8483b0d08e Fixes an issue when the IOU VM template has a console setting. 2015-05-03 18:10:17 -06:00
grossmj
b46be2445a Releasing adding a link. Fixes #235. 2015-05-03 14:03:20 -06:00
grossmj
d45328576f THis should fix RuntimeError: wrapped C/C++ object of type QNetworkReply has been deleted. 2015-05-03 11:55:56 -06:00
Julien Duponchelle
a31cc33c67 Do not crash if terminal doesn't support UTF-8
Fix #306
2015-05-02 15:41:18 +02:00
Julien Duponchelle
b874147b9c Fix windows build
Fix #304
2015-05-02 14:23:19 +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
09046ab89e Fix test suite 2015-04-30 16:59:14 +02:00
Julien Duponchelle
ae7c98ce78 Merge branch 'master' into unstable
Conflicts:
	gns3/version.py
2015-04-29 14:27:55 +02:00
grossmj
62bff009c1 Fixes "show only devices with captures" in the topology summary. 2015-04-28 19:30:44 -06:00
Julien Duponchelle
0c3ea4b05d 1.3.3dev1 2015-04-28 21:48:53 +02:00
Julien Duponchelle
9dde2fbcf8 Version 1.3.2 2015-04-28 21:09:33 +02:00
grossmj
740041a844 Fixes bug when IOS configs are not in VM settings. 2015-04-28 12:49:01 -06:00
grossmj
31bcb8c82d Fixes small issue with Qemu VM monitor. 2015-04-28 12:12:50 -06:00
grossmj
425ef9f059 Fixes #297. 2015-04-28 10:56:18 -06:00
grossmj
e059ecc6c4 Fixes issue when only one port is added after a QEMU VM is created. Fixes #296. 2015-04-28 09:09:35 -06: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
grossmj
425a8c864b Avoid Cygwin warning with VPCS on Windows. 2015-04-27 22:23:27 -06:00
grossmj
56c2b1d9b5 Merge remote-tracking branch 'origin/master' 2015-04-27 21:18:08 -06:00
grossmj
ab74d19584 Fixes issues with QThread handling. 2015-04-27 21:17:56 -06:00
Jeremy
3e197b72a8 Fixes missing title + icon in layer position warning message box. 2015-04-27 19:11:37 -06:00
Jeremy
863e1a2e20 Allows the warning message box to be displayed once only when moving an object to a background layer. 2015-04-27 19:05:21 -06:00
Jeremy
a00c8e0a53 Fixes small issue with old monitor setting. 2015-04-27 18:58:42 -06:00
Jeremy
3b98206942 Check the config path is set when creating a IOU or IOS router. 2015-04-27 18:53:07 -06:00
Jeremy Grossmann
f6cee7297a Merge pull request #292 from GNS3/prevent_empty_file
Prevents writing empty topologies.
2015-04-27 16:33:29 -06:00
Jeremy
6792e3d701 Removes residual link when a NIO cannot be created on the server. Fixes #294. 2015-04-27 15:20:37 -06:00
Jeremy
df1e5aef9b Fixes #294. 2015-04-27 14:46:37 -06:00
Julien Duponchelle
6c3d2926b7 Fix VPCS tests 2015-04-27 21:00:52 +02:00
Julien Duponchelle
302fcfc7d7 Do not crash if an antivirus intercept a message and send non UTF-8
Fix #295
2015-04-27 17:34:39 +02:00
grossmj
42dd9969e4 Avoid C++ runtime error when progress dialog is finished. 2015-04-27 09:30:26 -06:00
grossmj
6a69ce26e3 Merge remote-tracking branch 'origin/master' 2015-04-27 09:17:57 -06:00
grossmj
8d6db96a4c Move FileCopyThread to FileCopyWorker. 2015-04-27 09:17:44 -06:00
Julien Duponchelle
e61a6bae7e If project loading fail fallback to real temporary project
It should avoid the issue with assert in topologyFile
2015-04-27 16:54:12 +02:00
Julien Duponchelle
37989854a7 Do not crash if rotation is a string
Some users have this error. Not sure if it's a bug
or a tutorial which explain how to modify .gns3 files.

Fix #293
2015-04-27 16:37:26 +02:00
Julien Duponchelle
c70f8441e2 I think it's prevent empty topologies
Fix #290
2015-04-27 11:15:27 +02:00
grossmj
f9073055e9 Explicit utf-8 decoding. 2015-04-26 21:19:39 -06:00
grossmj
a22cdd9553 Fixes rare maximum recursion depth exceeded exception. 2015-04-26 17:36:59 -06:00
grossmj
67b8166ebc Check for invalid base VM configuration files. 2015-04-26 17:06:09 -06:00
grossmj
762e498a5c Catch ValueError exception thrown by mmap(): cannot mmap an empty file. 2015-04-26 16:07:08 -06:00
grossmj
48c9862e18 Use QThreads the correct way (moveToThread). 2015-04-26 16:01:35 -06:00
grossmj
a8b7faaed3 Fixes broken serial console connection. 2015-04-25 16:57:09 -06:00
grossmj
c56abc020e Fixes "RuntimeError: wrapped C/C++ object ... has been deleted" exceptions with item links. 2015-04-25 16:40:33 -06:00
grossmj
20067b91eb Allows exported config files to be created even when there is no config set on VMs. 2015-04-25 15:13:35 -06:00
grossmj
43004af842 Do not try to export empty VPCS startup configs. 2015-04-25 14:42:56 -06:00
grossmj
6eb51fdafd Prevent issues when a file with a simple number is considered valid JSON. 2015-04-25 14:16:10 -06:00
grossmj
c3f831727b Explicit error when mmap throw an invalid argument exception. 2015-04-25 12:44:51 -06:00
grossmj
6d328eb376 Do not replace invalid utf-8 characters when reading the iourc file (we catch the exception to tell the user this is an invalid file). 2015-04-25 12:05:31 -06:00
grossmj
b9424f41ab Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems). 2015-04-25 11:58:34 -06:00
Jeremy
b8aa1af55a Save as dialog opens in the projects directory. Fixes #267. 2015-04-24 17:36:55 -06:00
Jeremy
1ba6e361bd Adds Terminal + nc for serial console connections on OSX. Fixes #228. 2015-04-24 17:11:06 -06: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
d688db95e9 Improve warning when non unicode char in iourc 2015-04-24 19:10:05 +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
9c14b42bda Crash report not for developers and new key 2015-04-24 18:42:44 +02:00
Julien Duponchelle
5f04224d57 Do not crash if we can't change IOU permission
Fix #280
2015-04-24 15:42:11 +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
grossmj
37f3bb42a0 More checks when decompressing IOS images. 2015-04-23 21:33:31 -06:00
Jeremy Grossmann
6af2b098e2 Merge pull request #287 from GNS3/no_routers_warning
Warn users that they must provide their router images.
2015-04-23 17:13:02 -06:00
Jeremy
a30a0f8d0b Warn users that they must provide their router images. 2015-04-23 17:12:17 -06: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
b78c37dbbe Display an error and link to the documentation if no router available
It's the most common issue on the support forum.
2015-04-23 14:18:14 +02: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
grossmj
e7fdb804ae Merge remote-tracking branch 'origin/master' 2015-04-22 20:30:16 -06:00
Jeremy Grossmann
6749aee5cd Merge pull request #284 from GNS3/console_print
Display printed messages on both stdout and the GNS3 Qt Console.
2015-04-23 00:57:16 +00:00
grossmj
c3b846bac7 Merge remote-tracking branch 'origin/master' 2015-04-22 18:54:10 -06:00
Julien Duponchelle
5c9bb477b4 Display print( in std console and Qt Console 2015-04-22 17:30:25 +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
57247cd5cd Fix tests and a potential issue where initial_content is not send
Previously we didn't resend an initial_config if we already have
a vm id. The issue is if on the server your VM doesn't exist
because you import a topology it's broken.
2015-04-21 18:11:54 +02:00
Julien Duponchelle
1d7774bcd6 Support for reading an HTTP stream 2015-04-21 12:45:48 +02:00
grossmj
415ab6298e Merge remote-tracking branch 'origin/master' 2015-04-20 13:03:48 -06:00
Julien Duponchelle
806a8efe94 Merge branch 'master' into unstable 2015-04-20 17:25:35 +02:00
Julien Duponchelle
50925c4c30 Fix a crash in qemu loading
Fix #285
2015-04-20 11:12:46 +02:00
grossmj
c9d12184e0 Removes unnecessary progress dialog when listing VirtualBox VMs. 2015-04-19 17:53:30 -06:00
Julien Duponchelle
b0894b1e75 Merge branch 'master' into unstable 2015-04-17 15:07:57 +02:00
Julien Duponchelle
c2781b1f8b Drop unused code for the moment 2015-04-17 15:07:43 +02:00
grossmj
bd2ccc3612 Fixes issues when pushing configs for Dynamips and IOU. 2015-04-17 06:10:55 -06:00
Julien Duponchelle
92faccdd90 Merge branch 'master' into unstable 2015-04-16 18:38:35 +02:00
grossmj
5b42b41dcb Allow for empty initial-config path for IOU VM templates.
Send IOU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
2015-04-15 19:56:55 -06:00
grossmj
8a5c429e87 Allow for empty startup-config and private-config paths for IOS routers. 2015-04-15 19:51:39 -06:00
grossmj
194923ca27 Send QEMU VM settings while creating it (POST) and not using the update API call immediately after (PUT). 2015-04-15 18:40:00 -06:00
grossmj
a2b8391174 Some cleaning. 2015-04-15 18:35:34 -06:00
Julien Duponchelle
c0fd535067 Include resources and tests in pypi packages
Require by Gentoo maintainer

Fix #266
2015-04-14 14:51:14 +02:00
Julien Duponchelle
6629c38d2a Authorize dev users to use different GNS3 version 2015-04-14 13:55:13 +02:00
Julien Duponchelle
58032012aa Fix issue during project import on Windows with non local server
It was a race condition during project import.

Fix #279
2015-04-13 20:10:22 +02:00
Julien Duponchelle
7025accd88 1.4.0 dev1 2015-04-13 15:56:41 +02:00
Julien Duponchelle
cc40afcc4d GNS3 1.3.2 dev1 2015-04-11 14:06:02 +02:00
Julien Duponchelle
3ff2b5f546 Prepare 1.3.1 release 2015-04-11 09:33:08 +02:00
Julien Duponchelle
f5e2309563 Prepare 1.3.1rc4 2015-04-09 10:59:37 +02:00
Julien Duponchelle
75aa6d50b3 Fix crash when save as can't create a directory
Fix #272
2015-04-09 10:11:00 +02:00
Julien Duponchelle
021ad2a5c2 Allow less strict dependencies
Fix #259
2015-04-09 09:52:54 +02:00
Julien Duponchelle
ffe486e284 Prepare 1.3.1rc3 2015-04-07 16:18:12 +02:00
Julien Duponchelle
6132aa7864 Send HTTP errors 400 to the crash report system
Fix #269
2015-04-07 12:34:15 +02:00
Julien Duponchelle
56e55d9cd6 Prepare 1.3.2rc2 2015-04-06 21:36:35 +02:00
Julien Duponchelle
2f0e91d96e Fix race condition during old project import
Fix #268
2015-04-06 21:16:20 +02:00
Julien Duponchelle
32a45ad0d3 Fix tests 2015-04-06 15:31:30 +02:00
Julien Duponchelle
f0d0d6b73a Update crash report key 2015-04-05 11:56:32 +02:00
Julien Duponchelle
03c470846b Prepare 1.3.1rc1 2015-04-05 11:34:07 +02:00
Julien Duponchelle
66f347c973 Fix rare occasion when user manage to put test in port field
Fix #257
2015-04-03 19:36:01 +02:00
Julien Duponchelle
1cf8cae166 Fix a crash when exporting vpcs startup script
Fix #262
2015-04-03 19:22:57 +02:00
Julien Duponchelle
f2585438bd Fix an issue with sending iourc when a topologies is reloaded
Fix #264
2015-04-03 19:18:31 +02:00
Julien Duponchelle
f1e2c5b0d1 Solve issue when iourc contains non ascii characters
Fix #263
2015-04-03 18:04:37 +02:00
Julien Duponchelle
466ba18ec8 Handle corrupted zip file with IOS image
Fix #252
2015-04-03 16:41:45 +02:00
Julien Duponchelle
844fb2e6ec Don't crash if we try to contact a non GNS3 remote server returning JSON
Fix #253
2015-04-03 16:17:30 +02:00
Julien Duponchelle
41d8e2a0ae Skip tests in package 2015-04-01 16:53:35 +02:00
Julien Duponchelle
49ea016980 Check port range
Fix #258
2015-04-01 16:19:08 +02:00
Julien Duponchelle
34a8972ce4 Add a warning about too much ram for IOS
Thanks to eduardo from the community pour l'aide
2015-04-01 15:56:26 +02:00
Julien Duponchelle
a980e1910c Fix crash if project is already closed
Fix #206
2015-04-01 11:31:42 +02:00
grossmj
10758879db Check if wait for connection thread still running before emitting a signal. 2015-03-31 19:59:15 -06:00
grossmj
1e0986b1f6 Check if process files thread still running before emitting a signal. 2015-03-31 19:51:45 -06:00
grossmj
57e16f46ce Missing struct import. 2015-03-31 19:48:34 -06:00
Julien Duponchelle
cc076cc803 Raven is an optionnal dependencies for Debian
Fix #249
2015-03-31 16:51:58 +02:00
Julien Duponchelle
254262c5c4 Fix crash if a dumped topology as no node during save as
Fix #251
2015-03-31 16:47:21 +02:00
grossmj
dc8279c3ea Remove old debug message. 2015-03-30 22:17:28 -06:00
Jeremy
d34d9316f3 Fix: remove old ID references for ATM and Frame-Relay switches. 2015-03-30 13:27:09 -06:00
Jeremy
7496b1de21 Bump version to 1.3.1.dev1 2015-03-30 13:09:05 -06:00
Julien Duponchelle
27eca480d0 1.3.0 release 2015-03-30 18:05:49 +02:00
Jeremy Grossmann
311ec1d5df Merge pull request #246 from GNS3/save_as
Fixes "save as". Fixes #244.
2015-03-30 15:49:44 +00:00
Julien Duponchelle
a66beecd8d Fix tests 2015-03-30 16:55:50 +02:00
Julien Duponchelle
f833c8c598 Correctly close the project before reopening (otherwise port conflict)
Fix #244
2015-03-30 16:24:13 +02:00
Julien Duponchelle
828c3b3c9c Fix save as feature by changing uuid of VM and project 2015-03-30 16:18:45 +02:00
grossmj
d5f432d07c Remove path settings for IOS router templates (redundant with image setting). Fixes #239. 2015-03-29 18:59:33 -06:00
grossmj
0f8404f686 Fixes progress dialog with deleted thread issue. 2015-03-28 22:16:54 -06:00
grossmj
ff46870bf1 Fixes #243. 2015-03-28 21:13:13 -06:00
grossmj
1b55a5a399 Remove old code for Monitor support. 2015-03-27 08:53:52 -06:00
grossmj
afb06cab99 Remove useless code in legacy EtherSwitch class. 2015-03-26 09:35:12 -06:00
Jeremy Grossmann
d26ac237e9 Merge pull request #242 from GNS3/etherswitch
Fix crash in etherswitch router
2015-03-26 15:14:16 +00:00
Julien Duponchelle
41c200a011 Fix crash in etherswitch router
Fix #241
2015-03-26 10:59:57 +01:00
Jeremy
fecabca368 Merge remote-tracking branch 'origin/master' 2015-03-25 14:37:10 -06:00
grossmj
4afb864bef Do not expose the Qemu monitor port in the GUI. 2015-03-23 22:50:20 -06:00
grossmj
7b8690cbb7 Fixes VirtualBox packet capture issue. 2015-03-23 21:40:20 -06:00
grossmj
9a97b4b754 Bump version to 1.3.0.dev3 2015-03-23 21:22:10 -06:00
Jeremy
ad4e1f216d Bump version to 1.3.0.dev3 2015-03-23 18:37:42 -06:00
Jeremy
d6264dfc0b Bump version to 1.3.0rc2 2015-03-23 15:41:10 -06:00
Jeremy
675228d841 Load getting started html from local file. 2015-03-23 15:32:44 -06:00
Jeremy
a28567b48a Adds missing images for getting started dialog. 2015-03-23 14:17:55 -06:00
Jeremy
317ba23e5a Merge remote-tracking branch 'origin/master' 2015-03-23 14:00:53 -06:00
Jeremy
26a30da072 New getting started dialog. Have it completely offline too. 2015-03-23 14:00:48 -06:00
Julien Duponchelle
b35e87780b Changelog 2015-03-23 20:23:45 +01:00
Julien Duponchelle
f9aab38575 Merge pull request #238 from GNS3/untitled
Default project name
2015-03-23 20:17:16 +01:00
Julien Duponchelle
01f8815413 Default project name 2015-03-23 19:27:00 +01:00
Julien Duponchelle
963aabb8f5 Add debug for issue #232 2015-03-23 15:46:48 +01:00
Julien Duponchelle
884fe3c7d9 Update sentry key 2015-03-23 15:27:46 +01:00
grossmj
8866e2aa13 Display adapters in the tooltips in the correct order. 2015-03-22 20:32:54 -06:00
grossmj
07ac9710a8 Open consoles in alphanumerical order. 2015-03-22 14:52:58 -06:00
grossmj
9177962454 Auto idle-PC improvements. 2015-03-22 12:03:46 -06:00
grossmj
a66545dac5 Bump version to 1.3.0.dev2 2015-03-21 22:47:12 -06:00
grossmj
0614dcb3e2 Adds project id when requesting UDP port. 2015-03-21 22:27:40 -06:00
grossmj
359d6c4dba Fixes Thread problem. Fixes #229. 2015-03-21 21:32:59 -06:00
grossmj
242018d0d3 Cancel network requests if the progress dialog itself is canceled.
Avoid closing the preferences dialog or any configuration dialog if there is a pending request.
Fixes #227.
2015-03-21 20:23:04 -06:00
grossmj
d9a81d1d14 Fixes #228 (no alternative interface has been chosen). 2015-03-21 18:06:24 -06:00
grossmj
eeb3b70328 Catch OSError when reading or writing the local server config file. 2015-03-21 17:37:04 -06:00
grossmj
ca6820f91b Fixes GUI that could not be closed when using an already running local server. 2015-03-21 17:16:31 -06:00
grossmj
257eed01df Save configs when project is committed. 2015-03-21 14:52:17 -06:00
Jeremy Grossmann
f08475605a Merge pull request #234 from rashoodkhan/Fix-Issue-18
Del key deletes selected link - Fixes #18
2015-03-21 19:04:34 +00:00
Rashid Khan
64d480be48 Updated the key press event callback to pass the del signal to lower levels 2015-03-21 16:43:44 +05:30
Rashid Khan
f240bca81a Del key deletes selected link - Fixes #18 2015-03-21 02:12:15 +05:30
Jeremy Grossmann
3b67dc4ac0 Merge pull request #233 from GNS3/fix_crash_no_servers
Fix crash is no remote servers is available
2015-03-20 15:39:45 +00:00
Julien Duponchelle
06fba3a816 Fix crash is no remote servers is available
Fix #230
2015-03-20 10:56:31 +01:00
grossmj
1586ba83dc Bump version to 1.3.0.dev1 2015-03-19 19:56:31 -06:00
Julien Duponchelle
ab3a5df71d Bump version and CHANGELOG 2015-03-19 17:36:31 +01:00
Jeremy
9d91629ba7 Bump version to 1.3.0rc1.dev3 2015-03-18 15:40:02 -06:00
Jeremy
0921d51262 Save IOS router configs when saving the project (done right this time). 2015-03-18 15:34:31 -06:00
Julien Duponchelle
4bf898c9d9 Do not use another port if socket is in the TIME_WAIT state 2015-03-18 17:08:43 +01:00
Julien Duponchelle
d346c69033 Fix shutdown local server
Fix #224
2015-03-18 10:48:04 +01:00
grossmj
cd68e387c9 Handle legacy snapshots. 2015-03-17 23:02:01 -06:00
Jeremy
ee3d51cb84 Bump version to 1.3.0rc1.dev2 2015-03-17 19:08:18 -06:00
Jeremy
8e584dbe12 Fixes Qemu VM template with type "default" has the networking legacy mode set to off on Mac and Windows when using an old Qemu version. 2015-03-17 19:07:39 -06:00
Jeremy
90f955e471 Support for snapshots before 1.3. 2015-03-17 18:18:41 -06:00
Jeremy
702fc5ac31 Adds server information to Qemu, VirtualBox and VPCS info boxes. Fixes #217. 2015-03-17 16:48:30 -06:00
Jeremy
746a84b50b Set Jungle news feed refresh to 5 min instead of 1 min. 2015-03-17 15:26:12 -06:00
Jeremy
e8cc49fe14 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-17 13:00:06 -06:00
Jeremy
f5b9c97edb Fixes BadStatusLine exception. 2015-03-17 12:59:05 -06:00
Julien Duponchelle
376178b18a Solve issue with canceled progress dialog
Fix #214
2015-03-17 19:48:46 +01:00
Julien Duponchelle
76521218ae Support sending IOURC 2015-03-17 19:32:44 +01:00
Julien Duponchelle
f9a9c5c00f Send iourc to remote server 2015-03-17 18:59:46 +01:00
Julien Duponchelle
5779e164c6 Another fix for the pkg_ressources recurring bug
Fix #216
2015-03-17 11:51:39 +01:00
Jeremy
e17693905a Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-16 17:36:29 -06:00
Jeremy
92add6b14d Temporarily deactivate IOS router saveconfigs. 2015-03-16 17:36:23 -06:00
Julien Duponchelle
3493de7bb7 If project can't be create do not block application exit
Close #204
2015-03-16 23:11:02 +01:00
Julien Duponchelle
c0557f7974 Add assert in order to found bug #208 2015-03-16 22:44:06 +01:00
Julien Duponchelle
f3e8473f60 Fix missing context at first query
Fix #206, #209
2015-03-16 22:18:34 +01:00
Julien Duponchelle
d88a104237 Fix test 2015-03-16 22:18:34 +01:00
Jeremy
932c2162ad New remote servers were not set as non local by default. 2015-03-16 14:57:42 -06:00
Jeremy
6f3cdfcc4a Adds a 1MB disk by default for EtherSwitch router templates (to store the vlan database). 2015-03-16 13:18:21 -06:00
Jeremy
b311c7991c Fixes alignment options to ignore device labels. 2015-03-16 13:13:40 -06:00
Jeremy
2d25ff399f Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-16 12:52:07 -06:00
Jeremy
66c78f44c2 Prevent using lab instructions in a temporary project. Fixes #210. 2015-03-16 12:51:59 -06:00
Julien Duponchelle
6f68947815 Compute idlepc on remote servers
Fix #212
2015-03-16 19:29:30 +01:00
Julien Duponchelle
c24460962a Display a warning on console if port is already used 2015-03-16 18:57:18 +01:00
Julien Duponchelle
8a0512c956 Display an error if server version is incorect
Related to #211
2015-03-16 16:33:36 +01:00
grossmj
8f146c5161 Removes confreg setting for IOS routers. 2015-03-14 16:31:15 -06:00
grossmj
d5076999fd Fixes vanishing config paths when editing IOS router or IOU device templates 2015-03-14 16:13:47 -06:00
grossmj
69df0b6837 Bump version to 1.3.0rc1.dev1 2015-03-13 22:42:25 -06:00
grossmj
52bef05107 Bump version to 1.3.0beta2 2015-03-13 22:02:28 -06:00
grossmj
261f70c034 Update CHANGELOG 2015-03-13 22:00:19 -06:00
grossmj
c5e53125b5 Make sure local server is running before requesting shutdown. 2015-03-13 21:56:50 -06:00
Jeremy
304e84f901 Alternative local server shutdown (mostly intended for Windows). 2015-03-13 18:57:28 -06:00
Jeremy
ed4e4a1d93 Request user permission to kill the local server if it cannot be stopped. 2015-03-13 17:36:38 -06:00
Jeremy
ae00038493 Support RAM setting for VirtualBox VMs. 2015-03-13 17:13:36 -06:00
Jeremy
d6a60db1b7 Option to automatically take or not a screenshot when saving a project. Fixes #203. 2015-03-13 15:33:04 -06:00
Jeremy
a7fa3762aa Grey out local server preferences if the local server is not activated. 2015-03-13 15:16:37 -06:00
Jeremy
53a2b37c08 Adds "template" to the Wizard titles. 2015-03-13 11:10:31 -06:00
Jeremy
161f7cd514 Bump version to 1.3.0beta2.dev1 2015-03-12 18:51:22 -06:00
Jeremy
6816dc772b Changes to the Qemu VM wizard. 2015-03-12 18:03:29 -06:00
Jeremy
b72df57201 Fixes duplicate VM template entries for Qemu, VirtualBox and IOU. 2015-03-12 16:43:43 -06:00
grossmj
1bfdfc4535 Fixes tests. 2015-03-11 23:09:01 -06:00
grossmj
4149ed361b Update CHANGELOG. 2015-03-11 22:33:51 -06:00
grossmj
2e1b7e940b Bump version to 1.3beta1 2015-03-11 22:09:43 -06:00
grossmj
683c75d308 New title for VM/Devices/routers preference pages. 2015-03-11 22:08:31 -06:00
Jeremy
8e8efbb805 Deactivate auto idle-pc in contextual menu. 2015-03-11 19:32:40 -06:00
Jeremy
67da57e7f6 Optional IOU license key check. 2015-03-11 18:59:57 -06:00
Jeremy
1be5f9f748 Do not try to modify the project path on a remote server. 2015-03-11 18:05:28 -06:00
Jeremy
1ee5260d8c Relative picture paths are saved in the project. 2015-03-11 15:20:20 -06:00
Jeremy
c017586694 Relative path support of IOU, IOS and Qemu images. 2015-03-11 15:04:11 -06:00
Jeremy
ce8df3c833 More checks when automatically starting the local server. 2015-03-11 13:39:32 -06:00
Jeremy
b2a93c04c6 Fixes some issues when moving legacy settings. 2015-03-11 13:11:59 -06:00
Jeremy
2b19ba9572 Checks when opening or saving a project. 2015-03-11 11:14:20 -06:00
grossmj
3649ea612f Fixes GNS3 console issues. 2015-03-10 13:03:38 -06:00
grossmj
4dd9d3b18d Fixes base IOS and IOU base configs. 2015-03-10 12:20:05 -06:00
grossmj
db307ca37d Disk image browsers for HDC and HDD for Qemu VMs. 2015-03-10 12:09:32 -06:00
grossmj
5769927760 Support for HDC and HDD disk images in Qemu. 2015-03-10 11:50:30 -06:00
grossmj
e827e9be39 Renames server.conf and server.ini to gns3_server.conf and gns3_server.ini respectively. 2015-03-10 11:00:32 -06:00
Jeremy
b799e04c39 Remove remote servers list from module preferences + some other preferences re-factoring. 2015-03-09 18:22:53 -06:00
grossmj
0a1c45ef3b Bump version to 1.3.0beta1.dev2 2015-03-09 11:38:02 -06:00
grossmj
d06b6df34d Let the server know about the project name and convert old IOU projects on remote servers. 2015-03-08 19:13:01 -06:00
grossmj
b01854ec72 Bump the progress dialog minimum duration before display to 1000ms. 2015-03-08 12:47:52 -06:00
grossmj
89d2d28ccb Fixes port listing bug. 2015-03-08 12:47:11 -06:00
Jeremy
77f9bd931c Fixes Qemu networking. 2015-03-06 20:08:00 -07:00
Jeremy
9ae0779523 Give a warning when a object is move the background layer. 2015-03-06 17:54:07 -07:00
Jeremy
8d3952fcc9 Implement option to draw a rectangle when a node is selected. 2015-03-06 17:36:19 -07:00
Jeremy
0f759d763d New project icon. 2015-03-06 17:21:51 -07:00
Jeremy
22c7ae1053 Adds default name and placed in the project directory when taking a screenshot 2015-03-06 16:56:48 -07:00
Jeremy
f937aa374f Alignment options. 2015-03-06 15:39:32 -07:00
Jeremy
2a72cb9700 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-06 13:43:54 -07:00
Jeremy
200bc94870 Fixes #196: Import / export of the preferences file. 2015-03-06 13:43:49 -07:00
Julien Duponchelle
168fad78a4 I hope it's fixed the pkg_ressource bug 2015-03-06 21:08:31 +01:00
Julien Duponchelle
741cd4a857 Revert "Drop qemu unused preferences page"
This reverts commit 84ed6cd722.

Conflicts:
	gns3/http_client.py
2015-03-06 19:56:49 +01:00
Jeremy
68102d1523 Includes SSL cacert file path in the warnings. 2015-03-06 11:25:25 -07:00
Jeremy
be72b8c9fd Adds warnings if the cacert.pem file cannot be found. 2015-03-06 11:20:28 -07:00
Jeremy
0d000cf19b Bump version to 1.3.0beta1.dev1 2015-03-06 10:34:02 -07:00
grossmj
0ecf21813e Removes result["code"] references. 2015-03-05 21:09:06 -07:00
Jeremy
e840fbd67d Removes debug messages. 2015-03-05 19:11:51 -07:00
Jeremy
e85ea6a3ef Support for Raven to send crash report from a frozen state. 2015-03-05 16:11:43 -07:00
Julien Duponchelle
f80c6ae6f3 Do not crash when moving a project from temporary to non temp
Fixes #198
2015-03-05 20:46:15 +01:00
Julien Duponchelle
5050cfa7e8 Do not crash if you use Tools => Multi VPCS on a temporary project
Fixes #199
2015-03-05 20:44:50 +01:00
Julien Duponchelle
23d87cd926 Do not crash if egg cache is broken on user computer
Fixes #200
2015-03-05 20:26:58 +01:00
Julien Duponchelle
c5a2974d53 Report more informations about the OS 2015-03-05 20:14:11 +01:00
Julien Duponchelle
3aa0736478 Add more informations to the crash report 2015-03-05 17:38:40 +01:00
Julien Duponchelle
92f6aab290 Cleanup old codes 2015-03-05 17:17:31 +01:00
Julien Duponchelle
12322b4be0 Do not crash if query id is missing 2015-03-05 17:03:44 +01:00
Julien Duponchelle
7a1b0e7a48 Fix a crash when you change project and some query a remaining 2015-03-05 14:50:24 +01:00
Jeremy
1914522c3a Fixes adapters restoration when loading a pre-1.3 project. 2015-03-04 18:55:17 -07:00
Jeremy
ad07bd9bb9 Fixes adapter bug with VirtualBox. 2015-03-04 18:24:15 -07:00
Julien Duponchelle
75d2c6686d Notation harmonisation 2015-03-03 18:48:11 +01:00
Jeremy
9253b7f841 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-03 10:47:12 -07:00
Jeremy
60970134e6 Bump to version 1.3.0alpha1 2015-03-03 10:47:02 -07:00
Julien Duponchelle
ba7136f70a Changelog update 2015-03-03 18:44:28 +01:00
Jeremy
dc2c592ee1 Bump to version 1.3alpha1 2015-03-03 10:43:44 -07:00
Julien Duponchelle
f910eb7946 Fix tests 2015-03-03 15:09:15 +01:00
Julien Duponchelle
d72db0db77 Fix GUI 2015-03-03 15:08:42 +01:00
Julien Duponchelle
a251ec7739 Hide cloud memu 2015-03-03 11:46:11 +01:00
grossmj
a55d0e3b04 Fixes some more path initialization issues. 2015-03-02 23:34:21 -07:00
grossmj
5dc5f31d07 Check local server path is valid and fixes issues when starting on a clean system. 2015-03-02 22:54:53 -07:00
Jeremy
f97efabcdb Reload the server config only once when clicking on apply or ok in the preferences. 2015-03-02 15:42:47 -07:00
Jeremy
ba7995ab3f Prevent users to use the unfinished cloud integration. 2015-03-02 14:59:10 -07:00
Jeremy
bb1723e2bb Fixes old projects loading issue with Qemu. 2015-03-02 14:37:48 -07:00
Julien Duponchelle
95866fa5c2 Fix reopening of qemu topo 2015-03-02 21:15:53 +01:00
Julien Duponchelle
8a19456657 Reload config via API 2015-03-02 21:09:17 +01:00
Julien Duponchelle
4e24a9f4dd Fix blocking application when the server is dead 2015-03-02 19:34:52 +01:00
Julien Duponchelle
7a5f2412c0 Fix copy paste in GNS3 console
Fix #192
2015-03-02 19:14:39 +01:00
grossmj
cf334b1ea4 Check if executable path is set before os.path.exists() 2015-03-02 09:42:37 -07:00
grossmj
eb5a723991 Fixes executable path bugs (when shutil.which returns None). 2015-03-02 09:40:07 -07:00
grossmj
02e71a79bf Fixes auto save for converted projects. 2015-03-01 21:16:22 -07:00
grossmj
8eae452765 Fixes local server shutdown. 2015-03-01 21:14:27 -07:00
grossmj
05dea91c93 Fixes style loading. 2015-03-01 19:28:20 -07:00
grossmj
3f4dd04461 Check executable paths are valid when launching the GUI and find them if necessary. 2015-03-01 17:31:53 -07:00
grossmj
c2bbf98947 Fixes relative path in project file for IOU images. 2015-03-01 16:24:26 -07:00
grossmj
65be634999 Device IDs were not restored. 2015-03-01 13:05:04 -07:00
grossmj
c027648cb7 Bump to version 1.3.dev3 2015-02-28 21:39:52 -07:00
grossmj
96ff09885d Fixes image loading & some cleaning. 2015-02-28 21:35:47 -07:00
grossmj
cc08b18e9e Fixes WIC ports were not created. 2015-02-28 18:57:22 -07:00
grossmj
73186a771e Optional AUX console port allocation for Dynamips VMs. 2015-02-28 16:20:27 -07:00
grossmj
c7a266cdfc Fixes picture save and load in projects. 2015-02-28 14:58:58 -07:00
grossmj
a74b749421 Auto start the local server by default. 2015-02-28 14:52:32 -07:00
Jeremy
37a87dbdee Sync Dynamips config file. 2015-02-27 19:36:21 -07:00
Jeremy
054b9e3dd2 Some changes with config files on Windows. 2015-02-27 18:08:34 -07:00
Jeremy
9694eb5940 Delete temporary projects after the path has been changed on the server. 2015-02-27 16:50:19 -07:00
Jeremy
dafb36e3e9 Normalize paths in the local server config file on Windows. 2015-02-27 13:27:40 -07:00
Julien Duponchelle
364c771b96 Turn off pep8 verification in travis because the test are already slow
to run
2015-02-27 17:25:18 +01:00
Julien Duponchelle
93ba13b6a9 Try to fix a strange error in travis build 2015-02-27 17:01:41 +01:00
Julien Duponchelle
1969ad8dd9 Add Changelog file 2015-02-27 16:14:14 +01:00
Julien Duponchelle
0f1713ef32 Use the same logging code for gui and server
We have no longer references to tornado in the source
code.
2015-02-27 15:48:20 +01:00
Julien Duponchelle
4ddda18d5b Fix crash if you don't have gns3converter 2015-02-27 15:47:44 +01:00
Julien Duponchelle
7df1e92a36 Fix crash after creation of a link to a blank area
Fixes #191
2015-02-27 14:32:16 +01:00
Julien Duponchelle
998794e11e Keep path of vpcs startup file 2015-02-27 14:32:16 +01:00
grossmj
06036c41cd Fixes regression with Dynamips router. 2015-02-26 22:59:01 -07:00
Jeremy
cab6b7783b Do not hide the server console on Windows when debugging. 2015-02-26 19:29:38 -07:00
Jeremy
8abfe5ff7d Default style is now "Classic" on Windows. 2015-02-26 17:17:52 -07:00
Jeremy
9ce152bc42 Reactivates built-in module + some cleaning. 2015-02-26 16:38:02 -07:00
Jeremy
fb8ce37e4d Set the node name in setup. 2015-02-26 16:18:54 -07:00
Julien Duponchelle
b16d756941 Automatically save a converted project #189 2015-02-26 17:11:51 +01:00
Julien Duponchelle
72ad516140 Fix tests 2015-02-26 10:52:03 +01:00
Jeremy
56ed695544 IOU + VirtualBox conversion of old projects. 2015-02-25 18:55:35 -07:00
Jeremy
f567ab8b11 Fixes issue #184 (broken snapshots support). 2015-02-25 16:49:53 -07:00
Jeremy
968b879b0b Do not create the project instance twice when loading one. 2015-02-25 16:09:53 -07:00
Jeremy
05ffa8bc03 Fixes typos "egals" -> "equals". 2015-02-25 11:53:39 -07:00
Jeremy
ac4bfcb098 Allow for having no base configs for IOS and IOU. Fixes #144. 2015-02-24 16:45:55 -07:00
Julien Duponchelle
2be612e042 Crash report for GUI 2015-02-24 20:26:17 +01:00
Julien Duponchelle
1b1d7d9481 Report error setting from the GUI 2015-02-24 19:45:29 +01:00
Daniel Lintott
12ab8612cb Add GNS3 converter version to console version output 2015-02-24 15:40:52 +01:00
Julien Duponchelle
71696ab48b Fix race condition in project opening
Fixes #186
2015-02-24 15:34:33 +01:00
Julien Duponchelle
6d7e05a9e0 Fix the bug which prevent GUI close if server is dead
Fixes #185
2015-02-24 14:51:54 +01:00
Jeremy
82e13c7bed Move projects_path to the local server settings.
Properly start & stop the local server from the preferences.
2015-02-23 17:07:50 -07:00
Jeremy
df27824e3f Do not alter the scene when taking a screenshot. 2015-02-23 12:36:00 -07:00
Julien Duponchelle
fac0ccfe68 Note topology revision 2015-02-23 19:41:20 +01:00
Julien Duponchelle
d38536ff75 PEP8 2015-02-23 18:24:14 +01:00
Julien Duponchelle
56e0cafe0d Remove non pertinent TODO after test 2015-02-23 18:20:58 +01:00
Julien Duponchelle
c55bbe339f Fix tests 2015-02-23 17:49:04 +01:00
Julien Duponchelle
84ed6cd722 Drop qemu unused preferences page 2015-02-23 17:37:29 +01:00
Jeremy
dbcf8beda9 Restore the scene rect after a screenshot.
fixes idle-pc save to image ref.
2015-02-22 20:14:59 -07:00
Jeremy
dbe37d6bc0 Fixes aiohttp.errors.ClientDisconnectedError errors when SIGINT is received. 2015-02-22 12:36:44 -07:00
Jeremy
73b5652b5c Default NVRAM and Idle-PC for some IOS images. 2015-02-21 17:24:40 -07:00
Jeremy
2fea220ed8 Idle-PC and auto idle-pc for Dynamips. 2015-02-20 16:53:51 -07:00
Julien Duponchelle
8457854483 Zuper progress dialog, with progress bar when multiples queries waiting 2015-02-20 21:31:44 +01:00
Julien Duponchelle
f00ad62b96 Fix crash when click on blank project and after that you cancel 2015-02-20 17:44:00 +01:00
Julien Duponchelle
cd65f4c4d7 Qemu support 2015-02-20 17:31:49 +01:00
Julien Duponchelle
f9f4c59e55 Ignore Qt creator autosave files 2015-02-20 17:31:48 +01:00
Jeremy
402505e2cb Idle-PC proposals for Dynamips. 2015-02-19 19:14:30 -07:00
Jeremy
4b13e4f7c6 Fixes node configurator & new settings. 2015-02-19 17:28:42 -07:00
Jeremy
d0e9cc44e0 Dynamips import/export configs. 2015-02-19 16:04:15 -07:00
Jeremy
9af4e0e15e Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 18:40:16 -07:00
Jeremy
c2f77ba39f Fixes capture directory path. 2015-02-18 18:40:01 -07:00
grossmj
7db4e397f0 Set progress dialog timer to 500ms. 2015-02-18 17:54:51 -07:00
Jeremy
399db108f3 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 14:11:13 -07:00
Julien Duponchelle
9de5e5f984 One click exit ! Lazy people will be happy 2015-02-18 21:38:37 +01:00
Jeremy
30505269a6 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 13:26:56 -07:00
Julien Duponchelle
7b1f1268fc Progress dialog is back 2015-02-18 20:49:20 +01:00
Jeremy
fd23957e2e Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 12:48:42 -07:00
Julien Duponchelle
98dc5890fd Revert "Progress dialog"
This reverts commit 4e055a3eb0.
2015-02-18 20:47:40 +01:00
Jeremy
4a1eeeedd1 Merge remote-tracking branch 'origin/rest-api' into rest-api
Conflicts:
	gns3/http_client.py
2015-02-18 12:39:04 -07:00
Jeremy
bf3d11cbdc Fixes UDP allocation HTTP call with context. 2015-02-18 12:38:29 -07:00
Julien Duponchelle
4e055a3eb0 Progress dialog 2015-02-18 19:36:45 +01:00
Julien Duponchelle
e703f220ca Fix missing context 2015-02-18 18:24:31 +01:00
Julien Duponchelle
9e4ec60d47 A work in progress, progress dialog 2015-02-18 17:28:24 +01:00
Julien Duponchelle
6ea960f2b7 Fix IOU image dir 2015-02-18 16:45:39 +01:00
Julien Duponchelle
2d98d34137 Restore progress dialog, full credit to jeremy :) 2015-02-18 16:18:45 +01:00
Julien Duponchelle
42c2aec975 Fix bad location of the gns3_gui.conf file on OSX 2015-02-18 15:26:41 +01:00
Julien Duponchelle
b0f062234b Cleanup 2015-02-18 15:23:11 +01:00
Julien Duponchelle
16b3cb581b Set le répertoire des images IOU 2015-02-18 12:23:28 +01:00
Julien Duponchelle
65e5bc8da0 Fix tests 2015-02-18 11:30:44 +01:00
grossmj
82815cd697 Remove Dynamips hypervisor port range option. 2015-02-17 23:12:13 -07:00
Jeremy
87cabfeaeb Use context instead of partials for HTTP queries. 2015-02-17 18:39:09 -07:00
Julien Duponchelle
4dcd5712e8 Support initial config content 2015-02-17 18:22:50 +01:00
Julien Duponchelle
04a63d5e17 Fix a bug where remote server as seen as a local server 2015-02-17 15:29:44 +01:00
Julien Duponchelle
6b330eccef Fix tests after settings changes
@grossmj we need to discuss about the image directory
2015-02-17 15:17:24 +01:00
Julien Duponchelle
3b5a0a3067 Support IOU capture. Create a notion of context for HTTP queries 2015-02-17 11:34:55 +01:00
Jeremy
9c9c216f6c Save Qemu VMs using LocalConfig instead of QSettings. 2015-02-16 18:46:27 -07:00
Jeremy
e4cdecf653 Save IOU devices using LocalConfig instead of QSettings. 2015-02-16 18:36:46 -07:00
Jeremy
544e11ac9c Save Dynamips VM configs when closing a project. 2015-02-16 18:21:10 -07:00
Jeremy
3fe6f27c57 Save Dynamips routers using LocalConfig instead of QSettings. 2015-02-16 17:59:40 -07:00
Jeremy
c35a911d36 Adapter settings and configs for Dynamips VMs. 2015-02-16 16:53:50 -07:00
Jeremy
e7bb823677 readBaseConfig method for VMs 2015-02-16 13:10:21 -07:00
Julien Duponchelle
f79d7b8550 Support old IOU topology loading 2015-02-16 15:45:25 +01:00
Julien Duponchelle
11e5ebeacf Travis very verbose 2015-02-16 15:06:15 +01:00
Julien Duponchelle
2ed3da2328 Save initial config path in the topology 2015-02-16 11:44:46 +01:00
Julien Duponchelle
3d0a30e38c PEP8 2015-02-16 10:33:45 +01:00
grossmj
a8a8cd8158 Dynamips devices packet capture. 2015-02-15 17:45:53 -07:00
grossmj
b518520dd6 Dynamips devices support (packet capture to complete). 2015-02-15 12:18:12 -07:00
Julien Duponchelle
9e7662c255 Upload initial_config 2015-02-14 10:53:13 +01:00
Jeremy
0b70872163 Packet capture for Dynamips VMs. 2015-02-13 15:41:56 -07:00
Julien Duponchelle
cc3999e9f6 Slot => adapter 2015-02-13 19:46:08 +01:00
Julien Duponchelle
aa3e137c9a Fix IOU loading in GUI 2015-02-13 17:39:45 +01:00
Julien Duponchelle
c5dbf2d54d Compatible with PEP8 1.6 2015-02-13 11:23:32 +01:00
Julien Duponchelle
5770cfad3b PEP8 2015-02-13 10:51:15 +01:00
grossmj
9204ffae78 Successfully create a Dynamips router from the GUI via HTTP 2015-02-12 19:24:40 -07:00
Julien Duponchelle
5340ea400c Support network for IOU 2015-02-12 22:27:10 +01:00
Julien Duponchelle
990ccee91d Successfully create an iou device from the GUI via HTTP 2015-02-12 21:57:28 +01:00
grossmj
837885cb04 Fixes missing Node references. 2015-02-12 11:49:09 -07:00
Jeremy Grossmann
af00609edc Merge pull request #181 from GNS3/refactor_base_vm
Move duplicate code to node.py
2015-02-12 18:13:36 +00:00
Julien Duponchelle
8cf974701a Create a VM class 2015-02-12 19:04:54 +01:00
Julien Duponchelle
82cd586950 Move duplicate code to node.py 2015-02-12 14:22:18 +01:00
Julien Duponchelle
e7f74373dd Drop console port settings, now it's global 2015-02-12 11:05:53 +01:00
grossmj
a9bb8f7130 Prevent the scroll bars to be shown in the news dock widget. 2015-02-10 21:44:39 -07:00
Julien Duponchelle
68a084911d Merge branch 'allow_no_local_server' into rest-api 2015-02-10 16:52:21 +01:00
Julien Duponchelle
d02bb86083 Avoid side effect in test 2015-02-10 16:52:14 +01:00
Julien Duponchelle
408291403b Clean tests 2015-02-10 16:51:49 +01:00
Julien Duponchelle
5afe8d2805 Fix crash when changing server binary 2015-02-10 16:51:26 +01:00
Julien Duponchelle
1482b65c64 /version JIT 2015-02-10 16:40:46 +01:00
Julien Duponchelle
84173e2933 PEP 8 & tests 2015-02-10 11:09:08 +01:00
Jeremy
a0bd0c422e Create the directory for the gns3_gui.conf file. 2015-02-09 18:22:39 -07:00
Julien Duponchelle
6ebbc369ca WIP for allowing usage without a local server 2015-02-09 23:34:44 +01:00
Julien Duponchelle
72fa14cb8f Avoid side effect in test 2015-02-09 22:47:59 +01:00
Jeremy
7bed153cc8 Start local server with correct host/port (127.0.0.1:8000 by default). 2015-02-09 13:43:52 -07:00
Julien Duponchelle
07f0ccacb6 Fix bug of creation of recursive directory at topology opening 2015-02-09 18:56:20 +01:00
Julien Duponchelle
b95f75354e Reset HTTPCLient internal state between tests 2015-02-09 11:15:14 +01:00
Julien Duponchelle
15bf587616 Fix crash in tests 2015-02-09 10:45:36 +01:00
grossmj
e6dc1b5b09 Recent files with the new JSON local config. 2015-02-08 15:05:44 -07:00
grossmj
0839b06ec2 VirtualBox packet capture. 2015-02-08 14:44:56 -07:00
grossmj
67e9410f43 Transitioning to a JSON based config (away from QSettings). 2015-02-08 12:48:45 -07:00
grossmj
789409f74f Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-06 17:31:34 -07:00
grossmj
141d588596 VirtualBox implementation complete. 2015-02-06 17:31:13 -07:00
Julien Duponchelle
3a6a11bab3 Close project on all servers 2015-02-06 22:50:12 +01:00
Julien Duponchelle
bc17f8c865 Virtualbox support remote servers 2015-02-06 21:50:42 +01:00
Julien Duponchelle
9b434d4928 VPCS support remote server 2015-02-06 17:35:31 +01:00
Julien Duponchelle
56bbe64b35 The project class know if a project is created on remote server 2015-02-06 16:46:19 +01:00
Julien Duponchelle
5504e0e7b7 Fix an error in server config file writting 2015-02-06 14:47:00 +01:00
Julien Duponchelle
890c211fe2 Drop -files 2015-02-06 12:12:21 +01:00
Julien Duponchelle
14be61b5d8 PEP8 2015-02-06 12:03:01 +01:00
Julien Duponchelle
5455689f9b Save as ok? 2015-02-05 19:22:43 +01:00
Julien Duponchelle
68e10ea066 Solve a bug of double temporary project creation at startup 2015-02-05 14:26:16 +01:00
Julien Duponchelle
c2d3a2d94f Save as anonymous work 2015-02-05 11:38:41 +01:00
Julien Duponchelle
3905b9e089 Add travis badges 2015-02-05 10:56:27 +01:00
Jeremy
ce15caed4f Use deleteLater() for threads. 2015-02-04 19:04:37 -07:00
Jeremy
1c51219f40 Add project_id in all VM calls. 2015-02-04 17:13:35 -07:00
Julien Duponchelle
85d8446e93 Support temporary project Work in progress 2015-02-04 22:51:13 +01:00
Jeremy
83b5812ade Fixes merge issues. 2015-02-04 14:27:30 -07:00
Jeremy
488e26af6d Merge remote-tracking branch 'origin/rest-api' into rest-api
Conflicts:
	gns3/project.py
2015-02-04 13:53:39 -07:00
Jeremy
41549ae60d Explicit ID names, remove {uuid} from URLs and add vms in URLs for VMs. 2015-02-04 13:48:29 -07:00
Julien Duponchelle
1ede748d81 Display copyright 2015 in the console view 2015-02-04 19:40:38 +01:00
Julien Duponchelle
5913ba9491 Fix crash after previous commit 2015-02-04 19:38:46 +01:00
Julien Duponchelle
caaad2da85 Drop temporary directory settings in UI 2015-02-04 18:29:02 +01:00
Julien Duponchelle
143318dd34 Project commit 2015-02-04 18:05:07 +01:00
Julien Duponchelle
1d48e27c80 Api v1 2015-02-04 10:36:41 +01:00
Julien Duponchelle
c6ea09a031 Fix topology test 2015-02-04 09:17:03 +01:00
Jeremy
d75eca55ad Use project_id instead of project_uuid for the API. 2015-02-03 18:40:13 -07:00
Jeremy
e8432d0000 Change URL for projects: /project becomes /projects and project_id is used instead of uuid. 2015-02-03 18:23:11 -07:00
Julien Duponchelle
276f9dbfdb Fix project reopen 2015-02-03 22:28:20 +01:00
Julien Duponchelle
cf1a5ba382 Fix segfault 2015-02-03 20:17:22 +01:00
Julien Duponchelle
c551db1556 Do not send base config when reloading a VPCS 2015-02-03 20:12:28 +01:00
Julien Duponchelle
0caf3ea31a In case of error during project close continue. 2015-02-03 16:17:47 +01:00
Julien Duponchelle
a1bd6e34cc Cleanup temporary project code 2015-02-03 15:21:51 +01:00
Julien Duponchelle
c8d1c60bd8 This should improve travis build speed 2015-02-03 14:54:03 +01:00
Julien Duponchelle
41b2f64db4 Fix SIP version for build 2015-02-03 14:28:56 +01:00
Julien Duponchelle
3fc011e75e Fix travis build
Thanks to @dlintott
2015-02-03 14:24:24 +01:00
Julien Duponchelle
42a31a4479 Fix segfault 2015-02-03 11:37:35 +01:00
Julien Duponchelle
b044ff5f35 Update pyqt in build 2015-02-03 11:36:36 +01:00
Julien Duponchelle
843463a97f Auto pep8 2015-02-03 10:55:59 +01:00
Julien Duponchelle
c14765f69d Reuse old project uuid when reopen a topology 2015-02-03 10:48:06 +01:00
Jeremy
35e1b194c1 Fixes problem when retrieving the VirtualBox VM list. 2015-02-02 18:55:18 -07:00
Jeremy
912c2aeb03 Only save the necessary settings in the server config file. Rename setting path to vpcs_path. 2015-02-02 15:00:16 -07:00
Julien Duponchelle
25eeea20c7 Project uuid is loaded from topology.
A bug prevent the project restoration, this require more investiguation
2015-02-02 20:10:03 +01:00
Julien Duponchelle
67f5964aff Fix topology test 2015-02-02 19:44:39 +01:00
Julien Duponchelle
0bf7933d4d Drop project_settings Part 2 2015-02-02 17:19:07 +01:00
Julien Duponchelle
c5e868dc1b Drop project_settings PART 1 2015-02-02 16:20:17 +01:00
Jeremy
5f7e80643a Save port ranges in the config file. 2015-02-01 20:44:43 -07:00
Jeremy
bcd85eec82 Local server auto start. 2015-02-01 17:42:28 -07:00
Jeremy
55667b78df Clean local server settings save and load. 2015-02-01 17:39:23 -07:00
Jeremy
0ac429a0b6 New ConnectToServer to wait for a server connection to be complete. 2015-02-01 17:32:06 -07:00
Jeremy
bbc51b83b8 Use LocalServerConfig for VPCS and VirtualBox. Move port ranges in the local server preferences. 2015-01-31 19:49:46 -07:00
Jeremy
d0c709fa2f LocalServerConfig to load/save local server settings. 2015-01-31 19:47:29 -07:00
Jeremy
0dfcbb778b Completing VirtualBox VM implementation. 2015-01-31 15:09:35 -07:00
Jeremy
6ed70380b6 Deactivate modules but VPCS and VirtualBox. 2015-01-31 14:00:09 -07:00
Jeremy
4d844a1608 Remove error code + more VirtualBox integration. 2015-01-30 19:37:17 -07:00
Jeremy
9af650def7 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-01-30 14:41:56 -07:00
Jeremy
1935bbf510 Do not delete the QNetworkResponse in the slot connected to finished() signal. 2015-01-30 14:41:51 -07:00
Julien Duponchelle
eadfd7fd48 Fix double ask unsaved before close 2015-01-30 22:26:20 +01:00
Julien Duponchelle
1c33ad4527 Close windows close the projects 2015-01-30 21:47:06 +01:00
Julien Duponchelle
ac6cc76d67 Rename getter in order to follow project conventions 2015-01-30 18:08:18 +01:00
Julien Duponchelle
6adc8014da PEP 8 2015-01-30 17:53:14 +01:00
Julien Duponchelle
138df7c2c0 VPCS import config 2015-01-30 17:50:44 +01:00
Julien Duponchelle
efb8cbc9f4 Update VPCS 2015-01-30 16:31:00 +01:00
Julien Duponchelle
f0d03f2196 VPCS delete NIO 2015-01-30 10:56:50 +01:00
Julien Duponchelle
ed4077b1ff Test topology loading and signal mocking 2015-01-29 19:26:50 +01:00
Julien Duponchelle
20fe982df2 Topology open work 2015-01-29 17:22:42 +01:00
Julien Duponchelle
25c33b69f3 Test topology loading 2015-01-29 15:01:50 +01:00
Jeremy
722c8f38d1 GET, DELETE and PUT support for the http client. 2015-01-28 19:33:07 -07:00
Jeremy
3bb01d8eaf Ping between two VirtualBox VMs using REST API work. 2015-01-28 18:01:19 -07:00
Jeremy
d4d7c64442 Use UUID instead of vpcs_id and update post requests. 2015-01-28 17:57:52 -07:00
Jeremy
4c559f20a3 Optional body for post requests. 2015-01-28 17:54:22 -07:00
Julien Duponchelle
81fa1bc43f Pep8 2015-01-28 16:50:07 +01:00
Julien Duponchelle
0c63f37a8e Test dump topology 2015-01-28 16:45:29 +01:00
Julien Duponchelle
90d20b7e1a Capture log in py.test 2015-01-28 15:55:48 +01:00
Julien Duponchelle
1cad2a94ec Py test timeout 2015-01-28 15:54:44 +01:00
Julien Duponchelle
92d95bd585 Cleanup the HTTP client code 2015-01-28 13:50:21 +01:00
Julien Duponchelle
39da7cbe22 Exclude build from the travis PEP8 check 2015-01-28 13:16:57 +01:00
Julien Duponchelle
061c603831 Add pep8 as dev dependencies 2015-01-28 11:34:08 +01:00
Julien Duponchelle
8fb92a316a PEP8 2015-01-28 11:13:10 +01:00
Julien Duponchelle
527a571bf6 Improve tests for VPCS device 2015-01-28 10:36:55 +01:00
Jeremy
b2e81e3070 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-01-27 13:37:52 -07:00
Jeremy
1a389d68c5 Fixes logging to file and debug command. 2015-01-27 13:37:44 -07:00
Julien Duponchelle
5908b4aaf2 Fix travis tests 2015-01-27 19:22:43 +01:00
Julien Duponchelle
684781aadd Test VPCS 2015-01-27 19:14:30 +01:00
Julien Duponchelle
2b771772e2 Ping between two VPCS using REST API work 2015-01-27 16:04:24 +01:00
Julien Duponchelle
ff439a2b0b Turn off irc notification 2015-01-27 15:05:56 +01:00
Julien Duponchelle
ea8bcae586 Create a VPCS using the project uuid 2015-01-27 12:13:39 +01:00
Julien Duponchelle
3e7fff0c35 Ugly project creation when application start 2015-01-27 11:24:22 +01:00
Julien Duponchelle
81bd3b4893 Start posting data to rest api 2015-01-26 17:13:51 +01:00
Jeremy
68e9842bd9 Bump version to 1.2.3 2015-01-16 17:58:09 -07:00
Jeremy
54552031d8 Fixes broken -netdev + legacy virtio in Qemu support. 2015-01-16 17:44:07 -07:00
Jeremy
bfabccc60d Fixes bug with WICs interfaces no showing up in the port list. 2015-01-16 15:17:03 -07:00
Jeremy
17fe1df029 Fixes #175. 2015-01-16 11:36:44 -07:00
Jeremy
e10ccea86c Fixes #177 (broken ASA kernel/initrd) file browsers). 2015-01-16 10:51:07 -07:00
Jeremy
fa470bd8bf Fixes missing devices from the node view that use a remote server. 2015-01-16 10:43:25 -07:00
Jeremy
9b7a2cda15 Bump version to 1.2.2 2015-01-15 17:44:10 -07:00
Jeremy
d0d60d26da Fixes problem when comparing directories on Windows. 2015-01-15 16:01:06 -07:00
Jeremy
b2c14c1218 Copy images to their default directory (if chosen by the user). No more symlinks. 2015-01-15 15:26:56 -07:00
Jeremy
2d59dc2c72 Fixes bug when IOS router slot1 doesn't exist. 2015-01-15 11:24:07 -07:00
Jeremy
bb48cdf0a0 Update the VM name in VirtualBox for linked clones. 2015-01-14 16:48:32 -07:00
Jeremy
e00dcbcd92 Do not make SuperPutty the default console on Windows. 2015-01-12 17:48:32 -07:00
Jeremy
5ab7d6e94e Fixes resource path when frozen. 2015-01-12 17:23:51 -07:00
Jeremy
d1da9adc88 Adds Julien in the About dialog. 2015-01-12 16:50:26 -07:00
Jeremy
1455babd62 Bump version to 1.2.2.dev2 2015-01-12 16:26:40 -07:00
Jeremy
fc8529323c Fixes bugs when changing the hostname for Dynamips switches and hub. 2015-01-12 15:07:48 -07:00
Jeremy
f9130794ee Fixes bug when changing the hostname of a cloud or host. 2015-01-12 14:50:57 -07:00
Jeremy
e62d6c0edd Adds change hostname action and VirtualBox name in tooltip. 2015-01-11 18:25:36 -07:00
Jeremy
8d1dc4b090 Fixes blocked console. 2015-01-11 14:40:46 -07:00
Jeremy
acf6cf6ea2 Fixes base config dir path. 2015-01-08 12:42:01 -07:00
Jeremy
d9ee44f90a Default jungle news image. 2015-01-07 17:52:11 -07:00
Jeremy
bd6da5db9a Updates default jungle news image. 2015-01-07 16:03:06 -07:00
Jeremy
4b3ade9b48 Fixes default news loading on Windows. 2015-01-06 19:18:33 -07:00
Jeremy
9daed7e0d4 Adds short port names to the topology summary. 2015-01-06 18:41:44 -07:00
Jeremy Grossmann
4ef61e7af1 Merge pull request #159 from shmygov/vboxuser
Run VirtualBox as another user
2015-01-05 16:19:10 -07:00
Jeremy
851f2d0517 Put base configs into a base_configs directory. 2015-01-05 15:32:01 -07:00
Jeremy
5e5e04de8e Fixes #167 2015-01-04 19:01:33 -07:00
Jeremy
e8b2f952af Support for IOURC file on the server side. 2015-01-04 15:59:00 -07:00
Jeremy
85b5d10e5a Bump the maximum network adapters to 32 for Qemu. 2015-01-03 16:16:07 -07:00
Jeremy
b916ca7bfb EtherSwitch routers can be added and configured like other IOS routers. 2014-12-31 12:29:38 -07:00
Jeremy
46190154f6 Fixes broken file browsers in QEMU wizard. 2014-12-30 17:20:18 -07:00
Jeremy Grossmann
194552d2d3 Merge pull request #172 from planctechnologies/gns-137a
Fix cloud project load
2014-12-28 13:58:27 -07:00
Jerry Seutter
a736cbc4d5 Fix cloud project load 2014-12-28 13:22:00 -07:00
Jeremy
0310ecdfd0 Import & export config options in contextual device menu. 2014-12-26 20:33:35 -07:00
Jeremy
28bbc8bbe9 Refactor how to deal with initial base configs for IOS, IOU and VPCS. 2014-12-24 18:02:00 -07:00
Jeremy
f36ef66623 Copy base config templates to the directory where GNS3 settings are stored. 2014-12-24 17:19:16 -07:00
Jeremy
16ba51aa8c More checks on minimum RAM for IOS routers and updates default values to match the latest IOS image requirements. 2014-12-24 15:46:59 -07:00
Jeremy
52847d1fad Auto start project support. 2014-12-24 14:13:59 -07:00
Jeremy
4733cc8a3e Fixes SuperPutty command line to connect to devices with spaces in their hostname. 2014-12-24 13:51:47 -07:00
Jeremy
aecf61135f Merge remote-tracking branch 'origin/master' 2014-12-24 13:12:19 -07:00
Jeremy
252d86eb70 Avoid uninitialized nodes to be saved in the project file. 2014-12-24 13:12:10 -07:00
grossmj
438b0fe9d3 Adds more network interface options to the Qemu VM configuration Ui as well as descriptions for all NICs. 2014-12-23 18:34:40 -07:00
grossmj
f6c58b5a28 Gives the possibility to apply or not the same text to all selected items when editing notes. 2014-12-23 15:46:13 -07:00
Jeremy Grossmann
dee2f94c38 Merge pull request #171 from shmygov/suspendqemu
Add QEMU monitor port to control running QEMU VMs.
2014-12-23 12:28:05 -07:00
grossmj
1fc4dec5ca Fixes bug when importing Host node with UDP NIOs. 2014-12-23 11:24:28 -07:00
Dmitry Shmygov
d7ab12bc61 Add QEMU monitor port to control running QEMU VMs 2014-12-23 15:11:21 +03:00
Jeremy
1f50612a16 VPCS multi-host support. 2014-12-22 19:08:56 -07:00
Jeremy Grossmann
43715f1e34 Merge pull request #170 from noplay/fix_travis
Fix travis build
2014-12-22 09:20:54 -07:00
Julien Duponchelle
1bf0ff69d8 Fix travis build
The travis build is now working. But tests are failling, i
think it's normal and now we need to fix them.

I commented a test in cloud inspector because the test
run forever.
2014-12-22 10:51:54 +01:00
Jeremy
abf992dfb8 Fixes problem with SuperPutty. Make it the default on Windows. 2014-12-20 17:21:33 -07:00
Jeremy
707dfee696 Prevents application to crash on Windows when importing GNS3 config file. 2014-12-20 16:47:02 -07:00
Jeremy
e8c4f059c0 Set 5 seconds timeout for local server connection. 2014-12-19 17:40:58 -07:00
Jeremy
1a6f80f2df Auxiliary console support for IOS routers. 2014-12-19 15:43:15 -07:00
Jeremy
47ae310ac7 Check if any device runs and warn the user before closing a project. 2014-12-17 17:53:27 -07:00
Jeremy
923c61f9c7 Restore the debug level status when starting. 2014-12-17 17:26:51 -07:00
Jeremy
18b8c558cd Merge remote-tracking branch 'origin/master' 2014-12-17 16:09:20 -07:00
Jeremy
84a091e380 Fixes resource access on Mac OS X. Adds a base configs dir in the GNS3 project directory and copy IOS base configs there. 2014-12-17 16:09:08 -07:00
grossmj
1399098e30 Automatically select the symbol and category corresponding the edited item in the symbol selection dialog. 2014-12-16 21:51:06 -07:00
Jeremy
593f8add5d Auto screenshot when saving a project. 2014-12-15 18:14:33 -07:00
Jeremy
2f26624f29 Sync MainWindow Ui. 2014-12-15 17:08:18 -07:00
Jeremy
14c901d219 Merge pull request: Migrate cloud project to local #169 2014-12-15 17:05:53 -07:00
Jeremy
2b2e45ca45 Merge pull request: Move local project to cloud #168 2014-12-15 17:00:23 -07:00
Jeremy
868e9a322e Scale SVG image to icon sizes. Fixes #124. 2014-12-15 16:53:13 -07:00
Jeremy Grossmann
d2f3f58de1 Merge pull request #166 from planctechnologies/gns-167
Fix ASA devices in qemu
2014-12-13 13:00:56 -07:00
Jeremy Grossmann
a69b3fcd11 Merge pull request #165 from planctechnologies/gns-165
Run VPCS devices on cloud servers
2014-12-13 13:00:29 -07:00
Jerry Seutter
a3505ee7f2 Spelling fix 2014-12-12 16:44:06 -07:00
Jerry Seutter
efe5e0e2c4 Update vpcs download location, fix error message 2014-12-12 16:42:47 -07:00
Jeremy
8745527c5d Catch OSError exception for subprocess calls. 2014-12-11 12:15:24 -07:00
grossmj
78b40df71e Have the classic icons a little less dark. 2014-12-10 15:20:03 -07:00
Jerry Seutter
9675619ab9 Run ASA devices 2014-12-09 13:38:21 -07:00
Jeremy Grossmann
68ca2c2be6 Merge pull request #162 from dlintott/fix_161
Fix logic when checking for transparency or border_style.
2014-12-09 12:35:29 -07:00
Daniel Lintott
835ecbb410 Fix logic when checking for transparency or border_style.
A value of 0 is valid for both transparency and border_style. Python evaluates 0 to be False.

Fixes #161
2014-12-09 15:22:20 +00:00
Jeremy Grossmann
22756a3c13 Merge pull request #160 from planctechnologies/dev
Warn when deleting cloud instance with running devices
2014-12-08 18:13:11 -07:00
Jerry Seutter
3c2fb04ed8 Added VPCS devices to cloud projects. 2014-12-08 17:20:54 -07:00
Jeremy
db5fb840a6 Darken classic style icons. 2014-12-08 14:34:15 -07:00
Jerry Seutter
65b05d707b Merge branch 'master' into dev 2014-12-08 14:26:09 -07:00
Dmitry Shmygov
1b972df7e2 Run VirtualBox as another user 2014-12-06 01:44:48 +03:00
Jeremy
40266c275d Bump version to 1.2.2.dev1 2014-12-05 13:53:31 -07:00
Jeremy
197db35c80 Disable cloud. 2014-12-04 12:50:50 -07:00
Jeremy
8771e1ace7 Bump version to 1.2.1 2014-12-04 12:49:40 -07:00
Jeremy
e6e1275ad2 Fixes missing options. 2014-12-04 12:35:36 -07:00
Jeremy
417dc0859b Use bundled Qemu on Windows and OSX by default and checks if remote server are registered. 2014-12-04 12:25:49 -07:00
Jeremy
806e1f29bb Bump version to 1.2.1.dev2 2014-12-02 18:52:28 -07:00
Jeremy
9724f5769d Support for CPU throttling and process priority for Qemu. 2014-12-02 18:12:37 -07:00
Jeremy
5a0c8914c4 Rename IOSv symbols + improve support for ASA on Windows. 2014-12-02 14:49:04 -07:00
Jeremy
c2e0a30da8 Merge pull request #157. 2014-12-02 12:37:21 -07:00
Jeremy
45ae4f20a0 Merge remote-tracking branch 'origin/master' 2014-12-02 12:34:06 -07:00
Jeremy
398826d7ef IOSv and IOSv-L2 integration. 2014-12-02 12:33:49 -07:00
grossmj
14e5f3bf74 Merge remote-tracking branch 'origin/master' 2014-12-02 11:51:32 -07:00
grossmj
57b1cbf41b Fixes SecureCRT command line. 2014-12-02 11:51:17 -07:00
Julien Duponchelle
51a0d88b9b Revert "Add start instructions for MacOSX"
This reverts commit 16d4d7d1ea.
2014-12-01 22:57:17 +01:00
Julien Duponchelle
16d4d7d1ea Add start instructions for MacOSX 2014-12-01 21:26:16 +01:00
Jerry Seutter
2affb1513d Merge branch 'dev' of github.com:planctechnologies/gns3-gui into dev 2014-12-01 09:42:21 -07:00
Jerry Seutter
214d4b2a9e Grammar fixes 2014-12-01 09:40:02 -07:00
jseutter
2f486d979b Merge pull request #48 from planctechnologies/gns-133
Gns 133 Deleting an instance orphans devices on canvas
2014-12-01 09:37:00 -07:00
grossmj
023c5fb99a Install scripts for Mac OS X. 2014-11-30 19:37:15 -07:00
grossmj
329ed371f9 Adds default path for VBoxManage on Mac OS X. 2014-11-29 16:42:57 -07:00
grossmj
0577bfbde3 Support for older Qemu versions like the 0.11.0 on Windows. 2014-11-29 14:11:51 -07:00
grossmj
3ce5c35143 Use PROGRAMFILES and PROGRAMFILES(X86) environment variables for console command lines on Windows. 2014-11-28 17:57:54 -07:00
Massimiliano Pippi
9258ef4bb3 do nothing if server is None 2014-11-28 17:08:39 +01:00
Massimiliano Pippi
e02facc170 remove devices when removing instances 2014-11-28 16:58:33 +01:00
Massimiliano Pippi
4dc76926ea remove nodes when removing instances 2014-11-28 16:56:05 +01:00
Massimiliano Pippi
ba6be7e987 utility methods for shutting down cloud servers 2014-11-28 16:55:36 +01:00
grossmj
6b2da1ec97 Fixes port sorting issues. 2014-11-27 11:34:01 -07:00
Jeremy Grossmann
6aa1b515c7 Merge pull request #154 from dlintott/master
Validate the Idle-PC value and display an error if invalid
2014-11-25 13:55:16 -07:00
Daniel Lintott
5d64ec1c8f Validate the Idle-PC value and display an error if invalid 2014-11-25 20:34:43 +00:00
Jeremy
e1b81fb931 Fixes PyQt module usage. 2014-11-25 11:54:35 -07:00
Jeremy Grossmann
c91752598c Merge pull request #153 from planctechnologies/dev
Remove image access server references
2014-11-25 11:51:07 -07:00
grossmj
708515925f Do not put space in VirtualBox linked VM names. 2014-11-24 20:11:34 -07:00
Jeremy
a61745f868 Fixes C7200 IO cards insert/remove issues and makes C7200-IO-FE the default. 2014-11-24 17:02:00 -07:00
Jeremy
2ca7af34df Load project from command line. Fixes #151. 2014-11-24 14:21:51 -07:00
jseutter
bd7e6f4e3e Merge pull request #47 from planctechnologies/gns-136
Gns 136 gui won't exit when telnet session open to device
2014-11-24 10:49:51 -07:00
jseutter
32e86d461e Merge pull request #46 from planctechnologies/gns-132
Gns 132 Remove IAS (Image Access Server) code
2014-11-24 10:23:37 -07:00
Massimiliano Pippi
66ea5ad979 close cloud server connections when gns3 exits 2014-11-24 12:47:56 +01:00
Massimiliano Pippi
ebfa80d444 return the list of cloud servers as a property 2014-11-24 12:47:06 +01:00
Massimiliano Pippi
4247cc819d removed references to the IAS server 2014-11-24 12:18:42 +01:00
Massimiliano Pippi
1f9f5bd734 hardcode Ubuntu image in cloud preferences 2014-11-24 12:11:36 +01:00
Jeremy
426d791eea Fixes #48, #152. 2014-11-23 19:26:37 -07:00
grossmj
30d99b812c Merge remote-tracking branch 'origin/master' 2014-11-23 19:04:35 -07:00
grossmj
981e55bc4d Use the Qt binding abstraction layer. 2014-11-23 19:04:11 -07:00
Jeremy
bab1090a25 Use SubprocessError to catch Subprocess exceptions. 2014-11-22 17:45:04 -07:00
Jeremy
84b5181fd8 Support for full screen mode (View -> Fullscreen). 2014-11-21 16:52:06 -07:00
Jeremy
222ebb58c0 Bump to version 1.2.1.dev1 and fixes vboxmanage lookup on Windows. 2014-11-20 19:01:00 -07:00
Jerry Seutter
f5ad3a6a2e Merge branch 'master' into dev 2014-11-18 16:21:39 -07:00
Jerry Seutter
422af60827 Enabling cloud functionality 2014-11-18 16:21:17 -07:00
Jerry Seutter
9127321540 Merge branch 'master' into dev 2014-11-18 16:18:16 -07:00
jseutter
dcae059480 Merge pull request #44 from planctechnologies/gns-130
Gns 130: QThreads missing a parent QObject
2014-11-17 09:24:53 -07:00
jseutter
6d6603e013 Merge pull request #45 from planctechnologies/gns-131
Fix bug where new instances would not build.
2014-11-17 09:24:35 -07:00
Massimiliano Pippi
90fe8078c3 fixed transitions for custom instance states, fixes gns-131 2014-11-17 16:47:56 +01:00
Massimiliano Pippi
367fb32114 instance QThreads passing a parent 2014-11-17 14:24:03 +01:00
Massimiliano Pippi
b2b82afd71 fixed super() call, propagate parent 2014-11-17 14:23:52 +01:00
549 changed files with 186859 additions and 249133 deletions

11
.gitignore vendored
View File

@@ -37,6 +37,7 @@ nosetests.xml
# PyCharm
.idea
/.eggs
# OSX
.DS_Store
@@ -47,3 +48,13 @@ nosetests.xml
# Gedit Temp Files
*~
# Qt creator
*.autosave
# Licence keys
keys
# Custom config
/gns3_server.ini
updates

View File

@@ -1,24 +1,37 @@
language: python
#New container architecture
#http://docs.travis-ci.com/user/workers/container-based-infrastructure/
#sudo: false
python:
- "3.3"
- "3.4"
install:
- "pip install -r requirements.txt --use-mirrors"
- "pip install tox"
cache:
apt: true
directories:
- build
script: "python setup.py test"
branches:
only:
- master
before_install:
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
- sudo apt-get update -qq
- sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
- sudo apt-get install qt5-default qttools5-dev-tools
- sh scripts/prepare_travis.sh
notifications:
email: false
irc:
channels:
- "chat.freenode.net#gns3"
on_success: change
on_failure: always
#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"

View File

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

569
CHANGELOG Normal file
View File

@@ -0,0 +1,569 @@
# Change Log
## 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.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
## 1.3.5 16/06/2015
* Do not crash in a very rare case on Windows when stoping local server
* Escape usage to glob
* Fix QMessageBox.NoButton): argument 1 has unexpected type 'Servers'
* Turn on/off local server auth
* Fix 'ValueError' object has no attribute 'errno' in IOS decompress
* Fix error if communication with the update server is intercepted by a third party.
* Fix auth errors if you change the local server IP
* Support auth for local server
* Ensure no colored log output on Windows
* Fixes issue with default router settings for templates.
* Display a proper message if you use a remote server started with --local
* Catch zlib error when uncompress IOS
* Raise error if we pass non string to Port name
* Add basic auth support for local server
## 1.3.4 02/06/2015
* Check if an IOS image is set in the IOS router template
* Ensure the version number is written in configuration file
* Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs.
* Fix resize issue in server page
* Fix segfault when starting OSX server with allow connection from anywhere
* Fixes bug when editing c7200 templates.
* Fixes IOS decompression. Fixes #370.
* Topology auto start work for VPCS
* Avoid moving .gns3_temporary files.
* Handles MemoryError.
* Fix crash when a process listen on GNS3 port return an empty JSON
* Another fix for the topology None error
* Fix a rare crash in completion
* Fix crash when loading topology in rare conditions
## 1.3.3 14/05/2015
* New inline help text for the idle-pc dialog.
* Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template.
* Adds name to the thank you section.
* Prevent users to use VirtualBox linked clone VMs in temporary projects (for now).
* Capture error if the command is invalid
* Cleanup egg cache when exit
* Fix a crash in console when you used non UTF-8 terminal
* Fix crash during save as
* Change title when exporting an IOS startup-config.
* Removes analytics client on closing.
## 1.3.3rc1 07/05/2015
* Catch broken pipe error catched for OSX
* Prevent a topology made for next version to be open in previous version
* Check if the local server is really a local server
* NIO NAT support for QEMU VMs (user mode back-end is used).
* Modified version requirements, so that they require the dependency versions as minimums. Added some more detailed instructions for compilation on Windows.
* Prevent user to enter a None port
* Fix broken pipe error on OSX when frozen
* Prevent the same link created twice on OSX
* Project loading: names and IDs must be assigned to ports on the client side after nodes have been created. Fixes #326. Fixes config updating for IOS router and IOU devices.
* Fix a crash when dropping a .gns3
* Cleanup VPCS code
* Turn off config parser interpolation
* Support unicode characters in regex
* Fixes duplicate entries for "Recent files" on Windows. Fixes #316.
* Fixes VPCS multi-host. Fixes #318.
* Fixes issues when importing configs for IOS, IOU and VPCS. Fixes #314.
* Fixes issue when console setting present in IOS router templates.
* Do not send empty settings when creating VMs.
* Do not set a default private-config when creating a new IOS router template. Fixes #317.
* Refactors how startup-config and private-config are handled for IOS routers.
* Fixes IOU and QEMU tests.
* Makes sure all IOS router settings are saved in the project file & simplify loading from a project.
* Makes sure all VirtualBox VM settings are saved in the project file & simplify loading from a project.
* Makes sure all VPCS VM settings are saved in the project file & simplify loading from a project.
* Makes sure all IOU VM settings are saved in the project file & simplify loading from a project.
* Makes sure all QEMU VM settings are saved in the project file & simplify loading from a project.
* Fix save as by correctly renaming VM uuid project directory
* Fix save as duplicate the .gns3 file
* Fix broken project in Another assert topology fixe
* Prevent user to enter bad hostname
* Fixes an issue when the IOU VM template has a console setting.
* Releasing adding a link. Fixes #235.
* Fix RuntimeError: wrapped C/C++ object of type QNetworkReply has been deleted.
* Do not crash if terminal doesn't support UTF-8
* Fix windows build
* Fixes "show only devices with captures" in the topology summary.
## 1.3.2 28/04/2015
* Fixes bug when IOS configs are not in VM settings.
* Fixes small issue with Qemu VM monitor.
* Fixes issue when only one port is added after a QEMU VM is created. Fixes #296.
* Avoid Cygwin warning with VPCS on Windows.
* Fixes issues with QThread handling.
* Fixes missing title + icon in layer position warning message box.
* Allows the warning message box to be displayed once only when moving an object to a background layer.
* Fixes small issue with old monitor setting.
* Check the config path is set when creating a IOU or IOS router.
* Removes residual link when a NIO cannot be created on the server. Fixes #294.
* Fix VPCS tests
* Do not crash if an antivirus intercept a message and send non UTF-8
* Avoid C++ runtime error when progress dialog is finished.
* Merge remote-tracking branch 'origin/master'
* Move FileCopyThread to FileCopyWorker.
* If project loading fail fallback to real temporary project
* Do not crash if rotation is a string
* I think it's prevent empty topologies
* Explicit utf-8 decoding.
* Fixes rare maximum recursion depth exceeded exception.
* Check for invalid base VM configuration files.
* Catch ValueError exception thrown by mmap(): cannot mmap an empty file.
* Use QThreads the correct way (moveToThread).
* Fixes broken serial console connection.
* Fixes "RuntimeError: wrapped C/C++ object ... has been deleted" exceptions with item links.
* Allows exported config files to be created even when there is no config set on VMs.
* Do not try to export empty VPCS startup configs.
* Prevent issues when a file with a simple number is considered valid JSON.
* Explicit error when mmap throw an invalid argument exception.
* Do not replace invalid utf-8 characters when reading the iourc file (we catch the exception to tell the user this is an invalid file).
* Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems).
* Save as dialog opens in the projects directory. Fixes #267.
* Adds Terminal + nc for serial console connections on OSX. Fixes #228.
* Improve warning when non unicode char in iourc
* Crash report not for developers and new key
* Do not crash if we can't change IOU permission
* More checks when decompressing IOS images.
* Warn users that they must provide their router images.
* Display an error and link to the documentation if no router available
* Display print( in std console and Qt Console
* Fix tests and a potential issue where initial_content is not send
* Fix a crash in qemu loading
* Removes unnecessary progress dialog when listing VirtualBox VMs.
* Fixes issues when pushing configs for Dynamips and IOU.
* Allow for empty initial-config path for IOU VM templates. Send IOU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
* Allow for empty startup-config and private-config paths for IOS routers.
* Send QEMU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
* Include resources and tests in pypi packages
* Fix issue during project import on Windows with non local server
## 1.3.1 11/04/2015
* Release
## 1.3.1rc4 09/04/2015
* Fix crash when save as can't create a directory
* Allow less strict dependencies
## 1.3.1rc3 07/04/2015
* Send HTTP errors 400 to the crash report system
## 1.3.1rc2 06/04/2015
* Fix race condition during old project import
## 1.3.1rc1 05/04/2015
* Fix rare occasion when user manage to put text in port field
* Fix a crash when exporting vpcs startup script
* Fix an issue with sending iourc when a topologies is reloaded
* Solve issue when iourc contains non ascii characters
* Handle corrupted zip file with IOS image
* Don't crash if we try to contact a non GNS3 remote server returning JSON
* Skip tests in package
* Check port range
* Add a warning about too much ram for IOS
* Fix crash if project is already closed
* Check if wait for connection thread still running before emitting a signal.
* Check if process files thread still running before emitting a signal.
* Raven is an optionnal dependencies for Debian
* Fix crash if a dumped topology as no node during save as
* Fix: remove old ID references for ATM and Frame-Relay switches.
## 1.3.0 30/03/2015
* Fix etherswitch router
* Fix issues with progress dialog
* Fix save as
## 1.3.0rc2 23/03/2015
* Fix crash when in same occasion the project name is missing
* Update sentry key
* Display adapters in the tooltips in the correct order.
* Open consoles in alphanumerical order.
* Auto idle-PC improvements.
* Adds project id when requesting UDP port.
* Fixes Thread problem. Fixes #229.
* Cancel network requests if the progress dialog itself is canceled. Avoid closing the preferences dialog or any configuration dialog if there is a pending request. Fixes #227.
* Fixes #228 (no alternative interface has been chosen).
* Catch OSError when reading or writing the local server config file.
* Fixes GUI that could not be closed when using an already running local server.
* Save configs when project is committed.
* Del key deletes selected link
* Fix crash is no remote servers is available
## 1.3.0rc1 19/03/2015
* Handle legacy snapshots
* Add server informations for Qemu, VirtualBox and VPCS info boxes
* Support sending IOURC from client to remote servers
* Fixes crash when quick restart the client
* Add 1MB disk for EtherSwitch router templates (to store the vlan database)
* Fixes alignment options to ignore devices labels
* Compute IDLEPC on remote servers
* Prevent using lab instruction in a temporary project
* Display a warning on console if server port is already in used
* Display an error if server version is incorrect
## 1.3.0beta2 13/03/2015
* Alternative local server shutdown (faster GUI closing on Windows).
* Grey out local server preferences if the local server is not activated.
* Adds "template" to the Wizard titles.
* Option to automatically take or not a screenshot when saving a project.
* Support RAM setting for VirtualBox VMs.
* Fixed duplicate VM template entries for Qemu, VirtualBox and IOU.
## 1.3.0beta1 11/03/2015
* New title for VMs/Devices/routers preference pages.
* Deactivate auto idle-pc in contextual menu while we think about a better implementation.
* Optional IOU license key check.
* Relative picture paths are saved in projects.
* Relative path support of IOU, IOS and Qemu images.
* More checks when automatically starting the local server and find an alternative port if needed.
* Support for HDC and HDD disk images in Qemu.
* Fixed base IOS and IOU base configs.
* Fixed GNS3 console issues.
* Renamed server.conf and server.ini to gns3_server.conf and gns3_server.ini respectively.
* Remove remote servers list from module preferences + some other prefences re-factoring.
* Automatically convert old projects on remote servers.
* Bump the progress dialog minimum duration before display to 1000ms.
* Fixed port listing bug with Cloud and Host nodes.
* Fixed Qemu networking.
* Give a warning when a object is move the background layer.
* Option to draw a rectangle when a node is selected.
* New project icon (little yellow indicator).
* Default name for screenshot file is "screenshot".
* Alignment options (horizontal & vertical).
* Fixed import / export of the preferences file.
* Fixed pkg_ressource bug.
* Brought back Qemu preferences page.
* Include SSL cacert file with GNS3 Windows exe and Mac OS App to send crash report using HTTPS.
* Fixed adapter bug with VirtualBox.
* Fixed various errors when a project was not initialized.
## 1.3.0alpha1 03/03/2015
* No more console port and UDP tunneling settings by type of module
* Fixe save
* Settings are stored as JSON
* All communication with servers display a waiting dialog
* Add a revision number in the topology file
* Qemu can run on a server without graphical interface
* Automated crash reports
* You can now copy paste from the GNS 3 console
## 1.2.3 2015/01/17
* Fixed temporary files path setting in general preferences which was not used.
* Fixed missing devices from the node view when they use a remote server.
* Fixed broken ASA kernel/initrd file browsers.
* Fixed bug with WICs interfaces no showing up in the port list.
## 1.2.2 2015/01/16
### Small improvements / new features
* EtherSwitch routers can be added and configured like other IOS routers.
* Change hostname option in the contextual device menu.
* Import & export config options in contextual device menu.
* Auto screenshot when saving a project.
* Auto start project support (you have to manually edit your .gns3 project file).
* Changes to the IOU L2 initial-config (16 Ethernet interfaces, no shutdown by default and 0 serial interfaces).
* Upgraded SuperPutty to version 1.4.0.5 in the all-in-one installer.
* Possibility to apply or not the same text to all selected items when editing notes.
* Base configs are now stored in the GNS3 config directory.
* Short port names in the topology summary.
* Added the VirtualBox VM name in VirtualBox device tooltips.
* Set 5 seconds timeout for local server connections.
* Check if any device runs and warn the user before closing a project.
* Restore the debug level status when starting.
* Automatically select the symbol and category corresponding the edited item in the symbol selection dialog.
* Scale SVG images to icon sizes.
* Console switching from local/remote to remote/local while a VirtualBox VM is running.
* Default Jungle dock location is now bottom right corner.
### Bug fixes
* Fixed the default jungle news loading on Windows.
* Fixed SuperPutty integration (not the default, still have to select it in the preferences).
* Avoid uninitialized nodes to be saved in the project file.
Prevent GNS3 to crash on Windows when importing GNS3 config file.
* Fixed resource access on Mac OS X.
* Fixed transparency or border style restoration for ellipses and rectangles.
* Support spaces in the controller name of VirtualBox clones.
* Ignore Unicode errors when executing vboxmanage.
* Get Windows interface list from the registry if the COM service fails.
## 1.2.1 2014/12/04
* Support for full screen mode (View -> Fullscreen).
* Bundled Qemu 0.13.0 in the Windows all-in-one. Default for all local Qemu VMs.
* Bundled Qemu 0.14.1 in the Mac OS X App. Default for all local Qemu VMs.
* Changed ASA defaults to use Qemu 0.13.0 (on Windows), have 4 interfaces and CPU throttling to 65%.
* Fixed SecureCRT command line when space in the device name.
* Fixed port sorting issues.
* Added default path for VBoxManage on Mac OS X
* Upgraded gns3-converter to version 1.1.1 for Windows all-in-one and Mac OS X DMG.
* New idle-PC field validation.
* Possibility to load the project from command line (or double-click on a project on Windows).
* Fixed Unicode error when using VirtualBox VM with a name containing non-english characters.
## 1.2 2014/11/20
* New GUI styles: charcoal (default) & classic. Changing GUI Preferences
* Integration of GNS3 converter (allows old .net topologies to be opened).
* Allow Qemu VM to have no interface.
* Automatically extract IOS configs when a project is closed.
* Show the cancel button in Wizards on Mac OS X.
* Fix crash on Windows 32-bit.
* Fix "new project" bug when using the GNS3 IOU VM.
* Fix "could not find unused port" WinError 10013 bug
* qemu-system-i386 is the new default on 32-bit platforms.
* Option to deactivate the new project dialog at startup.
* Add "open a project" and "recent projects" buttons to the new project dialog.
* Fix platform detection issue with some Cisco IOS image file name.
* Add delay (default 500 ms) when Console to all nodes.
* Check for duplicate node names in Preferences.
* Fix bug when editing a Qemu VM configured to run on a remote server.
* News dock widget is smaller.
* Fix SecureCRT issue when disconnecting from an IOU device on Windows.
* Update VPCS to version 0.6 in the all-in-one installer.
## 1.1 2014/11/20
* Fixed broken cloud.
* Fixed broken remote server.
* Fixed Qemu binaries not showing up when editing a Qemu VM.
* Fixed EtherSwitch (until we come with a default template for it).
* Serial console for local VirtualBox.
* Warning message when creating an IOU device with a remote server in the Wizard.
* New Idle-PC dialog.

View File

@@ -4,8 +4,8 @@ include INSTALL
include LICENSE
include MANIFEST.in
include tox.ini
recursive-exclude tests *
recursive-include docs *
recursive-include tests *
recursive-include gns3 *
recursive-include resources *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

View File

@@ -1,7 +1,14 @@
GNS3-gui
========
GNS3 GUI repository (beta stage).
.. image:: https://travis-ci.org/GNS3/gns3-gui.svg?branch=master
:target: https://travis-ci.org/GNS3/gns3-gui
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
:target: https://pypi.python.org/pypi/gns3-gui
GNS3 GUI repository.
Linux (Debian based)
--------------------
@@ -13,7 +20,7 @@ Dependencies:
- Python 3.3 or above
- Setuptools
- PyQt libraries
- PyQt 5 libraries
- Apache Libcloud library
- Requests library
- Paramiko library
@@ -23,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:
@@ -36,7 +50,23 @@ Finally these commands will install the GUI as well as the rest of the dependenc
Windows
-------
Please use our all-in-one installer.
Please use our `all-in-one installer <https://community.gns3.com/community/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/
- 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
.. code:: bash
python setup.py install
to install the remaining dependencies.
Mac OS X
--------
@@ -52,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.
@@ -67,3 +102,26 @@ Finally, install both the GUI & server from the source.
python3 setup.py install
Or follow this `HOWTO that uses MacPorts <http://binarynature.blogspot.ca/2014/05/install-gns3-early-release-on-mac-os-x.html>`_.
Developement
-------------
If you want to update the interface, modify the .ui files using QT tools. And:
.. code:: bash
cd scripts
python build_pyqt.py
Test with PyQT4
~~~~~~~~~~~~~~~~
If you want to simulate a user with PyQT4:
.. code:: bash
export GNS3_QT4=1
python gns3/main.py

View File

@@ -1,4 +1,7 @@
-rrequirements.txt
pep8
pytest
pytest-pythonpath # useful for running tests outside tox
pytest-timeout
pytest-capturelog

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()

158
gns3/appliance_window.py Normal file
View File

@@ -0,0 +1,158 @@
#!/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 jinja2
import os
import sys
import shutil
from .utils.get_resource import get_resource
from .utils.wait_for_lambda_worker import WaitForLambdaWorker
from .utils.progress_dialog import ProgressDialog
from .utils.server_select import server_select
from .utils import human_filesize
from .qt import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui
from .ui.appliance_window_ui import Ui_ApplianceWindow
from .image_manager import ImageManager
from .registry.appliance import Appliance, ApplianceError
from .registry.registry import Registry
from .registry.config import Config, ConfigException
from .registry.image import Image
import logging
log = logging.getLogger(__name__)
class ApplianceWindow(QtWidgets.QWidget, Ui_ApplianceWindow):
def __init__(self, path, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setWindowTitle(path)
self._path = path
# Call linkClickedSlot() for all non local links
self.uiWebView.page().setLinkDelegationPolicy(QtWebKitWidgets.QWebPage.DelegateExternalLinks)
self.uiWebView.linkClicked.connect(self._linkClickedSlot)
# Expose JavaScript objects
self.uiWebView.page().mainFrame().javaScriptWindowObjectCleared.connect(self.javaScriptWindowObject)
# Enable the inspector on right click
self.uiWebView.settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)
self.show()
self._refresh()
def _refresh(self):
renderer = jinja2.Environment(loader=jinja2.FileSystemLoader(get_resource('static')))
renderer.filters['nl2br'] = lambda s: s.replace('\n', '<br />')
renderer.filters['human_filesize'] = human_filesize
template = renderer.get_template("appliance.html")
images_directories = []
images_directories.append(os.path.join(ImageManager.instance().getDirectory(), "QEMU"))
images_directories.append(os.path.dirname(self._path))
registry = Registry(images_directories)
try:
self._appliance = Appliance(registry, self._path)
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
self.close()
return
self.uiWebView.setHtml(template.render(appliance=self._appliance, registry=registry))
def javaScriptWindowObject(self):
frame = self.uiWebView.page().mainFrame()
frame.addToJavaScriptWindowObject('gns3', self)
def _linkClickedSlot(self, url):
"""
Open in a new browser other url
"""
QtGui.QDesktopServices.openUrl(url)
#
# Public Javascript methods
#
@QtCore.pyqtSlot(str)
def install(self, version):
"""
Install an appliance based on appliance version
:param version: Version to install
"""
try:
config = Config()
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
self.close()
return
appliance_configuration = self._appliance.search_images_for_version(version)
try:
allow_local_server = not (sys.platform.startswith("darwin") or sys.platform.startswith("win"))
server = server_select(self.parent(), allow_local_server=allow_local_server)
except ValueError:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", "In order to use a GNS3a file you need the GNS3VM or a remote server for Mac and Windows.")
self.close()
return
if server is None:
return
self.close()
try:
config.add_appliance(appliance_configuration, server.url())
except ConfigException as e:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
return
worker = WaitForLambdaWorker(lambda: config.save())
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(self._appliance["name"], version))
@QtCore.pyqtSlot(str, str)
def importAppliance(self, filename, md5sum):
path, _ = QtWidgets.QFileDialog.getOpenFileName()
if len(path) == 0:
return
md5 = Image(path).md5sum
if md5 != md5sum:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct image file.")
return
config = Config()
#TODO: ASK for VM type
worker = WaitForLambdaWorker(lambda: config.import_image(path))
progress_dialog = ProgressDialog(worker, "Add appliance", "Import the appliance...", None, busy=True, parent=self)
if not progress_dialog.exec_():
return
self._refresh()

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,341 +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))
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. """
try:
return self.driver.list_nodes()
except Exception as e:
log.error("list_instances returned an error: {}".format(e))
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,45 +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,311 +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, gns3_ias_url):
super(RackspaceCtrl, self).__init__(username, api_key)
self.gns3_ias_url = gns3_ias_url
# 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_shared_images(self, username, region, gns3_version):
"""
Given a GNS3 version, ask gns3-ias to share compatible images
Response:
[{"created_at": "", "schema": "", "status": "", "member_id": "", "image_id": "", "updated_at": ""},]
or, if access was already asked
[{"image_id": "", "member_id": "", "status": "ALREADYREQUESTED"},]
"""
endpoint = self.gns3_ias_url+"/images/grant_access"
params = {
"user_id": username,
"user_region": region.upper(),
"gns3_version": gns3_version,
}
try:
response = requests.get(endpoint, params=params)
except requests.ConnectionError:
raise ApiError("Unable to connect to IAS")
status = response.status_code
if status == 200:
return response.json()
elif status == 404:
raise ItemNotFound()
else:
raise ApiError("IAS status code: %d" % status)
def list_images(self):
"""
Return a dictionary containing RackSpace server images
retrieved from gns3-ias server
"""
if not (self.tenant_id and self.region):
return {}
try:
shared_images = self._get_shared_images(self.tenant_id, self.region, __version__)
images = {}
for i in shared_images:
images[i['image_id']] = i['image_name']
return images
except ItemNotFound:
return {}
except ApiError as e:
log.error('Error while retrieving image list: %s' % e)
return {}
def get_image(self, image_id):
return self.driver.get_image(image_id)
def get_provider(cloud_settings):
"""
Utility function to retrieve a cloud provider instance already authenticated and with the
region set
:param cloud_settings: cloud settings dictionary
:return: a provider instance or None on errors
"""
try:
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
ias_url = cloud_settings['gns3_ias_url']
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey, ias_url)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")
return
if not region:
region = provider.list_regions().values()[0]
if not provider.set_region(region):
log.error("Unable to set cloud provider region")
return
return provider

View File

@@ -1,466 +0,0 @@
from contextlib import contextmanager
import io
import json
from socket import error as socket_error
import logging
import os
import select
import tempfile
import time
import zipfile
from PyQt4.QtCore import QThread
from PyQt4.QtCore import pyqtSignal
from .exceptions import KeyPairExists
from .rackspace_ctrl import RackspaceCtrl, 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.error("SSH connection error to {}: {}".format(host, e))
yield None
finally:
client.close()
class ListInstancesThread(QThread):
"""
Helper class to retrieve data from the provider in a separate thread,
avoid freezing the gui
"""
instancesReady = pyqtSignal(object)
def __init__(self, parent, provider):
super(QThread, self).__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(QThread):
"""
Helper class to create instances in a separate thread
"""
instanceCreated = pyqtSignal(object, object)
def __init__(self, parent, provider, name, flavor_id, image_id):
super(QThread, self).__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(QThread):
"""
Helper class to remove an instance in a separate thread
"""
instanceDeleted = pyqtSignal(object)
def __init__(self, parent, provider, instance):
super(QThread, self).__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(QThread):
"""
Perform an SSH connection to the instances in a separate thread,
outside the GUI event loop, and start GNS3 server
"""
gns3server_started = pyqtSignal(str, str, str)
# This is for testing without pushing to github
# commands = '''
# DEBIAN_FRONTEND=noninteractive dpkg --configure -a
# DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
# DEBIAN_FRONTEND=noninteractive apt-get -y update
# DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
# DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
# DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
# ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
# mkdir -p /opt/gns3
# tar xzf /tmp/gns3-server.tgz -C /opt/gns3
# cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
# cd /opt/gns3/gns3-server; python3 ./setup.py install
# ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
# wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
# python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
# hostname gns3-iouvm
# tar xzf iouyap.tar.gz -C /usr/local/bin
# killall python3 gns3server gns3dms
# '''
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
killall python3 gns3server gns3dms
'''
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
super(QThread, self).__init__(parent)
self._host = host
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(QThread):
"""
Establish a websocket connection with the remote gns3server
instance. Run outside the GUI event loop.
"""
established = pyqtSignal(str)
def __init__(self, parent, provider, server_id, host, port, ca_file,
auth_user, auth_password, ssh_pkey, instance_id):
super(QThread, self).__init__(parent)
self._provider = provider
self._server_id = server_id
self._host = host
self._port = port
self._ca_file = ca_file
self._auth_user = auth_user
self._auth_password = auth_password
self._ssh_pkey = ssh_pkey
self._instance_id = instance_id
def run(self):
"""
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(QThread):
"""
Zip and Upload project to the cloud
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, cloud_settings, project_path, images_path):
super().__init__()
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(str(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(QThread):
"""
Upload multiple files to cloud files
uploads - A list of 2-tuples of (local_src_path, remote_dst_path)
"""
completed = pyqtSignal()
def __init__(self, parent, cloud_settings, uploads):
super(QThread, self).__init__(parent)
self._cloud_settings = cloud_settings
self._uploads = uploads
def run(self):
for src, dst in self._uploads:
log.debug('Upload from {} to {}'.format(src, dst))
provider = get_provider(self._cloud_settings)
provider.upload_file(src, dst)
log.debug('Upload image completed')
self.completed.emit()
class DownloadProjectThread(QThread):
"""
Downloads project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super().__init__()
self.project_name = cloud_project_file_name
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
self.cloud_settings = cloud_settings
def run(self):
try:
self.update.emit(0)
provider = get_provider(self.cloud_settings)
zip_file = provider.download_file(self.project_name)
zip_file = zipfile.ZipFile(zip_file, mode='r')
zip_file.extractall(self.project_dest_path)
zip_file.close()
project_name = zip_file.namelist()[0].strip('/')
self.update.emit(20)
with open(os.path.join(self.project_dest_path, project_name, project_name + '.gns3'), 'r') as f:
project_settings = json.loads(f.read())
images = set()
for node in project_settings["topology"].get("nodes", []):
if "properties" in node and "image" in node["properties"]:
images.add(node["properties"]["image"])
image_names_in_cloud = provider.find_storage_image_names(images)
for i, image in enumerate(images):
dest_path = os.path.join(self.images_dest_path, *image_names_in_cloud[image].split('/')[1:])
if not os.path.exists(os.path.dirname(dest_path)):
os.makedirs(os.path.dirname(dest_path))
provider.download_file(image_names_in_cloud[image], dest_path)
self.update.emit(20 + (float(i) / len(images) * 80))
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(str(e)), True)
def stop(self):
self.quit()
class DeleteProjectThread(QThread):
"""
Deletes project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
def __init__(self, project_file_name, cloud_settings):
super().__init__()
self.project_file_name = project_file_name
self.cloud_settings = cloud_settings
def run(self):
try:
provider = get_provider(self.cloud_settings)
provider.delete_file(self.project_file_name)
self.completed.emit()
except Exception as e:
log.exception("Error deleting project")
self.error.emit("Error deleting project: {}".format(str(e)), True)
def stop(self):
pass
def get_cloud_projects(cloud_settings):
provider = get_provider(cloud_settings)
return provider.list_projects()

View File

@@ -1,443 +0,0 @@
# -*- coding: utf-8 -*-
import ast
import logging
import os
from PyQt4.QtGui import QWidget
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QMenu
from PyQt4.QtGui import QAction
from PyQt4.QtGui import QInputDialog
from PyQt4.QtCore import QAbstractTableModel
from PyQt4.QtCore import QModelIndex
from PyQt4.QtCore import QTimer
from PyQt4.QtCore import pyqtSignal
from PyQt4.Qt import Qt
from .cloud.utils import (ListInstancesThread, CreateInstanceThread, DeleteInstanceThread,
StartGNS3ServerThread, WSConnectThread)
from libcloud.compute.types import NodeState
from .topology import Topology
# this widget was promoted on Creator, must use absolute imports
from gns3.ui.cloud_inspector_view_ui import Ui_CloudInspectorView
from gns3.cloud_instances import CloudInstances
log = logging.getLogger(__name__)
POLLING_TIMER = 10000 # in milliseconds
class RunningInstanceState(NodeState):
"""
GNS3 states for running instances
"""
GNS3SERVER_STARTING = 10
GNS3SERVER_STARTED = 11
WS_CONNECTED = 12
class InstanceTableModel(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 == Qt.DecorationRole:
if col == 1:
# status
return QIcon(self._get_status_icon_path(instance))
elif role == 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 == Qt.Horizontal and role == 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(QModelIndex(), self.rowCount(), self.rowCount())
if not len(self._instances):
self.beginInsertColumns(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(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(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 = pyqtSignal(str)
def __init__(self, parent):
super(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(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 = QTimer(self)
self._pollingTimer.timeout.connect(self._polling_slot)
# map flavor ids to combobox indexes
self.flavor_index_id = []
# TODO: Delete me
self._running = {}
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, instances):
"""
Fill the model data layer with instances retrieved through libcloud
"""
self._main_window = main_win
self._provider = main_win.cloudProvider
self._settings = main_win.cloudSettings()
log.info('CloudInspectorView.load')
for i in instances:
self._project_instances_id.append(i["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 = QAction("Delete", self)
delete_action.triggered.connect(self._deleteSelectedInstance)
# create context menu and add actions
menu = 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)
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 _gns3server_started_slot(self, id, host_ip, start_response):
"""
This slot is called when the StartGNS3ServerThread succesfully started
the server.
:param id: the id of the instance
:param host_ip: the host ip of the instance
:param start_response: the output of the server start script on the remote host
"""
# instance state transition: GNS3SERVER_STARTING --> GNS3SERVER_STARTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.GNS3SERVER_STARTED
self._model.updateInstanceFields(instance, ['state'])
data = ast.literal_eval(start_response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(host_ip)
# TODO: Move this directory into projectSettings.
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(id)
top_instance.set_later_attributes(host_ip, port, ssl_cert, ca_file)
ssh_pkey = top_instance.private_key
log.debug('Cloud server gns3server started.')
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file,
username, password, ssh_pkey, id)
wss_thread.established.connect(self._wss_connected_slot)
wss_thread.start()
def _wss_connected_slot(self, id):
"""
This slot is called when the WSConnectThread successfully connected to
the websocket on the remote host
"""
# instance state transition: GNS3SERVER_STARTED --> WS_CONNECTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.WS_CONNECTED
self._model.updateInstanceFields(instance, ['state'])
def _get_public_ip(self, ip_list):
"""
Pick the ipv4 address from the list of ip addresses that the instance
has.
"""
for ip in ip_list:
log.debug('Cloud server ip {}'.format(ip))
# Don't use the ipv6 address
if ':' not in ip:
log.debug('Chose {} as public ip'.format(ip))
return ip
return None
def _update_model(self, instances):
if not instances:
return
# populate underlying model if this is the first call
if self._model.rowCount() == 0 and len(instances) > 0:
self._populate_model(instances)
instance_manager = CloudInstances.instance()
instance_manager.update_instances(instances)
# filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
# cleanup 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()
# start gns3server if needed
for i in project_instances:
# get the real instance state from self._model
model_instance = self._model.getInstanceById(i.id)
if model_instance.state == RunningInstanceState.RUNNING:
# instance state transition: RUNNING --> GNS3SERVER_STARTING
model_instance.state = RunningInstanceState.GNS3SERVER_STARTING
self._model.updateInstanceFields(model_instance, ['state'])
# start GNS3 server and deadman switch
public_ip = self._get_public_ip(i.public_ips)
instance_manager.update_host_for_instance(i.id, public_ip)
topology_instance = instance_manager.get_instance(i.id)
ssh_thread = StartGNS3ServerThread(
self, public_ip, topology_instance.private_key, i.id,
self._provider.username, self._provider.api_key, self._provider.region,
1800)
ssh_thread.gns3server_started.connect(self._gns3server_started_slot)
ssh_thread.start()
def _populate_model(self, instances):
log.info('CloudInspectorView._populate_model')
self._model.flavors = self._provider.list_flavors()
# filter instances for current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
self._model.addInstance(i)
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 = 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:
if not name.endswith("-gns3"):
name += "-gns3"
create_thread = CreateInstanceThread(self, self._provider, name, flavor_id, image_id)
create_thread.instanceCreated.connect(self._main_window.add_instance_to_project)
create_thread.instanceCreated.connect(CloudInstances.instance().add_instance)
create_thread.start()

View File

@@ -1,145 +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
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
def update_instances(self, instances):
save_needed = False
# Look for instances that have been deleted
for static in self._instances:
found = False
for dynamic in instances:
if static.id == dynamic.id:
found = True
break
if not found:
self._instances.remove(static)
save_needed = True
if save_needed:
self.save()
def update_host_for_instance(self, instance_id, host):
for instance in self.instances:
if instance.id == instance_id:
if instance.host != host:
instance.host = host
self.save()
def save(self):
"""
Save the list of cloud instances to the config file
"""
log.debug('Saving cloud instances')
settings = QtCore.QSettings()
settings.beginGroup("CloudInstances")
settings.remove("")
# Save the instances
settings.beginWriteArray("cloud_instance", len(self._instances))
index = 0
for instance in self._instances:
settings.setArrayIndex(index)
for name in instance.fields():
value = getattr(instance, name) if not None else ""
log.debug('{}={}'.format(name, str(value)[0:60]))
settings.setValue(name, value)
index += 1
settings.endArray()
settings.endGroup()
def load(self):
"""
Load instance info from the config file to the topology
"""
log.debug('Loading cloud instances')
settings = QtCore.QSettings()
settings.beginGroup("CloudInstances")
# Load the instances
size = settings.beginReadArray("cloud_instance")
for index in range(0, size):
settings.setArrayIndex(index)
info = {}
for name in TopologyInstance.fields():
value = settings.value(name, "")
log.debug('{}={}'.format(name, str(value)[0:60]))
info[name] = value
ti = TopologyInstance(**info)
self._instances.append(ti)
def get_instance(self, instance_id):
"""
Retrieve a TopologyInstance objects if present
"""
for i in self._instances:
if i.id == instance_id:
return i
return None

View File

@@ -6,7 +6,7 @@ no service password-encryption
hostname %h
!
ip cef
no ip domain lookup
no ip domain-lookup
no ip icmp rate-limit unreachable
ip tcp synwait 5
no cdp log mismatch duplex

View File

@@ -8,7 +8,7 @@ hostname %h
!
ip cef
no ip routing
no ip domain lookup
no ip domain-lookup
no ip icmp rate-limit unreachable
ip tcp synwait 5
no cdp log mismatch duplex

View File

@@ -30,83 +30,83 @@ ip tcp synwait-time 5
!
interface Ethernet0/0
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/1
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/2
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/3
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/0
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/1
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/2
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/3
no ip address
shutdown
no shutdown
duplex auto
!
interface Serial2/0
interface Ethernet2/0
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/1
interface Ethernet2/1
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/2
interface Ethernet2/2
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/3
interface Ethernet2/3
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/0
interface Ethernet3/0
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/1
interface Ethernet3/1
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/2
interface Ethernet3/2
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/3
interface Ethernet3/3
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Vlan1
no ip address

View File

@@ -13,7 +13,7 @@ no ip icmp rate-limit unreachable
!
!
ip cef
no ip domain lookup
no ip domain-lookup
!
!
ip tcp synwait-time 5

View File

@@ -28,14 +28,14 @@ import json
from .qt import QtCore
from .node import Node
from .version import __version__
try:
from gns3converter import __version__ as gns3converter_version
except ImportError:
gns3converter_version = "Not installed"
class ConsoleCmd(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
def do_version(self, args):
"""
Show the version of GNS3 and its dependencies.
@@ -45,6 +45,7 @@ class ConsoleCmd(cmd.Cmd):
if hasattr(sys, "frozen"):
compiled = "(compiled)"
print("GNS3 version is {} {}".format(__version__, compiled))
print("GNS3 Converter version is {}".format(gns3converter_version))
print("Python version is {}.{}.{} ({}-bit) with {} encoding".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2],
@@ -189,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)
@@ -210,16 +211,15 @@ class ConsoleCmd(cmd.Cmd):
ch = logging.StreamHandler(sys.stdout)
if len(args) == 1:
try:
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
else:
print("Activating debugging")
root.addHandler(ch)
except:
print(self.do_debug.__doc__)
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
else:
print("Activating debugging")
root.addHandler(ch)
from .main_window import MainWindow
MainWindow.instance().setSettings({"debug_level": level})
else:
print(self.do_debug.__doc__)
@@ -259,6 +259,10 @@ class ConsoleCmd(cmd.Cmd):
:param params: list of parameters
"""
if self._topology.project is None:
print("Sorry, the project hasn't been saved yet")
return
topology = self._topology.dump()
if len(params) == 1:
# print out whole topology

View File

@@ -19,12 +19,15 @@ import platform
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):
@@ -36,12 +39,16 @@ class ConsoleView(PyCutExt, ConsoleCmd):
# Set introduction message
bitness = struct.calcsize("P") * 8
self.intro = "GNS3 management console. Running GNS3 version {} on {} ({}-bit).\n" \
"Copyright (c) 2006-2014 GNS3 Technologies.".format(__version__, platform.system(), bitness)
current_year = datetime.date.today().year
self.intro = "GNS3 management console.\nRunning GNS3 version {} on {} ({}-bit) with Python {} Qt {}.\n" \
"Copyright (c) 2006-{} GNS3 Technologies.".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)
@@ -69,7 +76,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
For exception handling purposes
(see exception hook in the program entry point).
"""
return False
def onKeyPress_Tab(self):
@@ -83,7 +90,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
if len(self.line) > 0:
cmd, args, _ = self.parseline(line)
if cmd == '':
if cmd is None or cmd == '':
compfunc = self.completedefault
else:
try:
@@ -170,7 +177,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
self.write(text, warning=True)
self.write("\n")
def writeServerError(self, node_id, code, message):
def writeServerError(self, node_id, message):
"""
Write server error messages coming from the server.
@@ -181,15 +188,14 @@ class ConsoleView(PyCutExt, ConsoleCmd):
node = Topology.instance().getNode(node_id)
server = name = ""
if node and node.name():
name = " {}:".format(node.name())
server = "from {}:{}".format(node.server().host,
node.server().port)
if node:
if node.name():
name = " {}:".format(node.name())
server = "from {}".format(node.server().url())
text = "Server error [{code}] {server}:{name} {message}".format(code=code,
server=server,
name=name,
message=message)
text = "Server error {server}:{name} {message}".format(server=server,
name=name,
message=message)
self.write(text, error=True)
self.write("\n")
@@ -203,12 +209,12 @@ class ConsoleView(PyCutExt, ConsoleCmd):
self.pointer = 0
if len(self.line):
self.history.append(self.line)
try:
self.lines.append(self.line)
source = "\n".join(self.lines)
self.more = self.onecmd(source)
except Exception as e:
print("Unknown error: {}".format(e))
try:
self.lines.append(self.line)
source = "\n".join(self.lines)
self.more = self.onecmd(source)
except Exception as e:
print("Unknown error: {}".format(e))
self.write(self.prompt)
self.lines = []

121
gns3/crash_report.py Normal file
View File

@@ -0,0 +1,121 @@
# -*- 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 platform
import struct
try:
import raven
RAVEN_AVAILABLE = True
except ImportError:
# raven is not installed with deb package in order to simplify packaging
RAVEN_AVAILABLE = False
from .utils.get_resource import get_resource
from .version import __version__
import logging
log = logging.getLogger(__name__)
# Dev build
if __version__[4] != 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://a86f12c42f7746288a81af9a9c1145a4:db8b6973bd2c448ea0a98675119ff8ee@app.getsentry.com/38506"
if hasattr(sys, "frozen"):
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):
# 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):
from .servers import Servers
local_server = Servers.instance().localServerSettings()
if local_server["report_errors"]:
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()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(platform.linux_distribution()),
"python:version": "{}.{}.{}".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2]),
"python:bit": struct.calcsize("P") * 8,
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
}
context = self._add_qt_informations(context)
client.tags_context(context)
try:
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(client.get_ident(report)))
def _add_qt_informations(self, context):
try:
from .qt import QtCore
import sip
except ImportError:
return context
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):
if cls._instance is None:
cls._instance = CrashReport()
return cls._instance

View File

@@ -15,19 +15,20 @@
# 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.
"""
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
# dynamically add the current version number

View File

@@ -19,11 +19,13 @@
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(QtWidgets.QDialog, Ui_configurationDialog):
class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
"""
Configuration dialog implementation.
@@ -35,7 +37,7 @@ 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)
@@ -53,12 +55,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

@@ -15,18 +15,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/>.
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.
"""
def __init__(self, parent, command, params):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self.setWindowTitle("Executing {}".format(command))
@@ -56,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

@@ -17,34 +17,38 @@
import os
import sys
import pkg_resources
from ..qt import QtCore, QtGui, QtWebKit
from ..qt import QtCore, QtGui, QtWebKitWidgets, QtWidgets
from ..ui.getting_started_dialog_ui import Ui_GettingStartedDialog
from ..utils.get_resource import get_resource
from ..local_config import LocalConfig
class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
class GettingStartedDialog(QtWidgets.QDialog, Ui_GettingStartedDialog):
"""
GettingStarted dialog.
"""
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.adjustSize()
self.uiWebView.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
self.uiWebView.page().setLinkDelegationPolicy(QtWebKitWidgets.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self.uiWebView.loadFinished.connect(self._loadFinishedSlot)
self.uiCheckBox.setChecked(QtCore.QSettings().value("GUI/hide_getting_started_dialog", False, type=bool))
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self._loadFinishedSlot)
self._timer.setSingleShot(True)
self._timer.start(5000)
self.uiWebView.load(QtCore.QUrl("http://start.gns3.net"))
self._local_config = LocalConfig.instance()
settings = parent.settings()
self.uiCheckBox.setChecked(settings["hide_getting_started_dialog"])
getting_started = get_resource(os.path.join("static", "getting_started.html"))
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl.fromLocalFile(getting_started))
else:
self.uiCheckBox.setChecked(True)
self.accept()
def showit(self):
"""
@@ -62,8 +66,10 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
:param result: ignored
"""
QtCore.QSettings().setValue("GUI/hide_getting_started_dialog", self.uiCheckBox.isChecked())
QtGui.QDialog.done(self, result)
settings = self.parentWidget().settings()
settings["hide_getting_started_dialog"] = self.uiCheckBox.isChecked()
self.parentWidget().setSettings(settings)
super().done(result)
def _urlClickedSlot(self, url):
"""
@@ -73,30 +79,4 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
"""
if QtGui.QDesktopServices.openUrl(url) is False:
QtGui.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))
def _loadFinishedSlot(self, result=False):
"""
Slot called when the web page has been loaded.
:param result: boolean
"""
self.uiWebView.loadFinished.disconnect(self._loadFinishedSlot)
self._timer.stop()
self._timer.timeout.disconnect()
if result is False:
# load a local resource if the page is not available
resource_name = os.path.join("static", "getting_started.html")
getting_started = None
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
getting_started = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
getting_started_page = pkg_resources.resource_filename("gns3", resource_name)
getting_started = os.path.normpath(getting_started_page)
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl("file://{}".format(getting_started)))
else:
self.uiCheckBox.setChecked(True)
self.accept()
QtWidgets.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))

View File

@@ -15,24 +15,26 @@
# 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 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.
"""
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
@@ -51,11 +53,13 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
Shows the help for Idle-PC.
"""
help_text = "Finding the right idlepc value is a trial and error process, consisting of applying " \
"different Idle-PC values and monitoring the CPU usage.\n\nBest Idle-PC values are usually " \
"obtained when IOS is in idle state, the following message being displayed " \
"on the console: {} con0 is now available ... Press RETURN to get started.".format(self._router.name())
QtGui.QMessageBox.information(self, "Hints for Idle-PC", help_text)
help_text = """Best Idle-PC values are obtained when IOS is in idle state, after the "Press RETURN to get started" message has appeared on the console, messages have finished displaying on the console and you have have actually pressed the RETURN key.
Finding the right idle-pc value is a trial and error process, consisting of applying different Idle-PC values and monitoring the CPU usage.
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.
"""
QtWidgets.QMessageBox.information(self, "Hints for Idle-PC", help_text)
def _applySlot(self):
"""
@@ -63,17 +67,19 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
"""
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())
# apply Idle-PC to all routers with the same IOS image
ios_image = self._router.settings()["image"]
ios_image = os.path.basename(self._router.settings()["image"])
for node in Topology.instance().nodes():
if hasattr(node, "idlepcs") and node.settings()["image"] == ios_image:
if hasattr(node, "idlepc") and node.settings()["image"] == ios_image:
node.setIdlepc(idlepc)
# apply the idle-pc to templates with the same IOS image
self._router.module().updateImageIdlepc(ios_image, idlepc)
def done(self, result):
"""
Called when the dialog is closed.
@@ -83,5 +89,4 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
if result:
self._applySlot()
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -1,68 +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(
project_file_name,
self.project_dest_path,
self.images_dest_path,
self.cloud_settings
)
progress_dialog = ProgressDialog(download_thread, "Importing project", "Downloading project files...", "Cancel",
parent=self.parent())
progress_dialog.show()
progress_dialog.exec_()
self.close()
def _deleteProject(self):
project_file_name = self.projects[self.listWidget.currentItem().text()]
button_clicked = QtGui.QMessageBox.question(
self,
"Delete project",
"Are you sure you want to delete project " + self.listWidget.currentItem().text(),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.Yes
)
if button_clicked == QtGui.QMessageBox.Yes:
delete_project_thread = DeleteProjectThread(project_file_name, self.cloud_settings)
progress_dialog = ProgressDialog(delete_project_thread, "Deleting project", "Deleting project files...",
"Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
self._listCloudProjects()

View File

@@ -16,13 +16,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import shutil
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,11 +32,11 @@ 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
self._project_settings = parent.projectSettings().copy()
self._project_settings = {}
default_project_name = "untitled"
self.uiNameLineEdit.setText(default_project_name)
self.uiLocationLineEdit.setText(os.path.join(self._main_window.projectsDirPath(), default_project_name))
@@ -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,34 +113,28 @@ 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
self._project_settings["project_path"] = os.path.join(project_location, project_name + ".gns3")
self._project_settings["project_files_dir"] = os.path.join(project_location, project_name + "-files")
self._project_settings["project_files_dir"] = project_location
self._project_settings["project_type"] = project_type
# delete all the project files
shutil.rmtree(self._project_settings["project_files_dir"], ignore_errors=True)
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -19,13 +19,17 @@
Dialog to configure and update node settings using widget pages.
"""
from ..qt import QtCore, QtGui
from ..ui.node_configurator_dialog_ui import Ui_NodeConfiguratorDialog
from gns3.http_client import HTTPClient
from gns3.progress import Progress
from ..qt import QtCore, QtGui, QtWidgets
from ..ui.node_properties_dialog_ui import Ui_NodePropertiesDialog
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
@@ -33,30 +37,30 @@ 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)
def _loadNodeItems(self):
"""
Loads the nodes into the Node configurator QTreeWidget
Loads the nodes into the Node properties QTreeWidget
"""
# create the parent (group) items
@@ -65,8 +69,8 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
continue
group_name = " {} group".format(str(node_item.node()))
parent = group_name
if not parent in self._parent_items:
item = QtGui.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
if parent not in self._parent_items:
item = QtWidgets.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
item.setIcon(0, QtGui.QIcon(node_item.node().defaultSymbol()))
item.setExpanded(True)
self._parent_items[parent] = item
@@ -76,11 +80,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.
@@ -117,11 +129,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):
"""
@@ -131,15 +143,15 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
"""
try:
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):
QtGui.QDialog.reject(self)
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
self.applySettings()
QtGui.QDialog.accept(self)
QtWidgets.QDialog.accept(self)
except ConfigurationError:
pass
@@ -165,7 +177,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
page.saveSettings(settings, node, group=True)
for index in range(0, item.childCount()):
child = item.child(index)
#child.node().update(settings) #TODO: delete
# child.node().update(settings) #TODO: delete
child.settings().update(settings)
# update the nodes with the settings
@@ -200,7 +212,8 @@ 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.
Store temporary node settings configured in a page widget.
@@ -212,7 +225,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()
@@ -233,7 +246,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
"""
@@ -269,10 +282,11 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
class ConfigurationError(Exception):
"""
Exception to be raised when a configuration error occurs.
"""
def __init__(self):
Exception.__init__(self)
super().__init__()

View File

@@ -19,17 +19,16 @@
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
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
"""
Preferences dialog implementation.
@@ -38,17 +37,23 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
super().__init__(parent)
self.setupUi(self)
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])
# Something has change?
self._modified = False
def _loadPreferencePages(self):
"""
Loads all preference pages (built-ins and from modules).
@@ -60,18 +65,17 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
ServerPreferencesPage,
PacketCapturePreferencesPage,
]
if ENABLE_CLOUD:
pages.append(CloudPreferencesPage)
for page in pages:
preferences_page = page()
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:
@@ -81,17 +85,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.
@@ -104,8 +134,12 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
current = previous
preferences_page = current.data(0, QtCore.Qt.UserRole)
name = preferences_page.windowTitle()
self.uiTitleLabel.setText("{} preferences".format(name))
accessible_name = preferences_page.accessibleName()
if accessible_name:
self.uiTitleLabel.setText(accessible_name)
else:
name = preferences_page.windowTitle()
self.uiTitleLabel.setText("{} preferences".format(name))
index = self.uiStackedWidget.indexOf(preferences_page)
widget = self.uiStackedWidget.widget(index)
self.uiStackedWidget.setMinimumSize(widget.size())
@@ -124,6 +158,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):
@@ -131,7 +168,16 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
Closes this dialog.
"""
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):
"""
@@ -139,9 +185,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():
QtGui.QDialog.accept(self)
QtWidgets.QDialog.accept(self)

View File

@@ -0,0 +1,275 @@
# -*- 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
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
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.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
settings = parent.settings()
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
# by default all radio buttons are unchecked
self.uiVmwareRadioButton.setAutoExclusive(False)
self.uiVirtualBoxRadioButton.setAutoExclusive(False)
self.uiVmwareRadioButton.setChecked(False)
self.uiVirtualBoxRadioButton.setChecked(False)
def _listVMwareVMsSlot(self):
"""
Slot to refresh the VMware VMs list.
"""
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 (require for VMware player) is probably not installed")
return
self._refreshVMListSlot()
def _listVirtualBoxVMsSlot(self):
"""
Slot to refresh the VirtualBox VMs list.
"""
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)
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:
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.
"""
if not Servers.instance().localServerIsRunning():
QtWidgets.QMessageBox.critical(self, "Local server", "{}".format("Local server is not running"))
return
server = Servers.instance().localServer()
if self.uiVmwareRadioButton.isChecked():
server.get("/vmware/vms", self._getVMsFromServerCallback)
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,15 +24,16 @@ 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_thread import ProcessFilesThread
from ..utils.process_files_worker import ProcessFilesWorker
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
from ..topology import Topology
from ..node import Node
class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
"""
Snapshots dialog implementation.
@@ -41,11 +42,11 @@ 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
self._project_files_dir = project_files_dir
self._project_files_dir = os.path.join(project_files_dir, "project-files")
self.uiCreatePushButton.clicked.connect(self._createSnapshotSlot)
self.uiDeletePushButton.clicked.connect(self._deleteSnapshotSlot)
@@ -69,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))
@@ -88,15 +89,14 @@ 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)
snapshot_name = "{name}_{date}".format(name=snapshot_name, date=time.strftime("%d%m%y_%H%M%S"))
snapshot_dir = os.path.join(self._project_files_dir, "snapshots", snapshot_name)
thread = ProcessFilesThread(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
thread.deleteLater()
progress_dialog = ProgressDialog(thread, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
worker = ProcessFilesWorker(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
progress_dialog = ProgressDialog(worker, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
self._listSnaphosts()
@@ -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
@@ -145,15 +145,37 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
if hasattr(node, "start") and node.status() == Node.started:
node.stop()
#FIXME: problably a bug when restoring a snapshot and the project name has changed.
thread = ProcessFilesThread(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
thread.deleteLater()
progress_dialog = ProgressDialog(thread, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
project_name, _ = os.path.splitext(os.path.basename(self._project_path))
legacy_project_files_dir = os.path.join(snapshot_path, "{}-files".format(project_name))
if os.path.exists(legacy_project_files_dir):
# support for pre 1.3 snapshots
for root, dirs, _ in os.walk(self._project_files_dir):
dirs[:] = [d for d in dirs if d not in "snapshots"]
for project_subdir in dirs:
project_subdir_path = os.path.join(root, project_subdir)
shutil.rmtree(project_subdir_path, ignore_errors=True)
dirs = os.listdir(legacy_project_files_dir)
for snapshot_subdir in dirs:
snapshot_subdir_path = os.path.join(legacy_project_files_dir, snapshot_subdir)
worker = ProcessFilesWorker(snapshot_subdir_path, os.path.join(self._project_files_dir, snapshot_subdir))
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
try:
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:
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)
progress_dialog.show()
progress_dialog.exec_()
from ..main_window import MainWindow
MainWindow.instance().loadProject(self._project_path)
MainWindow.instance().loadSnapshot(self._project_path)
self.accept()
def _snapshotDoubleClickedSlot(self, item):

View File

@@ -19,11 +19,12 @@
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.
@@ -33,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)
@@ -73,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(),
@@ -86,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(),
@@ -117,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,22 @@
# 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 QtSvg, QtCore, QtGui, QtWidgets
from ..items.svg_node_item import SvgNodeItem
from ..items.pixmap_node_item import PixmapNodeItem
from ..ui.symbol_selection_dialog_ui import Ui_SymbolSelectionDialog
from ..node import Node
import logging
log = logging.getLogger(__name__)
class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
"""
Symbol selection dialog.
@@ -32,69 +39,157 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
:param items: list of items
"""
def __init__(self, parent, items=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._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
selected_symbol = symbol
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
}
for name, category in categories.items():
self.uiCategoryComboBox.addItem(name, category)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
else:
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
first_item = items[0]
if isinstance(first_item, SvgNodeItem):
custom_symbol = first_item.renderer().objectName()
if not custom_symbol:
symbol_name = first_item.node().defaultSymbol()
else:
symbol_name = custom_symbol
selected_symbol = symbol_name
elif isinstance(first_item, PixmapNodeItem):
selected_symbol = first_item.pixmapSymbolPath()
custom_symbol = True
self.uiBuiltInSymbolRadioButton.setChecked(True)
self.uiSymbolListWidget.setFocus()
self.uiSymbolListWidget.setIconSize(QtCore.QSize(64, 64))
symbol_resources = QtCore.QResource(":/symbols")
for symbol in symbol_resources.children():
if symbol.endswith('.normal.svg'):
name = symbol[:-11]
item = QtGui.QListWidgetItem(self.uiSymbolListWidget)
if symbol.endswith(".svg"):
name = os.path.splitext(symbol)[0]
item = QtWidgets.QListWidgetItem(self.uiSymbolListWidget)
item.setText(name)
item.setIcon(QtGui.QIcon(':/symbols/' + symbol))
resource_path = ":/symbols/" + symbol
svg_renderer = QtSvg.QSvgRenderer(resource_path)
if resource_path == selected_symbol:
# this is a built-in symbol
custom_symbol = False
self.uiSymbolListWidget.setCurrentItem(item)
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
# Set the ARGB to 0 to prevent rendering artifacts
image.fill(0x00000000)
svg_renderer.render(QtGui.QPainter(image))
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
item.setIcon(icon)
if custom_symbol:
# this is a custom symbol
self.uiCustomSymbolRadioButton.setChecked(True)
self.uiSymbolLineEdit.setText(selected_symbol)
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(selected_symbol))
self.uiBuiltInGroupBox.setEnabled(False)
self.uiBuiltInGroupBox.hide()
self.adjustSize()
def _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:
if self.uiSymbolListWidget.isEnabled():
current = self.uiSymbolListWidget.currentItem()
if current:
name = current.text()
path = ":/symbols/{}.svg".format(name)
renderer = QtSvg.QSvgRenderer(path)
renderer.setObjectName(path)
for item in self._items:
if isinstance(item, SvgNodeItem):
item.setSharedRenderer(renderer)
else:
pixmap = QtGui.QPixmap(path)
if not pixmap.isNull():
item.setPixmap(pixmap)
item.setPixmapSymbolPath(path)
else:
QtWidgets.QMessageBox.critical(self, "Built-in SVG symbol", "Built-in SVG symbol cannot be applied on Pixmap node item")
return False
else:
symbol_path = self.uiSymbolLineEdit.text()
pixmap = QtGui.QPixmap(symbol_path)
if not pixmap.isNull():
for item in self._items:
if isinstance(item, PixmapNodeItem):
item.setPixmap(pixmap)
item.setPixmapSymbolPath(symbol_path)
else:
renderer = QtSvg.QSvgRenderer(symbol_path)
renderer.setObjectName(symbol_path)
if renderer.isValid():
item.setSharedRenderer(renderer)
else:
QtWidgets.QMessageBox.critical(self, "Custom pixmap symbol", "Custom pixmap symbol which is not SVG format cannot be applied on SVG node item")
return False
return True
def getSymbol(self):
if self.uiSymbolListWidget.isEnabled():
current = self.uiSymbolListWidget.currentItem()
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)
for item in self._items:
item.setDefaultRenderer(default_renderer)
item.setHoverRenderer(hover_renderer)
normal_symbol = ":/symbols/{}.svg".format(name)
else:
normal_symbol = self.uiSymbolLineEdit.text()
return normal_symbol
def getSymbols(self):
def _symbolBrowserSlot(self):
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
# supported image file formats
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._symbols_dir, file_formats)
if not path:
return
def getCategory(self):
return self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
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):
"""
@@ -103,6 +198,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,11 +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.
@@ -33,32 +33,45 @@ 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.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)
@@ -67,11 +80,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):
"""
@@ -82,7 +93,7 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
item.setDefaultTextColor(self._color)
item.setFont(self.uiPlainTextEdit.font())
item.setRotation(self.uiRotationSpinBox.value())
if item.editable():
if item.editable() and self.uiApplyTextToAllItemsCheckBox.isChecked():
item.setPlainText(self.uiPlainTextEdit.toPlainText())
def done(self, result):
@@ -94,4 +105,4 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
if result:
self._applyPreferencesSlot()
QtGui.QDialog.done(self, result)
super().done(result)

View File

@@ -0,0 +1,167 @@
#!/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["filename"]:
line_edit.setText(item["filename"])
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())["filename"])
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
for combo_box in self._images_combo_boxes:
combo_box.clear()
for vm in result:
combo_box.addItem(vm["filename"], vm)

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

@@ -0,0 +1,164 @@
# -*- 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._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()
for server in Servers.instance().remoteServers().values():
self.uiRemoteServersComboBox.addItem(server.url(), server)
if hasattr(self, "uiVMRadioButton") and not GNS3VM.instance().isRunning():
self.uiVMRadioButton.setEnabled(False)
if hasattr(self, "uiVMRadioButton") and GNS3VM.instance().isRunning():
self.uiVMRadioButton.setChecked(True)
elif self._use_local_server and self.uiLocalRadioButton.isChecked():
self.uiLocalRadioButton.setChecked(True)
else:
self.uiRemoteRadioButton.setChecked(True)
else:
if self.uiLocalRadioButton.isChecked():
servers = Servers.instance()
if servers.localServerAutoStart() and not servers.localServerIsRunning():
QtWidgets.QMessageBox.critical(self, "Wizard", "Local server is not running")
def validateCurrentPage(self):
"""
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)

276
gns3/gns3_vm.py Normal file
View File

@@ -0,0 +1,276 @@
# -*- 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
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)
@staticmethod
def execute_vmrun(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))
output = subprocess.check_output(command, timeout=timeout)
return output.decode("utf-8", errors="ignore").strip()
@staticmethod
def execute_vboxmanage(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))
output = subprocess.check_output(command, timeout=timeout)
return output.decode("utf-8", errors="ignore").strip()
@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":
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

779
gns3/http_client.py Normal file
View File

@@ -0,0 +1,779 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import http
import ipaddress
import uuid
import urllib.request
import pathlib
import base64
from functools import partial
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork
from .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
class HTTPClient(QtCore.QObject):
"""
HTTP client.
:param settings: Dictionnary with connection information to the server
:param network_manager: A QT network manager
"""
_instance_count = 1
# Callback class used for displaying progress
_progress_callback = None
connected_signal = QtCore.Signal()
connection_error_signal = QtCore.Signal(str)
def __init__(self, settings, network_manager):
super().__init__()
self._version = ""
self._scheme = settings.get("protocol", "http")
self._host = settings["host"]
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._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 getTunnel(self, port):
"""
Get a tunnel to the remote port.
For HTTP standard client it's the same port. For SSH it will create a new tunnel.
:param port: Remote port
:returns: Tuple host, port to connect
"""
return self._host, port
def releaseTunnel(self, port):
"""
Release a tunnel to the remote port.
For HTTP standard client it's do nothing
:param port: Allocated remote port
"""
pass
def settings(self):
"""
Return a dictionnary with server settings
"""
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:
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):
"""
Called when a query is over
"""
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):
"""
:param progress_callback: A progress callback instance
"""
cls._progress_callback = progress_callback
@staticmethod
def reset():
"""Reset HTTP client internal variables"""
HTTPClient._instance_count = 0
def url(self):
"""Returns current server url"""
return getNetworkUrl(self.protocol(), self.host(), self.port(), self.user(), self.settings())
def id(self):
"""
Returns this HTTP Client identifier.
:returns: HTTP client identifier (string)
"""
return self._id
def setLocal(self, value):
"""
Sets either this is a connection to a local server or not.
:param value: boolean
"""
self._local = value
def isLocal(self):
"""
Returns either this is a connection to a local server or not.
:returns: boolean
"""
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.
:returns: True or False
"""
return self._connected
def close(self):
"""
Closes the connection with the server.
"""
log.info("Connection to %s closed", self.url())
self._connected = False
def isLocalServerRunning(self):
"""
Synchronous check if a server is already running on this host.
:returns: boolean
"""
status, json_data = self.getSynchronous("version", timeout=2)
if json_data is None or status != 200:
return False
else:
version = json_data.get("version")
local_server = json_data.get("local", False)
if version != __version__:
log.debug("Client version {} differs with server version {}".format(__version__, version))
return False
if not local_server:
log.debug("Running server is not a GNS3 local server (not started with --local)")
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,
user=self._user,
passwd=self._password)
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
response = urllib.request.urlopen(url, timeout=timeout)
content_type = response.getheader("CONTENT-TYPE")
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 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, **kwargs):
"""
HTTP GET on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("GET", path, callback, **kwargs)
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
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("PUT", path, callback, **kwargs)
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
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("POST", path, callback, **kwargs)
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
Full arg list in createHTTPQuery
"""
self.createHTTPQuery("DELETE", path, callback, **kwargs)
def _request(self, url):
"""
Get a QNetworkRequest object. You can mock this
if you want low level mocking.
:param url: Url of remote ressource (QtCore.QUrl)
:returns: QT Network request (QtNetwork.QNetworkRequest)
"""
return QtNetwork.QNetworkRequest(url)
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 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:
return self.executeHTTPQuery(method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
else:
log.info("Connection to {}".format(self.url()))
query = partial(self._callbackConnect, method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
self._connect(query)
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 len(msg) > 0:
msg = "Can't connect to server {}: {}".format(self.url(), msg)
else:
msg = "Can't connect to server {}".format(self.url())
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 or pathlib.Path)
:param original_context: Original context
:param callback: callback method to call when the server replies
"""
if error is not False:
self._connectionError(callback)
return
if "version" not in params or "local" not in params:
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)
return
if params["version"] != __version__:
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
log.error(msg)
# Stable release
if __version_info__[3] == 0:
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
# 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 is forbidden for security reasons"
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
self._connected = True
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
"""
if self._user:
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(b"Authorization", auth_string.encode())
return request
def executeHTTPQuery(self, method, path, callback, body, context={}, downloadProgressCallback=None, showProgress=True, ignoreErrors=False, progressText=None):
"""
Call the remote server
:param method: HTTP method
:param path: Remote path
: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
"""
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(b"User-Agent", "GNS3 QT Client v{version}".format(version=__version__).encode())
# 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)
response = self._network_manager.sendCustomRequest(request, method.encode(), body)
import copy
context = copy.deepcopy(context)
query_id = str(uuid.uuid4())
context["query_id"] = query_id
response.finished.connect(partial(self._processResponse, response, callback, context, body, ignoreErrors))
if downloadProgressCallback is not None:
response.downloadProgress.connect(partial(self._processDownloadProgress, response, downloadProgressCallback, context))
if showProgress:
response.uploadProgress.connect(partial(self.notify_progress_upload, query_id))
response.downloadProgress.connect(partial(self.notify_progress_download, 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):
"""
Process a packet receive on the notification feed.
The feed can contains partial JSON. If we found a
part of a JSON we keep it for the next packet
"""
if response.error() == QtNetwork.QNetworkReply.NoError:
error_code = response.error()
if error_code >= 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)
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
def _requestCanceled(self, response, context):
if response.isRunning():
response.abort()
if "query_id" in context:
self.notify_progress_end_query(context["query_id"])
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()
if not ignore_errors:
log.info("Response error: %s (error: %d)", error_message, error_code)
if error_code < 200:
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").strip("\0")
# Some time antivirus intercept our query and reply with garbage content
except UnicodeError:
body = None
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
if callback is not None:
if not body or content_type != "application/json":
callback({"message": error_message}, error=True, server=self, context=context)
else:
log.debug(body)
try:
callback(json.loads(body), error=True, server=self, context=context)
except ValueError:
# It's happen 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))
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 len(body.strip(" \n\t")) > 0 and content_type == "application/json":
params = json.loads(body)
else:
params = {}
if callback is not None:
if status >= 400:
callback(params, error=True, server=self, context=context)
else:
callback(params, server=self, context=context)
# response.deleteLater()
if status == 400:
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):
"""
Returns a representation of this server.
:returns: dictionary
"""
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

159
gns3/image_manager.py Normal file
View File

@@ -0,0 +1,159 @@
# -*- 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
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 = os.path.basename(path)
server.post('{}/{}'.format(upload_endpoint, filename), None, body=pathlib.Path(path))
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):
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 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
"""
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

187
gns3/iouvm_converter.py Normal file
View File

@@ -0,0 +1,187 @@
#!/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 signal
import json
import os
from datetime import datetime
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...")
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"] == 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,19 +19,20 @@
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.
"""
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
@@ -57,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,13 +19,14 @@
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
class EthernetLinkItem(LinkItem):
"""
Ethernet link for the scene.
@@ -40,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
@@ -74,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:
@@ -105,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,21 +19,20 @@
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.
"""
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)
@@ -47,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.
@@ -67,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
@@ -92,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,11 @@ 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.
@@ -43,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
@@ -180,33 +181,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)
@@ -223,18 +224,30 @@ 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()
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
# 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
def _deleteActionSlot(self):
"""
Slot to receive events from the delete action in the
@@ -261,10 +274,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]
@@ -282,7 +295,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]
@@ -302,7 +315,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, "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()
@@ -313,7 +326,7 @@ class LinkItem(QtGui.QGraphicsPathItem):
elif self._destination_port.capturing():
self._destination_port.startPacketCaptureReader()
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):
"""
@@ -325,7 +338,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()
@@ -336,7 +349,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,24 +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():
class NodeItem(QtSvg.QGraphicsSvgItem):
"""
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
@@ -47,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.
@@ -93,42 +88,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# from the server.
self._last_error = None
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
from ..main_window import MainWindow
self._main_window = MainWindow.instance()
self._settings = self._main_window.uiGraphicsView.settings()
def setUnsavedState(self):
"""
@@ -225,7 +187,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
if self._node_label:
self._node_label.setPlainText(self._node.name())
if self._node_label.toPlainText() != self._node.name():
self._node_label.setPlainText(self._node.name())
self._centerLabel()
self.setUnsavedState()
# update the link tooltips in case the
@@ -253,13 +217,12 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self.scene().removeItem(self)
self.setUnsavedState()
def serverErrorSlot(self, node_id, code, message):
def serverErrorSlot(self, node_id, message):
"""
Slot to receive events from the attached Node instance
when the node has received an error from the server.
:param node_id: node identifier
:param code: error code
:param message: error message
"""
@@ -308,6 +271,19 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._node_label = label
def _centerLabel(self):
"""
Centers the node label.
"""
text_rect = self._node_label.boundingRect()
text_middle = text_rect.topRight() / 2
node_rect = self.boundingRect()
node_middle = node_rect.topRight() / 2
label_x_pos = node_middle.x() - text_middle.x()
label_y_pos = -25
self._node_label.setPos(label_x_pos, label_y_pos)
def _showLabel(self):
"""
Shows the node label on the scene.
@@ -317,13 +293,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._node_label = NoteItem(self)
self._node_label.setEditable(False)
self._node_label.setPlainText(self._node.name())
text_rect = self._node_label.boundingRect()
text_middle = text_rect.topRight() / 2
node_rect = self.boundingRect()
node_middle = node_rect.topRight() / 2
label_x_pos = node_middle.x() - text_middle.x()
label_y_pos = -25
self._node_label.setPos(label_x_pos, label_y_pos)
self._centerLabel()
def connectToPort(self, unavailable_ports=[]):
"""
@@ -335,35 +305,43 @@ 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 by port name
port_names = {}
# sort the ports
ports_dict = {}
for port in ports:
port_names[port.name()] = port
if port.adapterNumber() is not None:
# make the port number unique (special case with WICs).
port_number = port.portNumber()
if port_number >= 16:
port_number *= 8
ports_dict[(port.adapterNumber() * 16) + port_number] = port
elif port.portNumber()is not None:
ports_dict[port.portNumber()] = port
else:
ports_dict[port.name()] = port
try:
# try a numeric sort first
ports = sorted(port_names.keys(), key=int)
ports = sorted(ports_dict.keys(), key=int)
except ValueError:
# fall back to a classic sort
ports = sorted(port_names.keys())
ports = sorted(ports_dict.keys())
# show a contextual menu for the user to choose a port
for port in ports:
port_object = port_names[port]
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)
action = menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
action.setDisabled(True)
elif port_object.isFree():
menu.addAction(QtGui.QIcon(':/icons/led_red.svg'), port)
menu.addAction(QtGui.QIcon(':/icons/led_red.svg'), port_object.name())
else:
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port)
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
menu.triggered.connect(self.selectedPortSlot)
menu.exec_(QtGui.QCursor.pos())
@@ -394,19 +372,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):
"""
@@ -418,8 +396,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
# don't show the selection rectangle
option.state = QtGui.QStyle.State_None
QtSvg.QGraphicsSvgItem.paint(self, painter, option, widget)
if not self._settings["draw_rectangle_selected_item"]:
option.state = QtWidgets.QStyle.State_None
super().paint(painter, option, widget)
if not self._initialized or self.show_layer:
brect = self.boundingRect()
@@ -443,7 +422,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)
@@ -467,13 +446,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):
"""
@@ -482,7 +456,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,10 +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.
@@ -33,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()
@@ -58,6 +59,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
self.scene().removeItem(self)
from ..topology import Topology
Topology.instance().removeNote(self)
def editable(self):
@@ -77,9 +79,9 @@ class NoteItem(QtGui.QGraphicsTextItem):
"""
self._editable = value
#if not self._editable:
# if not self._editable:
# self.setFlag(self.ItemIsSelectable, enabled=False)
#else:
# else:
# self.setFlag(self.ItemIsSelectable)
def keyPressEvent(self, event):
@@ -100,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):
"""
@@ -131,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()
@@ -141,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):
"""
@@ -152,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
@@ -177,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)
@@ -197,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:
@@ -234,7 +236,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
if color:
self.setDefaultTextColor(QtGui.QColor(color))
if rotation is not None:
self.setRotation(rotation)
self.setRotation(float(rotation))
if z is not None:
self.setZValue(z)

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

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

View File

@@ -19,19 +19,20 @@
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.
"""
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
@@ -57,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,13 +20,14 @@ 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
class SerialLinkItem(LinkItem):
"""
Serial link for the scene.
@@ -41,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):
"""
@@ -88,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)
@@ -105,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,20 +19,21 @@
Base class for shape items (Rectangle, ellipse etc.).
"""
from ..qt import QtCore, QtGui
from ..qt import QtCore, QtGui, QtWidgets
class ShapeItem:
"""
Base class to draw shapes on the scene.
"""
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
@@ -57,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):
"""
@@ -68,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):
"""
@@ -93,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):
"""
@@ -144,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):
"""
@@ -207,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)
@@ -280,7 +281,7 @@ class ShapeItem:
color = QtGui.QColor(color)
else:
color = QtGui.QColor(255, 255, 255)
if transparency:
if transparency is not None:
color.setAlpha(transparency)
self.setBrush(QtGui.QBrush(color))
@@ -294,7 +295,7 @@ class ShapeItem:
pen.setColor(border_color)
if border_width is not None:
pen.setWidth(int(border_width))
if border_style:
if border_style is not None:
pen.setStyle(QtCore.Qt.PenStyle(border_style))
self.setPen(pen)

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,50 @@
# -*- 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 .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 = QtSvg.QSvgRenderer(symbol)
if symbol != node.defaultSymbol():
renderer.setObjectName(symbol)
else:
renderer = QtSvg.QSvgRenderer(node.defaultSymbol())
self.setSharedRenderer(renderer)

View File

@@ -1,184 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
JSON-RPC protocol implementation.
http://www.jsonrpc.org/specification
"""
import json
import uuid
class JSONRPCObject(object):
"""
Base object for JSON-RPC requests, responses,
notifications and errors.
"""
def __init__(self):
return JSONRPCEncoder().default(self)
def __str__(self, *args, **kwargs):
return json.dumps(self, cls=JSONRPCEncoder)
def __call__(self):
return JSONRPCEncoder().default(self)
class JSONRPCEncoder(json.JSONEncoder):
"""
Creates the JSON-RPC message.
"""
def default(self, obj):
"""
Returns a Python dictionary corresponding to a JSON-RPC message.
"""
if isinstance(obj, JSONRPCObject):
message = {"jsonrpc": 2.0}
for field in dir(obj):
if not field.startswith('_'):
value = getattr(obj, field)
message[field] = value
return message
return json.JSONEncoder.default(self, obj)
class JSONRPCInvalidRequest(JSONRPCObject):
"""
Error response for an invalid request.
"""
def __init__(self):
JSONRPCObject.__init__(self)
self.id = None
self.error = {"code": -32600, "message": "Invalid Request"}
class JSONRPCMethodNotFound(JSONRPCObject):
"""
Error response for an method not found.
:param request_id: JSON-RPC identifier
"""
def __init__(self, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32601, "message": "Method not found"}
class JSONRPCInvalidParams(JSONRPCObject):
"""
Error response for invalid parameters.
:param request_id: JSON-RPC identifier
"""
def __init__(self, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32602, "message": "Invalid params"}
class JSONRPCInternalError(JSONRPCObject):
"""
Error response for an internal error.
:param request_id: JSON-RPC identifier (optional)
"""
def __init__(self, request_id=None):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32603, "message": "Internal error"}
class JSONRPCParseError(JSONRPCObject):
"""
Error response for parsing error.
"""
def __init__(self):
JSONRPCObject.__init__(self)
self.id = None
self.error = {"code": -32700, "message": "Parse error"}
class JSONRPCCustomError(JSONRPCObject):
"""
Error response for an custom error.
:param code: JSON-RPC error code
:param message: JSON-RPC error message
:param request_id: JSON-RPC identifier (optional)
"""
def __init__(self, code, message, request_id=None):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": code, "message": message}
class JSONRPCResponse(JSONRPCObject):
"""
JSON-RPC successful response.
:param result: JSON-RPC result
:param request_id: JSON-RPC identifier
"""
def __init__(self, result, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.result = result
class JSONRPCRequest(JSONRPCObject):
"""
JSON-RPC request.
:param method: JSON-RPC destination method
:param params: JSON-RPC params for the corresponding method (optional)
:param request_id: JSON-RPC identifier (generated by default)
"""
def __init__(self, method, params=None, request_id=None):
JSONRPCObject.__init__(self)
if request_id == None:
request_id = str(uuid.uuid4())
self.id = request_id
self.method = method
if params:
self.params = params
class JSONRPCNotification(JSONRPCObject):
"""
JSON-RPC notification.
:param method: JSON-RPC destination method
:param params: JSON-RPC params for the corresponding method (optional)
"""
def __init__(self, method, params=None):
JSONRPCObject.__init__(self)
self.method = method
if params:
self.params = params

View File

@@ -22,12 +22,14 @@ 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__)
class Link(QtCore.QObject):
"""
Link implementation.
@@ -47,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(),
@@ -71,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():
@@ -136,10 +141,12 @@ class Link(QtCore.QObject):
self._destination_port.name()))
# delete the NIOs on both source and destination nodes
self._source_node.deleteNIO(self._source_port)
if self._source_port.nio():
self._source_node.deleteNIO(self._source_port)
self._source_port.setFree()
self._source_node.updated_signal.emit()
self._destination_node.deleteNIO(self._destination_port)
if self._destination_port.nio():
self._destination_node.deleteNIO(self._destination_port)
self._destination_port.setFree()
self._destination_node.updated_signal.emit()
@@ -200,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)
@@ -214,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)
@@ -224,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
@@ -244,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
@@ -254,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
@@ -335,29 +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
self._source_node.deleteNIO(self._source_port)
self._source_port.setFree()
self._source_node.updated_signal.emit()
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
self._destination_node.deleteNIO(self._destination_port)
self._destination_port.setFree()
self._destination_node.updated_signal.emit()
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)
@@ -371,6 +394,7 @@ class Link(QtCore.QObject):
self._source_nio_active = False
self._destination_nio_active = False
self.deleteLink()
def dump(self):
"""
@@ -384,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()}

301
gns3/local_config.py Normal file
View File

@@ -0,0 +1,301 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import json
import shutil
import copy
from .qt import QtCore
from .version import __version__
from .utils import parse_version
import logging
log = logging.getLogger(__name__)
class LocalConfig(QtCore.QObject):
"""
Handles the local GUI settings.
"""
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"
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)
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)
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._readConfig(system_wide_config_file)
config_file_in_cwd = os.path.join(os.getcwd(), filename)
if os.path.exists(config_file_in_cwd):
# use any config file present in the current working directory
self._config_file = config_file_in_cwd
elif not os.path.exists(self._config_file):
try:
# create the config file if it doesn't exist
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
with open(self._config_file, "w", encoding="utf-8") as f:
json.dump({"version": __version__, "type": "settings"}, f)
except OSError as e:
log.error("Could not create the config file {}: {}".format(self._config_file, e))
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
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:
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):
"""
Write the configuration file.
"""
self._settings["version"] = __version__
try:
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.
:returns: path to the config file.
"""
return self._config_file
def setConfigFilePath(self, config_file):
"""
Set a new config file
:returns: path to the config file.
"""
self._config_file = config_file
self._readConfig(self._config_file)
def settings(self):
"""
Get the settings.
:returns: settings (dict)
"""
return copy.deepcopy(self._settings)
def setSettings(self, settings):
"""
Save the settings.
:param settings: settings to save (dict)
"""
if self._settings != settings:
self._settings.update(settings)
self._writeConfig()
def loadSectionSettings(self, section, default_settings):
"""
Get all the settings from a given section.
:param default_settings: setting names and default values (dict)
:returns: settings (dict)
"""
settings = self.settings().get(section, dict())
def _copySettings(local, default):
"""
Copy only existing settings, ignore the other.
Add default values if require.
"""
# use default values for missing settings
for name, value in default.items():
if name not in local:
local[name] = value
elif isinstance(value, dict):
local[name] = _copySettings(local[name], default[name])
return local
settings = _copySettings(settings, default_settings)
self._settings[section] = settings
return copy.deepcopy(settings)
def saveSectionSettings(self, section, settings):
"""
Save all the settings in a given section.
:param section: section name
:param settings: settings to save (dict)
"""
if section not in self._settings:
self._settings[section] = {}
if self._settings[section] != settings:
self._settings[section].update(settings)
log.info("Section %s has changed. Saving configuration", section)
self._writeConfig()
else:
log.debug("Section %s has not changed. Skip saving configuration", section)
def experimental(self):
"""
:returns: Boolean. True if experimental features allowed
"""
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["experimental_features"]
@staticmethod
def instance(config_file=None):
"""
Singleton to return only on instance of LocalConfig.
:returns: instance of LocalConfig
"""
if not hasattr(LocalConfig, "_instance") or LocalConfig._instance is None:
LocalConfig._instance = LocalConfig(config_file=config_file)
return LocalConfig._instance

140
gns3/local_server_config.py Normal file
View File

@@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import configparser
from gns3.qt import QtCore
import logging
log = logging.getLogger(__name__)
class LocalServerConfig:
"""
Local server configuration.
"""
def __init__(self):
appname = "GNS3"
self._config = configparser.RawConfigParser()
if sys.platform.startswith("win"):
filename = "gns3_server.ini"
else:
filename = "gns3_server.conf"
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()
except OSError as e:
log.error("Could not create the local server configuration {}: {}".format(self._config_file, e))
self.readConfig()
def readConfig(self):
"""
Read the configuration file.
"""
try:
self._config.read(self._config_file, encoding="utf-8")
except (OSError, configparser.Error, UnicodeEncodeError) as e:
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
def writeConfig(self):
"""
Write the configuration file.
"""
try:
log.debug("Write configuration file %s", self._config_file)
with open(self._config_file, "w", encoding="utf-8") as fp:
self._config.write(fp)
except (OSError, configparser.Error) as e:
log.error("Could not write the local server configuration {}: {}".format(self._config_file, e))
def loadSettings(self, section, default_settings, types):
"""
Get all the settings from a given section.
:param section: section name
:param default_settings: setting names and default values (dict)
:param types: setting types (dict)
:returns: settings (dict)
"""
if section not in self._config:
self._config[section] = {}
settings = {}
for name, default in default_settings.items():
if types[name] is int:
settings[name] = self._config[section].getint(name, default)
elif types[name] is bool:
settings[name] = self._config[section].getboolean(name, default)
elif types[name] is float:
settings[name] = self._config[section].getfloat(name, default)
else:
settings[name] = self._config[section].get(name, default)
# sync with the config file
self.saveSettings(section, settings)
return settings
def saveSettings(self, section, settings):
"""
Save all the settings in a given section.
:param section: section name
:param settings: settings to save (dict)
"""
changed = False
if section not in self._config:
self._config[section] = {}
changed = True
for name, value in settings.items():
if name not in self._config[section] or self._config[section][name] != str(value):
self._config[section][name] = str(value)
changed = True
if changed:
self.writeConfig()
@staticmethod
def instance():
"""
Singleton to return only on instance of LocalServerConfig.
:returns: instance of Config
"""
if not hasattr(LocalServerConfig, "_instance"):
LocalServerConfig._instance = LocalServerConfig()
return LocalServerConfig._instance

109
gns3/logger.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python
# -*- 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/>.
"""Provide a pretty logging on console"""
import logging
import sys
import os
class ColouredFormatter(logging.Formatter):
RESET = '\x1B[0m'
RED = '\x1B[31m'
YELLOW = '\x1B[33m'
GREEN = '\x1B[32m'
PINK = '\x1b[35m'
def format(self, record, colour=False):
message = super().format(record)
if not colour or sys.platform.startswith("win"):
return message.replace("#RESET#", "")
level_no = record.levelno
if level_no >= logging.CRITICAL:
colour = self.RED
elif level_no >= logging.ERROR:
colour = self.RED
elif level_no >= logging.WARNING:
colour = self.YELLOW
elif level_no >= logging.INFO:
colour = self.GREEN
elif level_no >= logging.DEBUG:
colour = self.PINK
else:
colour = self.RESET
message = message.replace("#RESET#", self.RESET)
message = '{colour}{message}{reset}'.format(colour=colour, message=message, reset=self.RESET)
return message
class ColouredStreamHandler(logging.StreamHandler):
def format(self, record, colour=False):
if not isinstance(self.formatter, ColouredFormatter):
self.formatter = ColouredFormatter()
return self.formatter.format(record, colour)
def emit(self, record):
stream = self.stream
try:
msg = self.format(record, stream.isatty())
stream.write(msg)
stream.write(self.terminator)
self.flush()
# On OSX when frozen flush raise a BrokenPipeError
except BrokenPipeError:
pass
except Exception:
self.handleError(record)
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", "{")
else:
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])
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,24 +16,50 @@
# 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 datetime
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, 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
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
from gns3.utils.get_resource import get_resource
import datetime
import traceback
import time
import locale
import argparse
import signal
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__
@@ -81,11 +107,44 @@ 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('--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("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 = "exception.log"
exception_file_path = "exceptions.log"
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:
os.chdir(frozen_dir)
def exceptionHook(exception, value, tb):
@@ -94,22 +153,27 @@ 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/support/bug\n")
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
print("".join(lines))
try:
curdate = time.strftime("%d %b %Y %H:%M:%S")
logfile = open(exception_file_path, "a")
logfile = open(exception_file_path, "a", encoding="utf-8")
logfile.write("=== GNS3 {} traceback on {} ===\n".format(__version__, curdate))
logfile.write("".join(lines))
logfile.close()
except OSError as e:
print("Could not save traceback to {}: {}".format(exception_file_path, e))
print("Could not save traceback to {}: {}".format(os.path.normpath(exception_file_path), e))
if not sys.stdout.isatty():
# if stdout is not a tty (redirected to the console view),
# then print the exception on stderr too.
print("".join(lines), file=sys.stderr)
if exception is MemoryError:
print("YOUR SYSTEM IS OUT OF MEMORY!")
else:
CrashReport.instance().captureException(exception, value, tb)
# catch exceptions to write them in a file
sys.excepthook = exceptionHook
@@ -117,30 +181,22 @@ 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")
version = lambda version_string: [int(i) for i in version_string.split('.')]
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))
raise SystemExit("Requirement is Qt version 4.6 or higher, got version {}".format(QtCore.QT_VERSION_STR))
# 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))
try:
# if tornado is present then enable pretty logging.
import tornado.log
tornado.log.enable_pretty_logging()
except ImportError:
pass
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))
# check for the correct locale
# (UNIX/Linux only)
@@ -156,69 +212,63 @@ def main():
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
if sys.platform.startswith('win'):
if sys.platform.startswith('win') and hasattr(sys, "frozen"):
try:
import win32console
import win32con
import win32gui
except ImportError:
raise RuntimeError("Python for Windows extensions must be installed.")
raise SystemExit("Python for Windows extensions must be installed.")
try:
win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
exit_code = MainWindow.exit_code_reboot
while exit_code == MainWindow.exit_code_reboot:
exit_code = 0
app = QtGui.QApplication(sys.argv)
# this info is necessary for QSettings
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
# save client logging info to a file
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "GNS3_client.log") # FIXME: does it work?
try:
if not options.debug:
try:
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
except FileExistsError:
pass
handler = logging.FileHandler(logfile, "w")
if options.debug:
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
if len(root_logger.handlers) > 0:
root_handler = root_logger.handlers[0]
else:
root_handler = logging.StreamHandler()
root_logger.addHandler(root_handler)
root_handler.setLevel(logging.DEBUG)
else:
handler.setLevel(logging.INFO)
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
# hide the console
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
datefmt="%y%m%d %H:%M:%S")
handler.setFormatter(formatter)
log.addHandler(handler)
except OSError as e:
log.warn("could not log to {}: {}".format(logfile, e))
app = Application(sys.argv)
# 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)
mainwindow = MainWindow.instance()
mainwindow.show()
exit_code = app.exec_()
delattr(MainWindow, "_instance")
app.deleteLater()
# 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, logfile)
else:
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(LocalConfig.configDirectory(), exception_file_path)
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.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,7 @@ 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
from gns3.modules.docker import Docker
MODULES = [Builtin, VPCS, Dynamips, IOU, VirtualBox, Qemu]
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker, Builtin]

View File

@@ -19,11 +19,8 @@
Built-in module implementation.
"""
import os
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
@@ -33,62 +30,18 @@ log = logging.getLogger(__name__)
class Builtin(Module):
"""
Built-in module.
"""
def __init__(self):
Module.__init__(self)
super().__init__()
self._nodes = []
self._servers = []
def setProjectFilesDir(self, path):
"""
Sets the project files directory path this module.
:param path: path to the local project files directory
"""
pass # not used by this module
def setImageFilesDir(self, path):
"""
Sets the image files directory path this module.
:param path: path to the local image files directory
"""
pass # not used by this module
def addServer(self, server):
"""
Adds a server to be used by this module.
:param server: WebSocketClient instance
"""
log.info("adding server {}:{} to built-in module".format(server.host, server.port))
self._servers.append(server)
def removeServer(self, server):
"""
Removes a server from being used by this module.
:param server: WebSocketClient instance
"""
log.info("removing server {}:{} from built-in module".format(server.host, server.port))
self._servers.remove(server)
def servers(self):
"""
Returns all the servers used by this module.
:returns: list of WebSocketClient instances
"""
return self._servers
def configChangedSlot(self):
pass
def addNode(self, node):
"""
@@ -109,80 +62,27 @@ class Builtin(Module):
if node in self._nodes:
self._nodes.remove(node)
def allocateServer(self, node_class):
def reset(self):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (WebSocketClient instance)
Resets the module.
"""
# 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"])
log.info("Built-in module reset")
self._nodes.clear()
# 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 not True 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):
def createNode(self, node_class, server, project):
"""
Creates a new node.
:param node_class: Node object
:param server: WebSocketClient instance
:param server: HTTPClient instance
:param project: Project instance
"""
log.info("creating node {}".format(node_class))
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
server.reconnect()
except OSError as e:
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
if server not in self._servers:
self.addServer(server)
# create an instance of the node class
return node_class(self, server)
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""
@@ -195,13 +95,6 @@ class Builtin(Module):
log.info("configuring node {}".format(node))
node.setup()
def reset(self):
"""
Resets the servers.
"""
self._servers.clear()
@staticmethod
def findAlternativeInterface(node, missing_interface):
@@ -213,13 +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
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
@@ -256,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
@@ -25,6 +24,7 @@ from gns3.node import Node
from gns3.ports.port import Port
from gns3.nios.nio_generic_ethernet import NIOGenericEthernet
from gns3.nios.nio_linux_ethernet import NIOLinuxEthernet
from gns3.nios.nio_nat import NIONAT
from gns3.nios.nio_udp import NIOUDP
from gns3.nios.nio_tap import NIOTAP
from gns3.nios.nio_unix import NIOUNIX
@@ -36,17 +36,20 @@ log = logging.getLogger(__name__)
class Cloud(Node):
"""
Dynamips cloud.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
_name_instance_count = 1
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
super().__init__(module, server, project)
log.info("cloud is being created")
# create an unique id and name
@@ -55,13 +58,10 @@ class Cloud(Node):
name = "Cloud {}".format(self._name_id)
self.setStatus(Node.started) # this is an always-on node
self._defaults = {}
self._ports = []
self._module = module
self._initial_settings = None
self._settings = {"nios": [],
self._settings = {"name": name,
"interfaces": {},
"name": name}
"nios": []}
def delete(self):
"""
@@ -72,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.
@@ -82,11 +82,12 @@ class Cloud(Node):
if name:
self._settings["name"] = name
if initial_settings:
self._initial_settings = initial_settings
self._server.send_message("builtin.interfaces", None, self._setupCallback)
if additional_settings and "nios" in additional_settings:
self._settings["nios"] = additional_settings["nios"]
def _setupCallback(self, result, error=False):
self._server.get("/interfaces", self._setupCallback)
def _setupCallback(self, result, error=False, **kwargs):
"""
Callback for setup.
@@ -101,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):
"""
@@ -150,6 +154,19 @@ class Cloud(Node):
return NIOLinuxEthernet(linux_device)
return None
def _createNIONAT(self, nio):
"""
Creates a NIO NAT.
:param nio: nio string
"""
match = re.search(r"""^nio_nat:(.+)$""", nio)
if match:
identifier = match.group(1)
return NIONAT(identifier)
return None
def _createNIOTAP(self, nio):
"""
Creates a NIO TAP.
@@ -204,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.
@@ -211,53 +278,28 @@ class Cloud(Node):
:param new_settings: settings dictionary
"""
nios = new_settings["nios"]
updated = False
# 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_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 == 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)
if "nios" in new_settings:
nios = new_settings["nios"]
self._addPorts(nios, ignore_existing_nio=True)
updated = True
log.debug("port {} has been added".format(nio))
# delete ports
for nio in self._settings["nios"]:
if nio not in nios:
for port in self._ports.copy():
if port.name() == nio:
self._ports.remove(port)
updated = True
log.debug("port {} has been deleted".format(nio))
break
# delete ports
for nio in self._settings["nios"]:
if nio not in nios:
for port in self._ports.copy():
if port.name() == nio:
self._ports.remove(port)
updated = True
log.debug("port {} has been deleted".format(nio))
break
self._settings["nios"] = new_settings["nios"].copy()
if "name" in new_settings and new_settings["name"] != self.name():
self._settings["name"] = new_settings["name"]
updated = True
self._settings["nios"] = new_settings["nios"].copy()
if updated:
log.info("cloud {} has been updated".format(self.name()))
self.updated_signal.emit()
@@ -308,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:
@@ -327,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():
@@ -357,15 +401,22 @@ This is a pseudo-device for external connections
break
if not available_interface:
alternative_interface = self._module.findAlternativeInterface(self, topology_port_name)
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)
port.setName(topology_port["name"])
self._settings["nios"].append(topology_port["name"])
if alternative_interface:
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):
"""
@@ -396,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
"""
@@ -412,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

@@ -24,27 +24,44 @@ log = logging.getLogger(__name__)
class Host(Cloud):
"""
Pseudo host based on a Dynamips Cloud.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
_name_instance_count = 1
def __init__(self, module, server):
Cloud.__init__(self, module, server)
def __init__(self, module, server, project):
super().__init__(module, server, project)
log.info("host is being created")
# create an unique id and name
self._name_id = Host._name_instance_count
Host._name_instance_count += 1
name = "Host {}".format(self._name_id)
name = "Host{}".format(self._name_id)
self._settings["name"] = name
self.created_signal.connect(self._autoConfigure)
def setup(self, name=None, additional_settings={}):
"""
Setups this host.
:param name: optional name for this host
"""
if name:
self._settings["name"] = name
if additional_settings and "nios" in additional_settings:
self._settings["nios"] = additional_settings["nios"]
else:
self.created_signal.connect(self._autoConfigure)
self._server.get("/interfaces", self._setupCallback)
def _autoConfigure(self, node_id):
"""
@@ -59,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
@@ -70,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,18 +20,19 @@ Configuration page for clouds.
"""
import re
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtGui, 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.
"""
def __init__(self):
QtGui.QWidget.__init__(self)
super().__init__()
self.setupUi(self)
self._nios = []
@@ -47,6 +48,12 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiAddLinuxEthernetPushButton.clicked.connect(self._linuxEthernetAddSlot)
self.uiDeleteLinuxEthernetPushButton.clicked.connect(self._linuxEthernetDeleteSlot)
# connect NIO NAT slots
self.uiNIONATListWidget.currentRowChanged.connect(self._NIONATSelectedSlot)
self.uiNIONATListWidget.itemSelectionChanged.connect(self._NIONATChangedSlot)
self.uiAddNIONATPushButton.clicked.connect(self._NIONATAddSlot)
self.uiDeleteNIONATPushButton.clicked.connect(self._NIONATDeleteSlot)
# connect NIO UDP slots
self.uiNIOUDPListWidget.currentRowChanged.connect(self._NIOUDPSelectedSlot)
self.uiNIOUDPListWidget.itemSelectionChanged.connect(self._NIOUDPChangedSlot)
@@ -105,7 +112,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiGenericEthernetLineEdit.text()
if interface:
nio = "nio_gen_eth:{interface}".format(interface=interface)
if not nio in self._nios:
if nio not in self._nios:
self.uiGenericEthernetListWidget.addItem(nio)
self._nios.append(nio)
@@ -121,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())
@@ -154,7 +161,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiLinuxEthernetLineEdit.text()
if interface:
nio = "nio_gen_linux:{interface}".format(interface=interface)
if not nio in self._nios:
if nio not in self._nios:
self.uiLinuxEthernetListWidget.addItem(nio)
self._nios.append(nio)
@@ -170,14 +177,70 @@ 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())
def _NIONATSelectedSlot(self, index):
"""
Loads a selected NAT NIO.
:param index: ignored
"""
item = self.uiNIONATListWidget.currentItem()
if item:
nio = item.text()
match = re.search(r"""^nio_nat:(.+)$""", nio)
if match:
self.uiNIONATIdentiferLineEdit.setText(match.group(1))
def _NIONATChangedSlot(self):
"""
Enables the use of the delete button.
"""
item = self.uiNIONATListWidget.currentItem()
if item:
self.uiDeleteNIONATPushButton.setEnabled(True)
else:
self.uiDeleteNIONATPushButton.setEnabled(False)
def _NIONATAddSlot(self):
"""
Adds a new NAT NIO.
"""
identifier = self.uiNIONATIdentiferLineEdit.text()
if identifier:
nio = "nio_nat:{}".format(identifier)
if nio not in self._nios:
self.uiNIONATListWidget.addItem(nio)
self._nios.append(nio)
def _NIONATDeleteSlot(self):
"""
Deletes a NAT NIO.
"""
item = self.uiNIONATListWidget.currentItem()
if item:
nio = item.text()
# check we can delete that NIO
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
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())
def _NIOUDPSelectedSlot(self, index):
"""
Loads a selected UDP.
:param index: ignored
"""
item = self.uiNIOUDPListWidget.currentItem()
@@ -212,7 +275,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
nio = "nio_udp:{lport}:{rhost}:{rport}".format(lport=local_port,
rhost=remote_host,
rport=remote_port)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOUDPListWidget.addItem(nio)
self._nios.append(nio)
self.uiLocalPortSpinBox.setValue(local_port + 1)
@@ -230,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())
@@ -268,7 +331,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
tap_interface = self.uiNIOTAPLineEdit.text()
if tap_interface:
nio = "nio_tap:{}".format(tap_interface.lower())
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOTAPListWidget.addItem(nio)
self._nios.append(nio)
@@ -284,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())
@@ -325,7 +388,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
if local_file and remote_file:
nio = "nio_unix:{local}:{remote}".format(local=local_file,
remote=remote_file)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOUNIXListWidget.addItem(nio)
self._nios.append(nio)
@@ -341,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())
@@ -381,7 +444,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
local_file = self.uiVDELocalFileLineEdit.text()
if local_file and control_file:
nio = "nio_vde:{control}:{local}".format(control=control_file, local=local_file)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOVDEListWidget.addItem(nio)
self._nios.append(nio)
@@ -397,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())
@@ -405,6 +468,8 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
def _NIONullSelectedSlot(self, index):
"""
Loads a selected NULL NIO.
:param index: ignored
"""
item = self.uiNIONullListWidget.currentItem()
@@ -433,7 +498,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
identifier = self.uiNIONullIdentiferLineEdit.text()
if identifier:
nio = "nio_null:{}".format(identifier)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIONullListWidget.addItem(nio)
self._nios.append(nio)
@@ -449,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())
@@ -480,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()
@@ -490,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

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>500</height>
<width>653</width>
<height>478</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,13 +15,13 @@
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="QTabWidget" name="uiNIOsTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<widget class="QWidget" name="NIOEthernetTab">
<attribute name="title">
<string>NIO Ethernet</string>
<string>Ethernet</string>
</attribute>
<layout class="QVBoxLayout">
<item>
@@ -135,9 +135,104 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<widget class="QWidget" name="NIONATTab">
<attribute name="title">
<string>NIO UDP</string>
<string>NAT</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="uiNIONATSettingsGroupBox">
<property name="title">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="_2">
<item row="0" column="0">
<widget class="QLabel" name="uiNIONATIdentifierLabel">
<property name="text">
<string>Local identifier:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="uiNIONATIdentiferLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2" rowspan="3">
<widget class="QGroupBox" name="uiNIONATListGroupBox">
<property name="title">
<string>NIOs</string>
</property>
<layout class="QVBoxLayout" name="_3">
<item>
<widget class="QListWidget" name="uiNIONATListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="uiAddNIONATPushButton">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiDeleteNIONATPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
<item row="2" column="0" rowspan="2">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>294</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2">
<spacer name="spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>194</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="NIOUDPTab">
<attribute name="title">
<string>UDP</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -265,9 +360,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<widget class="QWidget" name="NIOTAPTab">
<attribute name="title">
<string>NIO TAP</string>
<string>TAP</string>
</attribute>
<layout class="QVBoxLayout">
<item>
@@ -317,9 +412,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<widget class="QWidget" name="NIOUnixTab">
<attribute name="title">
<string>NIO UNIX</string>
<string>UNIX</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -440,9 +535,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<widget class="QWidget" name="NIOVDETab">
<attribute name="title">
<string>NIO VDE</string>
<string>VDE</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -563,9 +658,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_6">
<widget class="QWidget" name="NIONullTab">
<attribute name="title">
<string>NIO NULL</string>
<string>NULL</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -575,9 +670,9 @@
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="uiNIONullIdentifierLabel">
<property name="text">
<string>Identifier:</string>
<string>Local identifier:</string>
</property>
</widget>
</item>
@@ -658,7 +753,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_7">
<widget class="QWidget" name="MiscTab">
<attribute name="title">
<string>Misc.</string>
</attribute>

View File

@@ -1,421 +1,458 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/modules/dynamips/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: Mon Mar 17 17:42:16 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_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName(_fromUtf8("cloudConfigPageWidget"))
cloudConfigPageWidget.resize(542, 500)
self.vboxlayout = QtGui.QVBoxLayout(cloudConfigPageWidget)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.tabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
self.tabWidget.setObjectName(_fromUtf8("tabWidget"))
self.tab = QtGui.QWidget()
self.tab.setObjectName(_fromUtf8("tab"))
self.vboxlayout1 = QtGui.QVBoxLayout(self.tab)
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.tab)
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)
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
cloudConfigPageWidget.resize(653, 478)
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.tab)
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.tabWidget.addTab(self.tab, _fromUtf8(""))
self.tab_2 = QtGui.QWidget()
self.tab_2.setObjectName(_fromUtf8("tab_2"))
self.gridlayout2 = QtGui.QGridLayout(self.tab_2)
self.gridlayout2.setObjectName(_fromUtf8("gridlayout2"))
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.tab_2)
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.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 = 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("uiNIONATIdentiferLineEdit")
self._2.addWidget(self.uiNIONATIdentiferLineEdit, 1, 0, 1, 1)
self.gridLayout_2.addWidget(self.uiNIONATSettingsGroupBox, 0, 0, 1, 2)
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("uiNIONATListWidget")
self._3.addWidget(self.uiNIONATListWidget)
self.gridLayout_2.addWidget(self.uiNIONATListGroupBox, 0, 2, 3, 1)
self.uiAddNIONATPushButton = QtWidgets.QPushButton(self.NIONATTab)
self.uiAddNIONATPushButton.setObjectName("uiAddNIONATPushButton")
self.gridLayout_2.addWidget(self.uiAddNIONATPushButton, 1, 0, 1, 1)
self.uiDeleteNIONATPushButton = QtWidgets.QPushButton(self.NIONATTab)
self.uiDeleteNIONATPushButton.setEnabled(False)
self.uiDeleteNIONATPushButton.setObjectName("uiDeleteNIONATPushButton")
self.gridLayout_2.addWidget(self.uiDeleteNIONATPushButton, 1, 1, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 294, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem1, 2, 0, 2, 1)
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, "")
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.tab_2)
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.tab_2)
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.tab_2)
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)
spacerItem1 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout2.addItem(spacerItem1, 2, 1, 1, 1)
self.tabWidget.addTab(self.tab_2, _fromUtf8(""))
self.tab_3 = QtGui.QWidget()
self.tab_3.setObjectName(_fromUtf8("tab_3"))
self.vboxlayout3 = QtGui.QVBoxLayout(self.tab_3)
self.vboxlayout3.setObjectName(_fromUtf8("vboxlayout3"))
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.tab_3)
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"))
spacerItem3 = QtWidgets.QSpacerItem(20, 211, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout2.addItem(spacerItem3, 2, 1, 1, 1)
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)
spacerItem2 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.vboxlayout3.addItem(spacerItem2)
self.tabWidget.addTab(self.tab_3, _fromUtf8(""))
self.tab_4 = QtGui.QWidget()
self.tab_4.setObjectName(_fromUtf8("tab_4"))
self.gridlayout5 = QtGui.QGridLayout(self.tab_4)
self.gridlayout5.setObjectName(_fromUtf8("gridlayout5"))
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.tab_4)
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"))
spacerItem4 = QtWidgets.QSpacerItem(20, 191, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.vboxlayout3.addItem(spacerItem4)
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.tab_4)
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.tab_4)
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.tab_4)
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)
spacerItem3 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout5.addItem(spacerItem3, 2, 0, 2, 2)
spacerItem4 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout5.addItem(spacerItem4, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_4, _fromUtf8(""))
self.tab_5 = QtGui.QWidget()
self.tab_5.setObjectName(_fromUtf8("tab_5"))
self.gridlayout9 = QtGui.QGridLayout(self.tab_5)
self.gridlayout9.setObjectName(_fromUtf8("gridlayout9"))
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.tab_5)
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"))
spacerItem5 = QtWidgets.QSpacerItem(160, 190, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
self.gridlayout5.addItem(spacerItem5, 2, 0, 2, 2)
spacerItem6 = QtWidgets.QSpacerItem(196, 132, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout5.addItem(spacerItem6, 3, 2, 1, 1)
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.tab_5)
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.tab_5)
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.tab_5)
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)
spacerItem5 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout9.addItem(spacerItem5, 2, 0, 2, 2)
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout9.addItem(spacerItem6, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_5, _fromUtf8(""))
self.tab_6 = QtGui.QWidget()
self.tab_6.setObjectName(_fromUtf8("tab_6"))
self.gridlayout13 = QtGui.QGridLayout(self.tab_6)
self.gridlayout13.setObjectName(_fromUtf8("gridlayout13"))
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.tab_6)
self.uiNIONullSettingsGroupBox.setObjectName(_fromUtf8("uiNIONullSettingsGroupBox"))
self.gridlayout14 = QtGui.QGridLayout(self.uiNIONullSettingsGroupBox)
self.gridlayout14.setObjectName(_fromUtf8("gridlayout14"))
self.label_9 = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
self.label_9.setObjectName(_fromUtf8("label_9"))
self.gridlayout14.addWidget(self.label_9, 0, 0, 1, 1)
self.uiNIONullIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONullSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
spacerItem7 = QtWidgets.QSpacerItem(161, 201, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
self.gridlayout9.addItem(spacerItem7, 2, 0, 2, 2)
spacerItem8 = QtWidgets.QSpacerItem(196, 132, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout9.addItem(spacerItem8, 3, 2, 1, 1)
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 = 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.tab_6)
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.tab_6)
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.tab_6)
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)
spacerItem7 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem7, 2, 0, 2, 2)
spacerItem8 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem8, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_6, _fromUtf8(""))
self.tab_7 = QtGui.QWidget()
self.tab_7.setObjectName(_fromUtf8("tab_7"))
self.gridLayout = QtGui.QGridLayout(self.tab_7)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiNameLabel = QtGui.QLabel(self.tab_7)
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
spacerItem9 = QtWidgets.QSpacerItem(20, 261, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem9, 2, 0, 2, 2)
spacerItem10 = QtWidgets.QSpacerItem(20, 181, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem10, 3, 2, 1, 1)
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.tab_7)
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
self.uiNameLineEdit = QtWidgets.QLineEdit(self.MiscTab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
spacerItem9 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem9, 1, 1, 1, 1)
self.tabWidget.addTab(self.tab_7, _fromUtf8(""))
self.vboxlayout.addWidget(self.tabWidget)
spacerItem11 = QtWidgets.QSpacerItem(20, 399, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem11, 1, 1, 1, 1)
self.uiNIOsTabWidget.addTab(self.MiscTab, "")
self.vboxlayout.addWidget(self.uiNIOsTabWidget)
self.retranslateUi(cloudConfigPageWidget)
self.tabWidget.setCurrentIndex(0)
self.uiNIOsTabWidget.setCurrentIndex(0)
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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("cloudConfigPageWidget", "NIO Ethernet", 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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("cloudConfigPageWidget", "NIO 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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("cloudConfigPageWidget", "NIO 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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("cloudConfigPageWidget", "NIO 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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), _translate("cloudConfigPageWidget", "NIO VDE", None))
self.uiNIONullSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.label_9.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.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), _translate("cloudConfigPageWidget", "NIO NULL", None))
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_7), _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

@@ -0,0 +1,255 @@
# -*- 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/>.
"""Docker module implementation."""
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
from ..module import Module
from ..module_error import ModuleError
from .docker_vm import DockerVM
from .settings import DOCKER_SETTINGS, DOCKER_CONTAINER_SETTINGS
import logging
log = logging.getLogger(__name__)
class Docker(Module):
"""Docker module."""
def __init__(self):
super().__init__()
self._settings = {}
self._docker_images = {}
self._nodes = []
# load the settings
self._loadSettings()
self._loadDockerImages()
def configChangedSlot(self):
# load the settings
self._loadSettings()
def _loadSettings(self):
"""Loads the settings from the persistent settings file."""
local_config = LocalConfig.instance()
self._settings = local_config.loadSectionSettings(
self.__class__.__name__, DOCKER_SETTINGS)
def _saveSettings(self):
"""Saves the settings to the persistent settings file."""
LocalConfig.instance().saveSectionSettings(
self.__class__.__name__, self._settings)
def _loadDockerImages(self):
"""Load the Docker images from the persistent settings file."""
local_config = LocalConfig.instance()
settings = local_config.settings()
if "images" in settings.get(self.__class__.__name__, {}):
for image in settings[self.__class__.__name__]["images"]:
name = image.get("name")
server = image.get("server")
key = "{server}:{name}".format(server=server, name=name)
if key in self._docker_images or not name or not server:
continue
container_settings = DOCKER_CONTAINER_SETTINGS.copy()
container_settings.update(image)
self._docker_images[key] = container_settings
def _saveDockerImages(self):
"""Saves the Docker images to the persistent settings file."""
self._settings["images"] = list(self._docker_images.values())
self._saveSettings()
def dockerImages(self):
"""
Returns Docker images settings.
:returns: Docker images settings
:rtype: dict
"""
return self._docker_images
def setDockerImages(self, new_docker_images):
"""Sets Docker image settings.
:param new_iou_images: Docker images settings (dictionary)
"""
self._docker_images = new_docker_images.copy()
self._saveDockerImages()
def addNode(self, node):
"""Adds a node to this module.
:param node: Node instance
"""
self._nodes.append(node)
def removeNode(self, node):
"""Removes a node from this module.
:param node: Node instance
"""
if node in self._nodes:
self._nodes.remove(node)
def settings(self):
"""
Returns the module settings
:returns: module settings (dictionary)
"""
return self._settings
def setSettings(self, settings):
"""Sets the module settings
:param settings: module settings (dictionary)
"""
self._settings.update(settings)
self._saveSettings()
def createNode(self, node_class, server, project):
"""Creates a new node.
:param node_class: Node object
:param server: HTTPClient instance
"""
log.info("creating node {}".format(node_class))
# create an instance of the node class
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""Sets up a node.
:param node: Node instance
:param node_name: Node name
"""
log.info("configuring node {} with id {}".format(node, node.id()))
image = None
if node_name:
for image_key, info in self._docker_images.items():
if node_name == info["imagename"]:
image = image_key
if not image:
selected_images = []
for image, info in self._docker_images.items():
if info["server"] == node.server().host() or (
node.server().isLocal() and info["server"] == "local"):
selected_images.append(image)
if not selected_images:
raise ModuleError("No Docker VM on server {}".format(
node.server().url()))
elif len(selected_images) > 1:
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
(selection, ok) = QtWidgets.QInputDialog.getItem(
mainwindow, "Docker Image", "Please choose an image",
selected_images, 0, False)
if ok:
image = selection
else:
raise ModuleError("Please select a Docker Image")
else:
image = selected_images[0]
image_settings = {}
for setting_name, value in self._docker_images[image].items():
if setting_name in node.settings() and value != "" and value is not None:
image_settings[setting_name] = value
imagename = self._docker_images[image]["imagename"]
node.setup(imagename, additional_settings=image_settings)
def reset(self):
"""Resets the servers."""
log.info("Docker module reset")
self._nodes.clear()
def getDockerImagesFromServer(self, server, callback):
"""Gets the Docker images list from a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
"""
server.get("/docker/images", callback)
@staticmethod
def getNodeClass(name):
"""
Returns the object with the corresponding name.
:param name: object name
"""
if name in globals():
return globals()[name]
@staticmethod
def classes():
"""Returns all the node classes supported by this module.
:returns: list of classes
"""
return [DockerVM]
def nodes(self):
"""
Returns all the node data necessary to represent a node
in the nodes view and create a node on the scene.
"""
nodes = []
for docker_image in self._docker_images.values():
nodes.append({
"class": DockerVM.__name__,
"name": docker_image["imagename"],
"server": docker_image["server"],
"symbol": docker_image["symbol"],
"categories": [docker_image["category"]]
})
return nodes
@staticmethod
def preferencePages():
"""
:returns: QWidget object list
"""
from .pages.docker_preferences_page import DockerPreferencesPage
from .pages.docker_vm_preferences_page import DockerVMPreferencesPage
from gns3.local_config import LocalConfig
if not LocalConfig.instance().experimental():
return []
return [DockerPreferencesPage, DockerVMPreferencesPage]
@staticmethod
def instance():
"""Singleton to return only one instance of Docker module.
:returns: instance of Docker"""
if not hasattr(Docker, "_instance"):
Docker._instance = Docker()
return Docker._instance

View File

@@ -0,0 +1,113 @@
# -*- 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/>.
"""Wizard for Docker images."""
from gns3.qt import QtGui, QtWidgets
from gns3.dialogs.vm_wizard import VMWizard
from ..ui.docker_vm_wizard_ui import Ui_DockerVMWizard
from .. import Docker
class DockerVMWizard(VMWizard, Ui_DockerVMWizard):
"""Wizard to create a Docker image.
:param docker_images: existing Docker images
:param parent: parent widget
"""
def __init__(self, docker_images, parent):
super().__init__(parent=parent, devices=[], use_local_server=True)
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(
":/icons/docker.png"))
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
self._docker_images = docker_images
if Docker.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
def initializePage(self, page_id):
super().initializePage(page_id)
if self.page(page_id) == self.uiImageWizardPage:
self._server.get(
"/docker/images", self._getDockerImagesFromServerCallback)
def _getDockerImagesFromServerCallback(
self, result, error=False, **kwargs):
"""Callback for getDockerImagesFromServer.
:param progress_dialog: QProgressDialog instance
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(
self, "Docker Images", "{}".format(result["message"]))
else:
self.uiImageListComboBox.clear()
existing_images = []
for existing_image in self._docker_images.values():
existing_images.append(existing_image["imagename"])
for image in result:
if image["imagename"] not in existing_images:
self.uiImageListComboBox.addItem(image["imagename"], image)
def validateCurrentPage(self):
"""Validates the server."""
if super().validateCurrentPage() is False:
return False
if self.currentPage() == self.uiImageWizardPage:
if not self.uiImageListComboBox.count():
QtWidgets.QMessageBox.critical(
self, "Docker images",
"There are no Docker images available!")
return False
return True
def getSettings(self):
"""Returns the settings set in this Wizard.
:return: settings
:rtype: dict
"""
if self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiRemoteRadioButton.isChecked():
server = self.uiRemoteServersComboBox.currentText()
elif self.uiVMRadioButton.isChecked():
server = "vm"
index = self.uiImageListComboBox.currentIndex()
imagename = self.uiImageListComboBox.itemText(index)
# FIXME: add some more configuration options for images
imageinfo = self.uiImageListComboBox.itemData(index)
settings = {
"imagename": imagename,
"server": server,
}
return settings

View File

@@ -0,0 +1,580 @@
# -*- 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/>.
"""
Docker VM implementation.
"""
from gns3.vm import VM
from gns3.node import Node
from gns3.ports.port import Port
from gns3.ports.ethernet_port import EthernetPort
from gns3.nios.nio_generic_ethernet import NIOGenericEthernet
from gns3.nios.nio_linux_ethernet import NIOLinuxEthernet
from gns3.nios.nio_udp import NIOUDP
from .settings import DOCKER_CONTAINER_SETTINGS
import logging
import re
log = logging.getLogger(__name__)
class DockerVM(VM):
"""
Docker Image.
:param module: parent module for this node
:param server: GNS3 server instance
"""
URL_PREFIX = "docker"
def __init__(self, module, server, project):
super().__init__(module, server, project)
log.info("Docker image instance is being created")
self._settings = {
"name": "",
"imagename": "",
"console": DOCKER_CONTAINER_SETTINGS["console"],
"adapters": DOCKER_CONTAINER_SETTINGS["adapters"],
"adapter_type": DOCKER_CONTAINER_SETTINGS["adapter_type"],
"startcmd": DOCKER_CONTAINER_SETTINGS["startcmd"]
}
def _addAdapters(self, adapters):
"""Adds adapters.
:param adapters: number of adapters
"""
for adapter_number in range(0, adapters):
adapter_name = EthernetPort.longNameType() + str(adapter_number)
short_name = EthernetPort.shortNameType() + str(adapter_number)
new_port = EthernetPort(adapter_name)
new_port.setShortName(short_name)
new_port.setAdapterNumber(adapter_number)
new_port.setPortNumber(0)
self._ports.append(new_port)
log.debug("Adapter {} has been added".format(adapter_name))
def setup(self, imagename, name=None, vm_id=None, additional_settings={}):
"""Sets up this Docker container.
:param imagename: image name
:param name: optional name
:param additional_settings: additional settings for this VM
"""
# let's create a unique name if none has been chosen
if not name:
name = imagename.replace(":", "-").replace("/", "-")
name = self.allocateName(name + "-")
self.setName(name)
if not name:
self.error_signal.emit(
self.id(), "could not allocate a name for this container")
return
self._settings["name"] = name
params = {
"name": name,
"imagename": imagename
}
if vm_id:
params["id"] = vm_id
params.update(additional_settings)
self.httpPost("/docker/images", self._setupCallback, body=params)
def _setupCallback(self, result, error=False, **kwargs):
"""Callback for Docker container setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if not super()._setupCallback(result, error=error, **kwargs):
return
self._addAdapters(self._settings.get("adapters", 0))
if self._loading:
self.loaded_signal.emit()
else:
self.setInitialized(True)
log.info(
"Docker container {} has been created".format(self.name()))
self.created_signal.emit(self.id())
self._module.addNode(self)
def start(self):
"""Starts this Docker container."""
log.debug("{} is starting".format(self.name()))
self.httpPost("/docker/images/{id}/start".format(
id=self._vm_id), self._startCallback)
def _startCallback(self, result, error=False, **kwargs):
"""Callback for Docker container start.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error(
"error while starting {}: {}".format(
self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
log.info("{} has started".format(self.name()))
self.setStatus(Node.started)
def stop(self):
"""Stops this Docker container."""
if self.status() == Node.stopped:
log.debug("{} is already stopped".format(self.name()))
return
log.debug("{} is stopping".format(self.name()))
self.httpPost("/docker/images/{id}/stop".format(
id=self._vm_id), self._stopCallback)
def _stopCallback(self, result, error=False, **kwargs):
"""Callback for Docker container stop.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping {}: {}".format(
self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
log.info("{} has stopped".format(self.name()))
self.setStatus(Node.stopped)
def delete(self):
"""Deletes this Docker container."""
log.debug("Docker container {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._id:
self.httpDelete("/docker/images/{id}".format(
id=self._vm_id), self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False, **kwargs):
"""Callback for Docker container deletion.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(
self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
updated = False
nb_adapters_changed = False
for name, value in result.items():
if name in self._settings and self._settings[name] != value:
log.info("{}: updating {} from '{}' to '{}'".format(self.name(), name, self._settings[name], value))
updated = True
if name == "name":
# update the node name
self.updateAllocatedName(value)
if name == "adapters":
nb_adapters_changed = True
self._settings[name] = value
if nb_adapters_changed:
log.debug("number of adapters has changed to {}".format(self._settings["adapters"]))
# TODO: dynamically add/remove adapters
self._ports.clear()
self._addAdapters(self._settings["adapters"])
if updated:
log.info("Docker VM {} has been updated".format(self.name()))
self.updated_signal.emit()
def update(self, new_settings):
"""
Updates the settings for this container.
:param new_settings: settings dictionary
"""
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)
print("nio_object")
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))
# delete ports
for nio in self._settings["nios"]:
if nio not in nios:
for port in self._ports.copy():
if port.name() == nio:
self._ports.remove(port)
updated = True
log.debug("port {} has been deleted".format(nio))
break
self._settings["nios"] = new_settings["nios"].copy()
if "name" in new_settings and new_settings["name"] != self.name():
self._settings["name"] = new_settings["name"]
updated = True
if updated:
log.info("cloud {} has been updated".format(self.name()))
self.updated_signal.emit()
def suspend(self):
"""Suspends this Docker container."""
if self.status() == Node.suspended:
log.debug("{} is already suspended".format(self.name()))
return
log.debug("{} is being suspended".format(self.name()))
self.httpPost("/docker/images/{id}/suspend".format(
id=self._vm_id), self._suspendCallback)
def _suspendCallback(self, result, error=False, **kwargs):
"""Callback for container suspend.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while suspending {}: {}".format(
self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
log.info("{} has suspended".format(self.name()))
self.setStatus(Node.suspended)
for port in self._ports:
# set ports as suspended
port.setStatus(Port.suspended)
self.suspended_signal.emit()
def reload(self):
"""Reloads this Docker container."""
log.debug("{} is being reloaded".format(self.name()))
self.httpPost("/docker/images/{id}/reload".format(
id=self._vm_id), self._reloadCallback)
def _reloadCallback(self, result, error=False, **kwargs):
"""Callback for Docker container reload.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while reloading {}: {}".format(
self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
log.info("{} has reloaded".format(self.name()))
def dump(self):
"""
Returns a representation of this Docker VM instance.
(to be saved in a topology file).
:returns: representation of the node (dictionary)
"""
container = {
"id": self.id(),
"vm_id": self._vm_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {},
"server_id": self._server.id()
}
# add the properties
for name, value in self._settings.items():
if value is not None and value != "":
container["properties"][name] = value
# add the ports
if self._ports:
ports = container["ports"] = []
for port in self._ports:
ports.append(port.dump())
return container
def info(self):
"""Returns information about this Docker container.
:returns: formated string
:rtype: string
"""
if self.status() == Node.started:
state = "started"
else:
state = "stopped"
info = """Docker container {name} is {state}
Node ID is {id}, server's Docker container ID is {vm_id}
""".format(name=self.name(),
id=self.id(),
vm_id=self._vm_id,
state=state
)
port_info = ""
for port in self._ports:
if port.isFree():
port_info += " {port_name} is empty\n".format(
port_name=port.name())
else:
port_info += " {port_name} {port_description}\n".format(
port_name=port.name(),
port_description=port.description())
return info + port_info
def load(self, node_info):
"""
Loads a cloud representation
(from a topology file).
:param node_info: representation of the node (dictionary)
"""
settings = node_info["properties"]
name = settings.pop("name")
self.updated_signal.connect(self._updatePortSettings)
log.info("Docker container {} is loading".format(name))
self._node_info = node_info
self.setup(name, settings)
def _updatePortSettings(self):
"""
Updates port settings when loading a topology.
"""
self.updated_signal.disconnect(self._updatePortSettings)
# update the port with the correct 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["name"] == port.name():
port.setId(topology_port["id"])
if topology_port["name"].startswith("nio_gen_eth") or topology_port["name"].startswith("nio_linux_eth"):
# lookup if the interface exists
available_interface = False
topology_port_name = topology_port["name"].split(':', 1)[1]
for interface in self._settings["interfaces"]:
if interface["name"] == topology_port_name:
available_interface = True
break
if not available_interface:
alternative_interface = self._module.findAlternativeInterface(self, topology_port_name)
if alternative_interface:
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)
port.setName(topology_port["name"])
self._settings["nios"].append(topology_port["name"])
log.info("cloud {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
def name(self):
"""
Returns the name of this Docker container.
:returns: name (string)
"""
return self._settings["name"]
def settings(self):
"""
Returns all settings of this Docker container.
:returns: settings
:rtype: dict
"""
return self._settings
def ports(self):
"""
Returns all the ports for this Docker VM instance.
:returns: list of Port instances
"""
return self._ports
def shellExecCmd(self):
"""Returns the execution command.
:returns: execution cmd for shell console
:rtype: string
"""
return "docker exec -it {} bash".format(self._settings['name'])
def console(self):
"""
Returns the console port for this Docker VM instance.
:returns: port (integer)
"""
return self._settings["console"]
def configPage(self):
"""Returns the configuration page widget to be used by the node configurator.
:returns: QWidget object
"""
from .pages.docker_vm_configuration_page import DockerVMConfigurationPage
return DockerVMConfigurationPage
@staticmethod
def defaultSymbol():
"""Returns the default symbol path for this node.
:returns: symbol path (or resource).
"""
return ":/symbols/docker_guest.normal.svg"
@staticmethod
def hoverSymbol():
"""Returns the symbol to use when this node is hovered.
:returns: symbol path (or resource).
"""
return ":/symbols/docker_guest.selected.svg"
@staticmethod
def symbolName():
return "Docker image"
@staticmethod
def categories():
"""
Returns the node categories the node is part of (used by the device panel).
:returns: list of node category (integer)
"""
return [Node.end_devices]
def __str__(self):
return "Docker image"
def addNIO(self, port, nio):
"""Adds a new NIO on the specified port for this container instance.
:param port: Port instance
:param nio: NIO instance
"""
params = self.getNIOInfo(nio)
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self.httpPost(
"/{prefix}/images/{id}/adapters/{adapter}/ports/{port}/nio".format(
adapter=port.adapterNumber(),
port=port.portNumber(),
prefix=self.URL_PREFIX,
id=self._vm_id
),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this instance
:param port: Port instance
"""
log.debug("{} is deleting an NIO".format(self.name()))
self.httpDelete(
"/{prefix}/images/{vm_id}/adapters/{adapter}/ports/{port}/nio".format(
adapter=port.adapterNumber(),
prefix=self.URL_PREFIX,
port=port.portNumber(),
vm_id=self._vm_id),
self._deleteNIOCallback)
def _createNIOGenericEthernet(self, nio):
"""Creates a NIO Generic Ethernet.
:param nio: nio string
"""
match = re.search(r"""^nio_gen_eth:(.+)$""", nio)
if match:
ethernet_device = match.group(1)
return NIOGenericEthernet(ethernet_device)
return None
def _createNIOLinuxEthernet(self, nio):
"""Creates a NIO Linux Ethernet.
:param nio: nio string
"""
match = re.search(r"""^nio_gen_linux:(.+)$""", nio)
if match:
linux_device = match.group(1)
return NIOLinuxEthernet(linux_device)
return None
def _createNIOUDP(self, nio):
"""
Creates a NIO UDP.
:param nio: nio string
"""
match = re.search(r"""^nio_udp:(\d+):(.+):(\d+)$""", nio)
if match:
lport = int(match.group(1))
rhost = match.group(2)
rport = int(match.group(3))
return NIOUDP(lport, rhost, rport)
return None

View File

View File

@@ -0,0 +1,65 @@
# -*- 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/>.
"""
Configuration page for Docker preferences.
"""
import sys
from gns3.qt import QtWidgets
from .. import Docker
from ..ui.docker_preferences_page_ui import Ui_DockerPreferencesPageWidget
from ..settings import DOCKER_SETTINGS
class DockerPreferencesPage(QtWidgets.QWidget, Ui_DockerPreferencesPageWidget):
"""QWidget preference page for Docker."""
def __init__(self):
super().__init__()
self.setupUi(self)
# connect signals
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
if not sys.platform.startswith("linux"):
# Docker is only supported on Linux
self.uiUseLocalServercheckBox.setEnabled(False)
def _restoreDefaultsSlot(self):
"""Slot to populate the page widgets with the default settings."""
self._populateWidgets(DOCKER_SETTINGS)
def _populateWidgets(self, settings):
"""Populates the widgets with the settings.
:param settings: Docker settings
"""
self.uiUseLocalServercheckBox.setChecked(settings["use_local_server"])
def loadPreferences(self):
"""Loads Docker preferences."""
docker_settings = Docker.instance().settings()
self._populateWidgets(docker_settings)
def savePreferences(self):
"""Saves Docker preferences."""
new_settings = {}
new_settings["use_local_server"] = self.uiUseLocalServercheckBox.isChecked()
Docker.instance().setSettings(new_settings)

View File

@@ -0,0 +1,87 @@
# -*- 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/>.
"""
Configuration page for Docker images.
"""
from gns3.qt import QtWidgets
from ..ui.docker_vm_configuration_page_ui import Ui_dockerVMConfigPageWidget
class DockerVMConfigurationPage(
QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
"""QWidget configuration page for Docker images."""
def __init__(self):
super().__init__()
self.setupUi(self)
# TODO: finish docker name change
self.uiImageListLabel.hide()
self.uiImageListComboBox.hide()
def loadSettings(self, settings, node=None, group=False):
"""
Loads the VirtualBox VM settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of images
"""
if not group:
# set the device name
if "name" in settings:
self.uiNameLineEdit.setText(settings["name"])
else:
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
if "startcmd" in settings:
self.uiCMDLineEdit.setText(settings["startcmd"])
else:
self.uiCMDLabel.hide()
self.uiCMDLineEdit.hide()
else:
self.uiNameLabel.hide()
self.uiNameLineEdit.hide()
self.uiCMDLabel.hide()
self.uiCMDLineEdit.hide()
self.uiImageListLabel.hide()
self.uiImageListComboBox.hide()
def saveSettings(self, settings, node=None, group=False):
"""Saves the Docker container settings.
:param settings: the settings (dictionary)
:param node: Node instance
:param group: indicates the settings apply to a group of VMs
"""
if not group:
if "startcmd" in settings:
startcmd = self.uiCMDLineEdit.text()
settings["startcmd"] = startcmd
if "name" in settings:
name = self.uiNameLineEdit.text()
if not name:
QtWidgets.QMessageBox.critical(self, "Name", "VMware name cannot be empty!")
else:
settings["name"] = name
else:
del settings["startcmd"]
del settings["name"]

View File

@@ -0,0 +1,230 @@
# -*- 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/>.
"""
Configuration page for Docker image preferences.
"""
import copy
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 .. import Docker
from ..settings import DOCKER_CONTAINER_SETTINGS
from ..ui.docker_vm_preferences_page_ui import Ui_DockerVMPreferencesPageWidget
from ..pages.docker_vm_configuration_page import DockerVMConfigurationPage
from ..dialogs.docker_vm_wizard import DockerVMWizard
class DockerVMPreferencesPage(
QtWidgets.QWidget, Ui_DockerVMPreferencesPageWidget):
"""QWidget preference page for Docker image preferences."""
def __init__(self):
super().__init__()
self.setupUi(self)
self._main_window = MainWindow.instance()
self._docker_images = {}
self._items = []
self.uiNewDockerVMPushButton.clicked.connect(self._dockerImageNewSlot)
self.uiEditDockerVMPushButton.clicked.connect(
self._dockerImageEditSlot)
self.uiDeleteDockerVMPushButton.clicked.connect(
self._dockerImageDeleteSlot)
self.uiDockerVMsTreeWidget.itemSelectionChanged.connect(
self._dockerImageChangedSlot)
self.uiDockerVMsTreeWidget.itemPressed.connect(
self._dockerImagePressedSlot)
def _createSectionItem(self, name):
section_item = QtWidgets.QTreeWidgetItem(self.uiDockerVMInfoTreeWidget)
section_item.setText(0, name)
font = section_item.font(0)
font.setBold(True)
section_item.setFont(0, font)
return section_item
def _refreshInfo(self, docker_image):
self.uiDockerVMInfoTreeWidget.clear()
# fill out the General section
section_item = self._createSectionItem("General")
QtWidgets.QTreeWidgetItem(
section_item, ["Image name:", docker_image["imagename"]])
# FIXME: add more configuration options
QtWidgets.QTreeWidgetItem(
section_item, ["CMD:", str(docker_image["startcmd"])])
# QtWidgets.QTreeWidgetItem(
# section_item, ["Server:", docker_image["server"]])
# # fill out the Network section
# section_item = self._createSectionItem("Network")
# QtWidgets.QTreeWidgetItem(
# section_item, ["Adapters:", str(docker_image["adapters"])])
# QtWidgets.QTreeWidgetItem(
# section_item, ["Use any adapter:", "{}".format(
# docker_image["use_any_adapter"])])
# QtWidgets.QTreeWidgetItem(
# section_item, ["Type:", docker_image["adapter_type"]])
self.uiDockerVMInfoTreeWidget.expandAll()
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(0)
self.uiDockerVMInfoTreeWidget.resizeColumnToContents(1)
def _dockerImageChangedSlot(self):
"""Loads a selected Docker image from the tree widget."""
selection = self.uiDockerVMsTreeWidget.selectedItems()
self.uiDeleteDockerVMPushButton.setEnabled(len(selection) != 0)
single_selected = len(selection) == 1
self.uiEditDockerVMPushButton.setEnabled(single_selected)
if single_selected:
key = selection[0].data(0, QtCore.Qt.UserRole)
docker_image = self._docker_images[key]
self._refreshInfo(docker_image)
else:
self.uiDockerVMInfoTreeWidget.clear()
def _dockerImageNewSlot(self):
"""Creates a new Docker image."""
wizard = DockerVMWizard(self._docker_images, parent=self)
wizard.show()
if wizard.exec_():
new_image_settings = wizard.getSettings()
key = "{server}:{imagename}".format(
server=new_image_settings["server"],
imagename=new_image_settings["imagename"])
self._docker_images[key] = DOCKER_CONTAINER_SETTINGS.copy()
self._docker_images[key].update(new_image_settings)
item = QtWidgets.QTreeWidgetItem(self.uiDockerVMsTreeWidget)
item.setText(0, self._docker_images[key]["imagename"])
item.setIcon(
0, QtGui.QIcon(self._docker_images[key]["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
self.uiDockerVMsTreeWidget.setCurrentItem(item)
def _dockerImageEditSlot(self):
"""Edits a Docker image"""
item = self.uiDockerVMsTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
docker_image = self._docker_images[key]
dialog = ConfigurationDialog(
docker_image["imagename"], docker_image,
DockerVMConfigurationPage(), parent=self)
dialog.show()
if dialog.exec_():
if docker_image["imagename"] != item.text(0):
new_key = "{server}:{imagename}".format(
server=docker_image["server"],
name=docker_image["imagename"])
if new_key in self._docker_images:
QtWidgets.QMessageBox.critical(
self, "Docker image",
"Docker image name {} already exists for server {}".format(
docker_image["imagename"],
docker_image["server"]))
docker_image["imagename"] = item.text(0)
return
self._docker_images[new_key] = self._docker_images[key]
del self._docker_images[key]
item.setText(0, docker_image["imagename"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(docker_image)
def _dockerImageDeleteSlot(self):
"""Deletes a Docker image."""
for item in self.uiDockerVMsTreeWidget.selectedItems():
if item:
key = item.data(0, QtCore.Qt.UserRole)
del self._docker_images[key]
self.uiDockerVMsTreeWidget.takeTopLevelItem(
self.uiDockerVMsTreeWidget.indexOfTopLevelItem(item))
def _dockerImagePressedSlot(self, item, column):
"""Slot for item pressed.
:param item: ignored
:param column: ignored
"""
if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.RightButton:
self._showContextualMenu()
def _showContextualMenu(self):
"""Contextual menu."""
menu = QtWidgets.QMenu()
change_symbol_action = QtWidgets.QAction("Change symbol", menu)
change_symbol_action.setIcon(QtGui.QIcon(":/icons/node_conception.svg"))
change_symbol_action.setEnabled(len(self.uiDockerVMsTreeWidget.selectedItems()) == 1)
change_symbol_action.triggered.connect(self._changeSymbolSlot)
menu.addAction(change_symbol_action)
delete_action = QtWidgets.QAction("Delete", menu)
delete_action.triggered.connect(self._dockerImageDeleteSlot)
menu.addAction(delete_action)
menu.exec_(QtGui.QCursor.pos())
def _changeSymbolSlot(self):
"""Change a symbol for a Docker image."""
item = self.uiDockerVMsTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
docker_image = self._docker_images[key]
dialog = SymbolSelectionDialog(
self, symbol=docker_image["symbol"],
category=docker_image["category"])
dialog.show()
if dialog.exec_():
normal_symbol, selected_symbol = dialog.getSymbols()
category = dialog.getCategory()
item.setIcon(0, QtGui.QIcon(normal_symbol))
docker_image["symbol"] = normal_symbol
docker_image["category"] = category
def loadPreferences(self):
"""Loads the Docker VM preferences."""
docker_module = Docker.instance()
self._docker_images = copy.deepcopy(docker_module.dockerImages())
self._items.clear()
for key, docker_image in self._docker_images.items():
item = QtWidgets.QTreeWidgetItem(self.uiDockerVMsTreeWidget)
item.setText(0, docker_image["imagename"])
item.setIcon(0, QtGui.QIcon(docker_image["symbol"]))
item.setData(0, QtCore.Qt.UserRole, key)
self._items.append(item)
if self._items:
self.uiDockerVMsTreeWidget.setCurrentItem(self._items[0])
self.uiDockerVMsTreeWidget.sortByColumn(
0, QtCore.Qt.AscendingOrder)
def savePreferences(self):
"""Saves the Docker image preferences."""
Docker.instance().setDockerImages(self._docker_images)

View File

@@ -0,0 +1,39 @@
# -*- 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/>.
"""
Default Docker settings.
"""
import sys
from gns3.node import Node
DOCKER_SETTINGS = {
"docker_url": "",
"docker_user": "",
"use_local_server": sys.platform.startswith("linux") # Docker only supported on Linux
}
DOCKER_CONTAINER_SETTINGS = {
"symbol": ":/symbols/vbox_guest.svg",
"category": Node.end_devices,
"adapters": 4,
"adapter_type": "veth",
"console": "gnome-terminal",
"enable_remote_console": False,
"startcmd": ""
}

View File

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DockerPreferencesPageWidget</class>
<widget class="QWidget" name="DockerPreferencesPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Docker</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="uiTabWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="uiServerSettingsTabWidget">
<attribute name="title">
<string>General settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="uiUseLocalServercheckBox">
<property name="text">
<string>Use the local server</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>455</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>254</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="uiRestoreDefaultsPushButton">
<property name="text">
<string>Restore defaults</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/docker/ui/docker_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DockerPreferencesPageWidget(object):
def setupUi(self, DockerPreferencesPageWidget):
DockerPreferencesPageWidget.setObjectName("DockerPreferencesPageWidget")
DockerPreferencesPageWidget.resize(200, 200)
self.verticalLayout = QtWidgets.QVBoxLayout(DockerPreferencesPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(DockerPreferencesPageWidget)
self.uiTabWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.uiTabWidget.setObjectName("uiTabWidget")
self.uiServerSettingsTabWidget = QtWidgets.QWidget()
self.uiServerSettingsTabWidget.setObjectName("uiServerSettingsTabWidget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiServerSettingsTabWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiUseLocalServercheckBox = QtWidgets.QCheckBox(self.uiServerSettingsTabWidget)
self.uiUseLocalServercheckBox.setChecked(True)
self.uiUseLocalServercheckBox.setObjectName("uiUseLocalServercheckBox")
self.verticalLayout_2.addWidget(self.uiUseLocalServercheckBox)
spacerItem = QtWidgets.QSpacerItem(20, 455, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem)
self.uiTabWidget.addTab(self.uiServerSettingsTabWidget, "")
self.verticalLayout.addWidget(self.uiTabWidget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem1 = QtWidgets.QSpacerItem(254, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.uiRestoreDefaultsPushButton = QtWidgets.QPushButton(DockerPreferencesPageWidget)
self.uiRestoreDefaultsPushButton.setObjectName("uiRestoreDefaultsPushButton")
self.horizontalLayout_2.addWidget(self.uiRestoreDefaultsPushButton)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(DockerPreferencesPageWidget)
self.uiTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(DockerPreferencesPageWidget)
def retranslateUi(self, DockerPreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
DockerPreferencesPageWidget.setWindowTitle(_translate("DockerPreferencesPageWidget", "Docker"))
self.uiUseLocalServercheckBox.setText(_translate("DockerPreferencesPageWidget", "Use the local server"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiServerSettingsTabWidget), _translate("DockerPreferencesPageWidget", "General settings"))
self.uiRestoreDefaultsPushButton.setText(_translate("DockerPreferencesPageWidget", "Restore defaults"))

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>dockerVMConfigPageWidget</class>
<widget class="QWidget" name="dockerVMConfigPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>509</width>
<height>346</height>
</rect>
</property>
<property name="windowTitle">
<string>Docker image configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="uiTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiNameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiCMDLabel">
<property name="text">
<string>CMD:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiCMDLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiImageListLabel">
<property name="text">
<string>Image name:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="uiImageListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/cetko/projects/gns3/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_dockerVMConfigPageWidget(object):
def setupUi(self, dockerVMConfigPageWidget):
dockerVMConfigPageWidget.setObjectName("dockerVMConfigPageWidget")
dockerVMConfigPageWidget.resize(509, 346)
self.verticalLayout = QtWidgets.QVBoxLayout(dockerVMConfigPageWidget)
self.verticalLayout.setObjectName("verticalLayout")
self.uiTabWidget = QtWidgets.QTabWidget(dockerVMConfigPageWidget)
self.uiTabWidget.setObjectName("uiTabWidget")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLabel = QtWidgets.QLabel(self.tab)
self.uiNameLabel.setObjectName("uiNameLabel")
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
self.uiCMDLabel = QtWidgets.QLabel(self.tab)
self.uiCMDLabel.setObjectName("uiCMDLabel")
self.gridLayout.addWidget(self.uiCMDLabel, 1, 0, 1, 1)
self.uiCMDLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiCMDLineEdit.setObjectName("uiCMDLineEdit")
self.gridLayout.addWidget(self.uiCMDLineEdit, 1, 1, 1, 1)
self.uiImageListLabel = QtWidgets.QLabel(self.tab)
self.uiImageListLabel.setObjectName("uiImageListLabel")
self.gridLayout.addWidget(self.uiImageListLabel, 2, 0, 1, 1)
self.uiImageListComboBox = QtWidgets.QComboBox(self.tab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiImageListComboBox.sizePolicy().hasHeightForWidth())
self.uiImageListComboBox.setSizePolicy(sizePolicy)
self.uiImageListComboBox.setObjectName("uiImageListComboBox")
self.gridLayout.addWidget(self.uiImageListComboBox, 2, 1, 1, 1)
self.uiTabWidget.addTab(self.tab, "")
self.verticalLayout.addWidget(self.uiTabWidget)
self.retranslateUi(dockerVMConfigPageWidget)
self.uiTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(dockerVMConfigPageWidget)
def retranslateUi(self, dockerVMConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
dockerVMConfigPageWidget.setWindowTitle(_translate("dockerVMConfigPageWidget", "Docker image configuration"))
self.uiNameLabel.setText(_translate("dockerVMConfigPageWidget", "Name:"))
self.uiCMDLabel.setText(_translate("dockerVMConfigPageWidget", "CMD:"))
self.uiImageListLabel.setText(_translate("dockerVMConfigPageWidget", "Image name:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("dockerVMConfigPageWidget", "General settings"))

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DockerVMPreferencesPageWidget</class>
<widget class="QWidget" name="DockerVMPreferencesPageWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>505</width>
<height>350</height>
</rect>
</property>
<property name="windowTitle">
<string>Docker Containers</string>
</property>
<property name="accessibleName">
<string>Docker VM templates</string>
</property>
<property name="accessibleDescription">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="uiNewDockerVMPushButton">
<property name="text">
<string>&amp;New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiEditDockerVMPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiDeleteDockerVMPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QTreeWidget" name="uiDockerVMInfoTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="indentation">
<number>10</number>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>1</string>
</property>
</column>
<column>
<property name="text">
<string>2</string>
</property>
</column>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QTreeWidget" name="uiDockerVMsTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>uiNewDockerVMPushButton</tabstop>
<tabstop>uiDeleteDockerVMPushButton</tabstop>
</tabstops>
<resources/>
<connections/>
<designerdata>
<property name="gridDeltaX">
<number>10</number>
</property>
<property name="gridDeltaY">
<number>10</number>
</property>
<property name="gridSnapX">
<bool>true</bool>
</property>
<property name="gridSnapY">
<bool>true</bool>
</property>
<property name="gridVisible">
<bool>true</bool>
</property>
</designerdata>
</ui>

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/docker/ui/docker_vm_preferences_page.ui'
#
# Created by: PyQt5 UI code generator 5.5
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DockerVMPreferencesPageWidget(object):
def setupUi(self, DockerVMPreferencesPageWidget):
DockerVMPreferencesPageWidget.setObjectName("DockerVMPreferencesPageWidget")
DockerVMPreferencesPageWidget.resize(505, 350)
DockerVMPreferencesPageWidget.setAccessibleDescription("")
self.gridLayout = QtWidgets.QGridLayout(DockerVMPreferencesPageWidget)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.uiNewDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
self.uiNewDockerVMPushButton.setObjectName("uiNewDockerVMPushButton")
self.horizontalLayout_5.addWidget(self.uiNewDockerVMPushButton)
self.uiEditDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
self.uiEditDockerVMPushButton.setEnabled(False)
self.uiEditDockerVMPushButton.setObjectName("uiEditDockerVMPushButton")
self.horizontalLayout_5.addWidget(self.uiEditDockerVMPushButton)
self.uiDeleteDockerVMPushButton = QtWidgets.QPushButton(DockerVMPreferencesPageWidget)
self.uiDeleteDockerVMPushButton.setEnabled(False)
self.uiDeleteDockerVMPushButton.setObjectName("uiDeleteDockerVMPushButton")
self.horizontalLayout_5.addWidget(self.uiDeleteDockerVMPushButton)
self.gridLayout.addLayout(self.horizontalLayout_5, 1, 1, 1, 1)
self.uiDockerVMInfoTreeWidget = QtWidgets.QTreeWidget(DockerVMPreferencesPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDockerVMInfoTreeWidget.sizePolicy().hasHeightForWidth())
self.uiDockerVMInfoTreeWidget.setSizePolicy(sizePolicy)
self.uiDockerVMInfoTreeWidget.setIndentation(10)
self.uiDockerVMInfoTreeWidget.setAllColumnsShowFocus(True)
self.uiDockerVMInfoTreeWidget.setObjectName("uiDockerVMInfoTreeWidget")
self.uiDockerVMInfoTreeWidget.header().setVisible(False)
self.gridLayout.addWidget(self.uiDockerVMInfoTreeWidget, 0, 1, 1, 1)
self.uiDockerVMsTreeWidget = QtWidgets.QTreeWidget(DockerVMPreferencesPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDockerVMsTreeWidget.sizePolicy().hasHeightForWidth())
self.uiDockerVMsTreeWidget.setSizePolicy(sizePolicy)
self.uiDockerVMsTreeWidget.setMaximumSize(QtCore.QSize(160, 16777215))
font = QtGui.QFont()
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.uiDockerVMsTreeWidget.setFont(font)
self.uiDockerVMsTreeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiDockerVMsTreeWidget.setIconSize(QtCore.QSize(32, 32))
self.uiDockerVMsTreeWidget.setRootIsDecorated(False)
self.uiDockerVMsTreeWidget.setObjectName("uiDockerVMsTreeWidget")
self.uiDockerVMsTreeWidget.headerItem().setText(0, "1")
self.uiDockerVMsTreeWidget.header().setVisible(False)
self.gridLayout.addWidget(self.uiDockerVMsTreeWidget, 0, 0, 2, 1)
self.retranslateUi(DockerVMPreferencesPageWidget)
QtCore.QMetaObject.connectSlotsByName(DockerVMPreferencesPageWidget)
DockerVMPreferencesPageWidget.setTabOrder(self.uiNewDockerVMPushButton, self.uiDeleteDockerVMPushButton)
def retranslateUi(self, DockerVMPreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
DockerVMPreferencesPageWidget.setWindowTitle(_translate("DockerVMPreferencesPageWidget", "Docker Containers"))
DockerVMPreferencesPageWidget.setAccessibleName(_translate("DockerVMPreferencesPageWidget", "Docker VM templates"))
self.uiNewDockerVMPushButton.setText(_translate("DockerVMPreferencesPageWidget", "&New"))
self.uiEditDockerVMPushButton.setText(_translate("DockerVMPreferencesPageWidget", "&Edit"))
self.uiDeleteDockerVMPushButton.setText(_translate("DockerVMPreferencesPageWidget", "&Delete"))
self.uiDockerVMInfoTreeWidget.headerItem().setText(0, _translate("DockerVMPreferencesPageWidget", "1"))
self.uiDockerVMInfoTreeWidget.headerItem().setText(1, _translate("DockerVMPreferencesPageWidget", "2"))

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DockerVMWizard</class>
<widget class="QWizard" name="DockerVMWizard">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>585</width>
<height>424</height>
</rect>
</property>
<property name="windowTitle">
<string>New Docker VM template</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QWizardPage" name="uiServerWizardPage">
<property name="title">
<string>Server</string>
</property>
<property name="subTitle">
<string>Please choose a server type to run your new Docker VM.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="uiServerTypeGroupBox">
<property name="title">
<string>Server type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="uiRemoteRadioButton">
<property name="text">
<string>Remote</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiVMRadioButton">
<property name="text">
<string>GNS3 VM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="uiLocalRadioButton">
<property name="text">
<string>Local</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiRemoteServersGroupBox">
<property name="title">
<string>Remote servers</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="uiLoadBalanceCheckBox">
<property name="text">
<string>Load balance across all available remote servers</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiRemoteServersLabel">
<property name="text">
<string>Run on server:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="uiRemoteServersComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiImageWizardPage">
<property name="title">
<string>Docker Virtual Machine</string>
</property>
<property name="subTitle">
<string>Please choose a Docker virtual machine from the list.</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="uiImageListLabel">
<property name="text">
<string>Image list:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="uiImageListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>uiImageListComboBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/docker/ui/docker_vm_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.5
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DockerVMWizard(object):
def setupUi(self, DockerVMWizard):
DockerVMWizard.setObjectName("DockerVMWizard")
DockerVMWizard.resize(585, 424)
DockerVMWizard.setModal(True)
self.uiServerWizardPage = QtWidgets.QWizardPage()
self.uiServerWizardPage.setObjectName("uiServerWizardPage")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerWizardPage)
self.verticalLayout.setObjectName("verticalLayout")
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
self.horizontalLayout.setObjectName("horizontalLayout")
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiRemoteRadioButton.setChecked(True)
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
self.horizontalLayout.addWidget(self.uiVMRadioButton)
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.verticalLayout.addWidget(self.uiServerTypeGroupBox)
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
self.gridLayout_7 = QtWidgets.QGridLayout(self.uiRemoteServersGroupBox)
self.gridLayout_7.setObjectName("gridLayout_7")
self.uiLoadBalanceCheckBox = QtWidgets.QCheckBox(self.uiRemoteServersGroupBox)
self.uiLoadBalanceCheckBox.setChecked(True)
self.uiLoadBalanceCheckBox.setObjectName("uiLoadBalanceCheckBox")
self.gridLayout_7.addWidget(self.uiLoadBalanceCheckBox, 0, 0, 1, 2)
self.uiRemoteServersLabel = QtWidgets.QLabel(self.uiRemoteServersGroupBox)
self.uiRemoteServersLabel.setObjectName("uiRemoteServersLabel")
self.gridLayout_7.addWidget(self.uiRemoteServersLabel, 1, 0, 1, 1)
self.uiRemoteServersComboBox = QtWidgets.QComboBox(self.uiRemoteServersGroupBox)
self.uiRemoteServersComboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteServersComboBox.sizePolicy().hasHeightForWidth())
self.uiRemoteServersComboBox.setSizePolicy(sizePolicy)
self.uiRemoteServersComboBox.setObjectName("uiRemoteServersComboBox")
self.gridLayout_7.addWidget(self.uiRemoteServersComboBox, 1, 1, 1, 1)
self.verticalLayout.addWidget(self.uiRemoteServersGroupBox)
DockerVMWizard.addPage(self.uiServerWizardPage)
self.uiImageWizardPage = QtWidgets.QWizardPage()
self.uiImageWizardPage.setObjectName("uiImageWizardPage")
self.gridLayout = QtWidgets.QGridLayout(self.uiImageWizardPage)
self.gridLayout.setObjectName("gridLayout")
self.uiImageListLabel = QtWidgets.QLabel(self.uiImageWizardPage)
self.uiImageListLabel.setObjectName("uiImageListLabel")
self.gridLayout.addWidget(self.uiImageListLabel, 0, 0, 1, 1)
self.uiImageListComboBox = QtWidgets.QComboBox(self.uiImageWizardPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiImageListComboBox.sizePolicy().hasHeightForWidth())
self.uiImageListComboBox.setSizePolicy(sizePolicy)
self.uiImageListComboBox.setObjectName("uiImageListComboBox")
self.gridLayout.addWidget(self.uiImageListComboBox, 0, 1, 1, 1)
DockerVMWizard.addPage(self.uiImageWizardPage)
self.retranslateUi(DockerVMWizard)
QtCore.QMetaObject.connectSlotsByName(DockerVMWizard)
def retranslateUi(self, DockerVMWizard):
_translate = QtCore.QCoreApplication.translate
DockerVMWizard.setWindowTitle(_translate("DockerVMWizard", "New Docker VM template"))
self.uiServerWizardPage.setTitle(_translate("DockerVMWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("DockerVMWizard", "Please choose a server type to run your new Docker VM."))
self.uiServerTypeGroupBox.setTitle(_translate("DockerVMWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("DockerVMWizard", "Remote"))
self.uiVMRadioButton.setText(_translate("DockerVMWizard", "GNS3 VM"))
self.uiLocalRadioButton.setText(_translate("DockerVMWizard", "Local"))
self.uiRemoteServersGroupBox.setTitle(_translate("DockerVMWizard", "Remote servers"))
self.uiLoadBalanceCheckBox.setText(_translate("DockerVMWizard", "Load balance across all available remote servers"))
self.uiRemoteServersLabel.setText(_translate("DockerVMWizard", "Run on server:"))
self.uiImageWizardPage.setTitle(_translate("DockerVMWizard", "Docker Virtual Machine"))
self.uiImageWizardPage.setSubTitle(_translate("DockerVMWizard", "Please choose a Docker virtual machine from the list."))
self.uiImageListLabel.setText(_translate("DockerVMWizard", "Image list:"))

View File

@@ -20,11 +20,13 @@ Dynamips module implementation.
"""
import os
import glob
import shutil
import hashlib
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.node import Node
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
from ..module_error import ModuleError
@@ -41,9 +43,10 @@ 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 DYNAMIPS_SETTINGS
from .settings import IOS_ROUTER_SETTINGS
from .settings import PLATFORMS_DEFAULT_RAM
from .settings import DEFAULT_IDLEPC
PLATFORM_TO_CLASS = {
"c1700": C1700,
@@ -55,42 +58,74 @@ PLATFORM_TO_CLASS = {
"c7200": C7200
}
import logging
log = logging.getLogger(__name__)
class Dynamips(Module):
"""
Dynamips module.
"""
def __init__(self):
Module.__init__(self)
super().__init__()
self._settings = {}
self._ios_routers = {}
self._servers = []
self._nodes = []
self._working_dir = ""
self._images_dir = ""
self._ios_images_cache = {}
self.configChangedSlot()
def configChangedSlot(self):
# load the settings and IOS images.
self._loadSettings()
self._loadIOSRouters()
@staticmethod
def getDefaultIdlePC(path):
"""
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
@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.
"""
# load the settings
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in DYNAMIPS_SETTINGS.items():
self._settings[name] = settings.value(name, value, type=DYNAMIPS_SETTING_TYPES[name])
settings.endGroup()
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
if not os.path.exists(self._settings["dynamips_path"]):
dynamips_path = shutil.which("dynamips")
if dynamips_path:
self._settings["dynamips_path"] = os.path.abspath(dynamips_path)
else:
self._settings["dynamips_path"] = ""
self._loadIOSRouters()
def _saveSettings(self):
"""
@@ -98,176 +133,50 @@ class Dynamips(Module):
"""
# save the settings
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in self._settings.items():
settings.setValue(name, value)
settings.endGroup()
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
# save some settings to the local server config file
server_settings = {
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
"ghost_ios_support": self._settings["ghost_ios_support"],
"sparse_memory_support": self._settings["sparse_memory_support"],
"mmap_support": self._settings["mmap_support"],
}
if self._settings["dynamips_path"]:
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
def _loadIOSRouters(self):
"""
Load the IOS routers from the persistent settings file.
"""
# load the settings
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
# load the IOS images
size = settings.beginReadArray("ios_router")
for index in range(0, size):
settings.setArrayIndex(index)
name = settings.value("name")
server = settings.value("server")
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers or not name or not server:
continue
self._ios_routers[key] = {}
for setting_name, default_value in IOS_ROUTER_SETTINGS.items():
self._ios_routers[key][setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if settings.contains(slot):
self._ios_routers[key][slot] = settings.value(slot, "")
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if settings.contains(wic):
self._ios_routers[key][wic] = settings.value(wic, "")
platform = self._ios_routers[key]["platform"]
chassis = self._ios_routers[key]["chassis"]
if platform == "c7200":
self._ios_routers[key]["midplane"] = settings.value("midplane", "vxr")
self._ios_routers[key]["npe"] = settings.value("npe", "npe-400")
self._ios_routers[key]["slot0"] = settings.value("slot0", "C7200-IO-FE")
else:
self._ios_routers[key]["iomem"] = 5
if platform in ("c3725", "c3725", "c2691"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "GT96100-FE")
elif platform == "c3600" and chassis == "3660":
self._ios_routers[key]["slot0"] = settings.value("slot0", "Leopard-2FE")
elif platform == "c2600" and chassis == "2610":
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1E")
elif platform == "c2600" and chassis == "2611":
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2E")
elif platform == "c2600" and chassis in ("2620", "2610XM", "2620XM", "2650XM"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1FE")
elif platform == "c2600" and chassis in ("2621", "2611XM", "2621XM", "2651XM"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2FE")
elif platform == "c1700" and chassis in ("1720", "1721", "1750", "1751", "1760"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-1FE")
elif platform == "c1700" and chassis in ("1751", "1760"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
settings.endArray()
settings.endGroup()
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 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
def _saveIOSRouters(self):
"""
Saves the IOS routers to the persistent settings file.
"""
# save the settings
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
settings.remove("")
# save the IOS images
settings.beginWriteArray("ios_router", len(self._ios_routers))
index = 0
for ios_router in self._ios_routers.values():
settings.setArrayIndex(index)
for name, value in ios_router.items():
settings.setValue(name, value)
index += 1
settings.endArray()
settings.endGroup()
def _delete_dynamips_files(self):
"""
Deletes useless local Dynamips files from the working directory
"""
files = glob.glob(os.path.join(self._working_dir, "dynamips", "*.ghost"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "*_lock"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "ilt_*"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_rommon_vars"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_ssa"))
for file in files:
try:
log.debug("deleting file {}".format(file))
os.remove(file)
except OSError as e:
log.warn("could not delete file {}: {}".format(file, e))
continue
def setProjectFilesDir(self, path):
"""
Sets the project files directory path this module.
:param path: path to the local project files directory
"""
#self._delete_dynamips_files() #FIXME: cause issues
self._working_dir = path
log.info("local working directory for Dynamips module: {}".format(self._working_dir))
# update the server with the new working directory / project name
for server in self._servers:
if server.connected():
self._sendSettings(server)
def setImageFilesDir(self, path):
"""
Sets the image files directory path this module.
:param path: path to the local image files directory
"""
self._images_dir = os.path.join(path, "IOS")
def imageFilesDir(self):
"""
Returns the files directory path this module.
:returns: path to the local image files directory
"""
return self._images_dir
def addServer(self, server):
"""
Adds a server to be used by this module.
:param server: WebSocketClient instance
"""
log.info("adding server {}:{} to Dynamips module".format(server.host, server.port))
self._servers.append(server)
self._sendSettings(server)
def removeServer(self, server):
"""
Removes a server from being used by this module.
:param server: WebSocketClient instance
"""
log.info("removing server {}:{} from Dynamips module".format(server.host, server.port))
self._servers.remove(server)
def servers(self):
"""
Returns all the servers used by this module.
:returns: list of WebSocketClient instances
"""
return self._servers
self._settings["routers"] = list(self._ios_routers.values())
self._saveSettings()
def addNode(self, node):
"""
@@ -286,6 +195,8 @@ 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):
@@ -323,99 +234,22 @@ class Dynamips(Module):
:param settings: module settings (dictionary)
"""
params = {}
for name, value in settings.items():
if name in self._settings and self._settings[name] != value:
params[name] = value
if params:
for server in self._servers:
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
params.update({"project_name": project_name})
server.send_notification("dynamips.settings", params)
self._settings.update(settings)
self._saveSettings()
def _sendSettings(self, server):
"""
Sends the module settings to the server.
:param server: WebSocketClient instance
"""
log.info("sending Dynamips settings to server {}:{}".format(server.host, server.port))
params = self._settings.copy()
# send the local working directory only if this is a local server
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
params.update({"project_name": project_name})
server.send_notification("dynamips.settings", params)
def allocateServer(self, node_class, use_cloud=False):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (WebSocketClient instance)
"""
# allocate a server for the node
servers = Servers.instance()
if use_cloud:
from ...topology import Topology
topology = Topology.instance()
top_instance = topology.anyInstance()
server = servers.getCloudServer(top_instance.host, top_instance.port, top_instance.ssl_ca_file)
else:
if self._settings["use_local_server"]:
# use the local server
server = servers.localServer()
else:
# pick up a remote server (round-robin method)
server = next(iter(servers))
if not server:
raise ModuleError("No remote server is configured")
return server
def createNode(self, node_class, server):
def createNode(self, node_class, server, project):
"""
Creates a new node.
:param node_class: Node object
:param server: WebSocketClient instance
:param server: HTTPClient instance
:param project: Project instance
"""
log.info("creating node {}".format(node_class))
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
server.reconnect()
except OSError as e:
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
if server not in self._servers:
self.addServer(server)
# create an instance of the node class
return node_class(self, server)
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""
@@ -428,7 +262,6 @@ class Dynamips(Module):
log.info("configuring node {}".format(node))
if isinstance(node, Router):
ios_router = None
if node_name:
for ios_key, info in self._ios_routers.items():
@@ -436,63 +269,32 @@ class Dynamips(Module):
ios_router = self._ios_routers[ios_key]
break
# hack for EtherSwitch router
if isinstance(node, EtherSwitchRouter) and node.server() == Servers.instance().localServer():
for info in self._ios_routers.values():
if info["platform"] == "c3725" and info["server"] == "local":
ios_router = {
"platform": "c3725",
"path": info["path"],
"ram": info["ram"],
"startup_config": info["startup_config"],
}
break
if not ios_router:
raise ModuleError("Please create an c3725 IOS router in order to use an EtherSwitch router")
if not ios_router:
raise ModuleError("No IOS router for platform {}".format(node.settings()["platform"]))
settings = {}
# set initial settings like the chassis or an Idle-PC value etc.
if "chassis" in ios_router and ios_router["chassis"]:
settings["chassis"] = ios_router["chassis"]
if "idlepc" in ios_router and ios_router["idlepc"]:
settings["idlepc"] = ios_router["idlepc"]
if ios_router["startup_config"]:
settings["startup_config"] = ios_router["startup_config"]
if "private_config" in ios_router and ios_router["private_config"]:
settings["private_config"] = ios_router["private_config"]
vm_settings = {}
for setting_name, value in ios_router.items():
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
vm_settings[setting_name] = value
if ios_router["platform"] == "c7200":
settings["midplane"] = ios_router["midplane"]
settings["npe"] = ios_router["npe"]
elif "iomem" in ios_router:
settings["iomem"] = ios_router["iomem"]
base_name = "R"
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
# must be an EtherSwitch router
base_name = "ESW"
if "nvram" in ios_router and ios_router["nvram"]:
settings["nvram"] = ios_router["nvram"]
# Older GNS3 versions may have the following invalid settings in the VM template
if "console" in vm_settings:
del vm_settings["console"]
if "sensors" in vm_settings:
del vm_settings["sensors"]
if "power_supplies" in vm_settings:
del vm_settings["power_supplies"]
if "disk0" in ios_router and ios_router["disk0"]:
settings["disk0"] = ios_router["disk0"]
if "disk1" in ios_router and ios_router["disk1"]:
settings["disk1"] = ios_router["disk1"]
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if slot in ios_router:
settings[slot] = ios_router[slot]
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if wic in ios_router:
settings[wic] = ios_router[wic]
if node.server().isCloud():
settings["cloud_path"] = "images/IOS"
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
else:
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
ram = vm_settings.pop("ram")
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)
else:
node.setup()
@@ -505,7 +307,7 @@ class Dynamips(Module):
"""
for ios_router in self._ios_routers.values():
if ios_router["path"] == image_path:
if os.path.basename(ios_router["image"]) == image_path:
if ios_router["idlepc"] != idlepc:
ios_router["idlepc"] = idlepc
self._saveIOSRouters()
@@ -513,36 +315,12 @@ class Dynamips(Module):
def reset(self):
"""
Resets the servers and nodes.
Resets the module.
"""
log.info("Dynamips module reset")
for server in self._servers:
if server.connected():
server.send_notification("dynamips.reset")
self._servers.clear()
for node in self._nodes:
node.reset()
self._nodes.clear()
def notification(self, destination, params):
"""
To received notifications from the server.
:param destination: JSON-RPC method
:param params: JSON-RPC params
"""
if "devices" in params:
for node in self._nodes:
for device in params["devices"]:
if node.name() == device:
message = "node {}: {}".format(node.name(), params["message"])
self.notification_signal.emit(message, params["details"])
if hasattr(node, "stop"):
node.stop()
def exportConfigs(self, directory):
"""
Exports all configs for all nodes to a directory.
@@ -551,8 +329,8 @@ class Dynamips(Module):
"""
for node in self._nodes:
if hasattr(node, "exportConfigs") and node.initialized():
node.exportConfigs(directory)
if isinstance(node, Router) and node.initialized():
node.exportConfigToDirectory(directory)
def importConfigs(self, directory):
"""
@@ -562,8 +340,8 @@ class Dynamips(Module):
"""
for node in self._nodes:
if hasattr(node, "importConfigs") and node.initialized():
node.importConfigs(directory)
if isinstance(node, Router) and node.initialized():
node.importConfigFromDirectory(directory)
def findAlternativeIOSImage(self, image, node):
"""
@@ -582,7 +360,7 @@ class Dynamips(Module):
mainwindow = MainWindow.instance()
ios_routers = self.iosRouters()
candidate_ios_images = {}
alternative_image = {"path": image,
alternative_image = {"image": image,
"ram": None,
"idlepc": None}
@@ -592,23 +370,25 @@ 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]
alternative_image["path"] = ios_router["path"]
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"]
self._ios_images_cache[image] = alternative_image
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
path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
if path:
alternative_image["path"] = path
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow, None)
if image_path:
alternative_image["image"] = image_path
self._ios_images_cache[image] = alternative_image
return alternative_image
@@ -640,22 +420,13 @@ 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 [EtherSwitchRouter, EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
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()}
)
for ios_router in self._ios_routers.values():
@@ -663,9 +434,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

@@ -55,8 +55,8 @@ ADAPTER_MATRIX = {"C1700-MB-1FE": {"nb_ports": 1,
"port": FastEthernetPort},
"C7200-IO-FE": {"nb_ports": 1,
"wics": 0,
"port": FastEthernetPort},
"wics": 0,
"port": FastEthernetPort},
"C7200-IO-GE-E": {"nb_ports": 1,
"wics": 0,
@@ -125,4 +125,4 @@ ADAPTER_MATRIX = {"C1700-MB-1FE": {"nb_ports": 1,
"PA-POS-OC3": {"nb_ports": 1,
"wics": 0,
"port": POSPort},
}
}

View File

@@ -19,18 +19,19 @@
Wizard for IOS routers.
"""
import sys
import os
import re
from gns3.qt import QtCore, QtGui
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.servers import Servers
from gns3.utils.message_box import MessageBox
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, 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
@@ -50,8 +51,12 @@ PLATFORM_TO_CLASS = {
"c7200": C7200
}
import logging
log = logging.getLogger(__name__)
class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
"""
Wizard to create an IOS router.
@@ -61,23 +66,30 @@ 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)
self.uiPlatformComboBox.currentIndexChanged[str].connect(self._platformChangedSlot)
self.uiPlatformComboBox.addItems(list(PLATFORMS_DEFAULT_RAM.keys()))
#FIXME: hide because of issue on Windows.
self._router = None
# Validate the Idle PC value
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())
# location of the base config templates
self._base_startup_config_template = get_resource(os.path.join("configs", "ios_base_startup-config.txt"))
self._base_private_config_template = get_resource(os.path.join("configs", "ios_base_private-config.txt"))
self._base_etherswitch_startup_config_template = get_resource(os.path.join("configs", "ios_etherswitch_startup-config.txt"))
# FIXME: hide because of issue on Windows.
self.uiTestIOSImagePushButton.hide()
# Mandatory fields
@@ -96,140 +108,18 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
1: self.uiWic1comboBox,
2: self.uiWic2comboBox}
self._ios_routers = ios_routers
if Dynamips.instance().settings()["use_local_server"]:
# skip the server page if we use the local server
self.setStartId(1)
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
"""
Slot for when the remote server radio button is toggled.
:param checked: either the button is checked or not
"""
if checked:
self.uiRemoteServersGroupBox.setEnabled(True)
else:
self.uiRemoteServersGroupBox.setEnabled(False)
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)
def _platformChangedSlot(self, platform):
"""
Updates the chassis comboBox based on the selected platform.
:param platform: selected router platform
"""
self.uiChassisComboBox.clear()
if platform in CHASSIS:
self.uiChassisComboBox.addItems(CHASSIS[platform])
def _testIOSImageSlot(self):
"""
Slot to locally test the IOS image.
"""
platform = self.uiPlatformComboBox.currentText()
ram = self.uiRamSpinBox.value()
ios_image = self.uiIOSImageLineEdit.text()
dynamips = os.path.realpath(Dynamips.instance().settings()["path"])
if not os.path.exists(dynamips):
QtGui.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
return
command = '"{path}" -P {platform} -r {ram} "{ios_image}"'.format(path=dynamips,
platform=platform[1:],
ram=ram,
ios_image=ios_image)
try:
RunInTerminal(command)
except OSError as e:
QtGui.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
def _idlePCFinderSlot(self):
"""
Slot for the idle-PC finder.
"""
server = Servers.instance().localServer()
module = Dynamips.instance()
platform = self.uiPlatformComboBox.currentText()
ios_image = self.uiIOSImageLineEdit.text()
ram = self.uiRamSpinBox.value()
router_class = PLATFORM_TO_CLASS[platform]
self._router = router_class(module, server)
self._router.setup(ios_image, ram, name="AUTOIDLEPC")
self._router.created_signal.connect(self.createdSlot)
self.uiIdlePCFinderPushButton.setEnabled(False)
def createdSlot(self, node_id):
"""
The node for the auto Idle-PC has been created.
:param node_id: not used
"""
self._router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
self._auto_idlepc_progress_dialog = QtGui.QProgressDialog("Searching for an Idle-PC value...", "Cancel", 0, 0, parent=self)
self._auto_idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._auto_idlepc_progress_dialog.setWindowTitle("Idle-PC finder")
self._auto_idlepc_progress_dialog.show()
def _computeAutoIdlepcCallback(self, result, error=False):
"""
Callback for computeAutoIdlepc.
:param result: server response
:param error: indicates an error (boolean)
"""
self._router.delete()
if self._auto_idlepc_progress_dialog.wasCanceled():
return
self._auto_idlepc_progress_dialog.accept()
if error:
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: ".format(result["message"]))
else:
if result["idlepc"] and result["idlepc"] != "0x0":
self.uiIdlepcLineEdit.setText(result["idlepc"])
else:
logs = "\n".join(result["logs"])
MessageBox(self, "Idle-PC finder", "Could not find an Idle-PC value", details=logs)
def _iosImageBrowserSlot(self):
"""
Slot to open a file browser and select an IOU image.
"""
from ..pages.ios_router_preferences_page import IOSRouterPreferencesPage
path = IOSRouterPreferencesPage.getIOSImage(self)
if not path:
return
self.uiIOSImageLineEdit.clear()
self.uiIOSImageLineEdit.setText(path)
self.addImageSelector(self.uiIOSExistingImageRadioButton, self.uiIOSImageListComboBox, self.uiIOSImageLineEdit, self.uiIOSImageToolButton, IOSRouterPreferencesPage.getIOSImage)
def _prefillPlatform(self):
"""
Try to guess the platform based on image name
"""
# try to guess the platform
image = os.path.basename(path)
match = re.match("^(c[0-9]+)\\-\w+", image.lower())
image = os.path.basename(self.uiIOSImageLineEdit.text())
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)
@@ -242,9 +132,12 @@ 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"):
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:
self.uiPlatformComboBox.setCurrentIndex(index)
@@ -253,6 +146,182 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if index != -1:
self.uiChassisComboBox.setCurrentIndex(index)
def _platformChangedSlot(self, platform):
"""
Updates the chassis comboBox based on the selected platform.
:param platform: selected router platform
"""
self.uiChassisComboBox.clear()
if platform in CHASSIS:
self.uiChassisComboBox.addItems(CHASSIS[platform])
if platform not in ("c2600", "c3600", "c2691", "c3725", "c3745"):
self.uiEtherSwitchCheckBox.setChecked(False)
self.uiEtherSwitchCheckBox.hide()
else:
self.uiEtherSwitchCheckBox.show()
def _testIOSImageSlot(self):
"""
Slot to locally test the IOS image.
"""
platform = self.uiPlatformComboBox.currentText()
ram = self.uiRamSpinBox.value()
ios_image = self.uiIOSImageLineEdit.text()
dynamips = os.path.realpath(Dynamips.instance().settings()["image"])
if not os.path.exists(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:],
ram=ram,
ios_image=ios_image)
try:
RunInTerminal(command)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
def _idlePCValidateSlot(self):
"""
Slot to validate the entered Idle-PC Value
"""
validator = self.uiIdlepcLineEdit.validator()
text_input = self.uiIdlepcLineEdit.text()
state = validator.validate(text_input, len(text_input))[0]
if state == QtGui.QValidator.Acceptable:
color = '#A2C964' # green
self._idle_valid = True
elif state == QtGui.QValidator.Intermediate:
color = '#fff79a' # yellow
self._idle_valid = False
else:
color = '#f6989d' # red
self._idle_valid = False
self.uiIdlepcLineEdit.setStyleSheet('QLineEdit { background-color: %s }' % color)
def _idlePCFinderSlot(self):
"""
Slot for the idle-PC finder.
"""
from gns3.main_window import MainWindow
main_window = MainWindow.instance()
server = Servers.instance().getServerFromString(self.getSettings()["server"])
module = Dynamips.instance()
platform = self.uiPlatformComboBox.currentText()
ios_image = self.uiIOSImageLineEdit.text()
ram = self.uiRamSpinBox.value()
router_class = PLATFORM_TO_CLASS[platform]
self._router = router_class(module, server, main_window.project())
self._router.setup(ios_image, ram, name="AUTOIDLEPC")
self._router.created_signal.connect(self.createdSlot)
self._router.server_error_signal.connect(self.serverErrorSlot)
self.uiIdlePCFinderPushButton.setEnabled(False)
def _etherSwitchSlot(self, state):
"""
Slot if the EtherSwitch option is chosen or not.
:param state: boolean
"""
if state:
# forces the name to EtherSwitch
self.uiNameLineEdit.setText("EtherSwitch router")
# self.uiNameLineEdit.setEnabled(False)
else:
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())
# self.uiNameLineEdit.setEnabled(True)
def createdSlot(self, node_id):
"""
The node for the auto Idle-PC has been created.
:param node_id: not used
"""
self._router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
def serverErrorSlot(self, node_id, message):
"""
The auto idle-pc node could not be created.
:param node_id: not used
:param message: error message from the server.
"""
QtWidgets.QMessageBox.critical(self, "Idle-PC finder", "Could not create IOS router: {}".format(message))
def _computeAutoIdlepcCallback(self, result, error=False, **kwargs):
"""
Callback for computeAutoIdlepc.
:param result: server response
:param error: indicates an error (boolean)
"""
if self._router:
self._router.delete()
self._router = None
if error:
QtWidgets.QMessageBox.critical(self, "Idle-PC finder", "Error: {}".format(result["message"]))
else:
idlepc = result["idlepc"]
self.uiIdlepcLineEdit.setText(idlepc)
QtWidgets.QMessageBox.information(self, "Idle-PC finder", "Idle-PC value {} has been found suitable for your IOS image".format(idlepc))
def _iosImageBrowserSlot(self):
"""
Slot to open a file browser and select an IOU image.
"""
from ..pages.ios_router_preferences_page import IOSRouterPreferencesPage
server = Servers.instance().getServerFromString(self.getSettings()["server"])
path = IOSRouterPreferencesPage.getIOSImage(self, server)
if not path:
return
self.uiIOSImageLineEdit.clear()
self.uiIOSImageLineEdit.setText(path)
# try to guess the platform
image = os.path.basename(path)
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
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
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
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.")
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 done(self, result):
if self._router:
self._router.delete()
super().done(result)
def _populateAdapters(self, platform, chassis):
"""
Loads the adapter and WIC configuration.
@@ -270,7 +339,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
for slot_number, slot_adapters in ADAPTER_MATRIX[platform][chassis].items():
self._widget_slots[slot_number].setEnabled(True)
if type(slot_adapters) == str:
if isinstance(slot_adapters, str):
# only one default adapter for this slot.
self._widget_slots[slot_number].addItem(slot_adapters)
else:
@@ -292,12 +361,12 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
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)))
@@ -308,7 +377,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
path = self.uiIOSImageLineEdit.text()
if os.path.isfile(path):
minimum_required_ram = IOSRouterPreferencesPage.getMinimumRequiredRAM(path)
if minimum_required_ram > PLATFORMS_DEFAULT_RAM[platform]:
if minimum_required_ram >= PLATFORMS_DEFAULT_RAM[platform]:
self.uiRamSpinBox.setValue(minimum_required_ram)
else:
self.uiRamSpinBox.setValue(PLATFORMS_DEFAULT_RAM[platform])
@@ -323,35 +392,34 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self._populateAdapters(platform, chassis)
if platform == "c7200":
self.uiSlot0comboBox.setCurrentIndex(self.uiSlot0comboBox.findText("C7200-IO-FE"))
if self.uiEtherSwitchCheckBox.isChecked():
self.uiSlot1comboBox.setCurrentIndex(self.uiSlot1comboBox.findText("NM-16ESW"))
elif self.page(page_id) == self.uiIdlePCWizardPage:
path = self.uiIOSImageLineEdit.text()
idle_pc = Dynamips.getDefaultIdlePC(path)
if idle_pc is not None:
self.uiIdlepcLineEdit.setText(idle_pc)
def validateCurrentPage(self):
"""
Validates the IOS name.
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:
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()
QtWidgets.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
return False
return True
# minimum_required_ram = self._getMinimumRequiredRAM(path)
# if minimum_required_ram > ram:
# QtGui.QMessageBox.warning(self, "IOS image", "There is not sufficient RAM allocated to this IOS image, recommended RAM is {} MB".format(minimum_required_ram))
#
# # basename doesn't work on Unix with Windows paths
# if not sys.platform.startswith('win') and len(path) > 2 and path[1] == ":":
# import ntpath
# image = ntpath.basename(path)
# else:
# image = os.path.basename(path)
#
# if image.startswith("c7200p"):
# QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the c7200 platform with NPE-G2 and using it is not recommended.\nPlease use an IOS image that do not start with c7200p.")
def getSettings(self):
"""
Returns the settings set in this Wizard.
@@ -359,32 +427,42 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
:return: settings dict
"""
path = self.uiIOSImageLineEdit.text()
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
image = self.uiIOSImageLineEdit.text()
if self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiRemoteRadioButton.isChecked():
if self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
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 = {
"name": self.uiNameLineEdit.text(),
"path": path,
"image": image,
"startup_config": get_default_base_config(self._base_startup_config_template),
"ram": self.uiRamSpinBox.value(),
"nvram": PLATFORMS_DEFAULT_NVRAM[platform],
"idlepc": self.uiIdlepcLineEdit.text(),
"image": os.path.basename(path),
"platform": self.uiPlatformComboBox.currentText(),
"platform": platform,
"chassis": self.uiChassisComboBox.currentText(),
"server": server,
}
if self.uiEtherSwitchCheckBox.isChecked():
settings["startup_config"] = get_default_base_config(self._base_etherswitch_startup_config_template)
settings["symbol"] = ":/symbols/multilayer_switch.svg"
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat
settings["category"] = Node.switches
else:
settings["auto_delete_disks"] = True
image_file = os.path.basename(image)
if image_file.lower().startswith("c7200p"):
settings["npe"] = "npe-g2"
for slot_id, widget in self._widget_slots.items():
if widget.isEnabled():
settings["slot{}".format(slot_id)] = widget.currentText()
@@ -406,4 +484,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

@@ -23,35 +23,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
import re
from gns3.node import Node
from gns3.ports.atm_port import ATMPort
from .device import Device
import logging
log = logging.getLogger(__name__)
class ATMSwitch(Node):
class ATMSwitch(Device):
"""
Dynamips ATM switch.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
log.info("ATM switch is being created")
super().__init__(module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._atmsw_id = None
self._ports = []
self._module = module
self._settings = {"name": "",
"mappings": {}}
def setup(self, name=None, initial_ports=[], initial_mappings={}):
def setup(self, name=None, device_id=None, initial_ports=[], initial_mappings={}):
"""
Setups this ATM switch.
:param name: optional name for this switch.
:param device_id: device identifier on the server
:param initial_ports: ports to be automatically added when creating this ATM switch
:param initial_mappings: mappings to be automatically added when creating this ATM switch
"""
@@ -78,61 +79,11 @@ class ATMSwitch(Node):
port.setPacketCaptureSupported(True)
self._ports.append(port)
params = {"name": name}
self._server.send_message("dynamips.atmsw.create", params, self._setupCallback)
def _setupCallback(self, result, error=False):
"""
Callback for setup.
:param result: server response
: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["code"], result["message"])
return
self._atmsw_id = result["id"]
if not self._atmsw_id:
self.error_signal.emit(self.id(), "returned ID from server is null")
return
self._settings["name"] = result["name"]
log.info("ATM switch {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this ATM switch.
"""
log.debug("ATM switch {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._atmsw_id:
self._server.send_message("dynamips.atmsw.delete", {"id": self._atmsw_id}, self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False):
"""
Callback for delete.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
log.info("ATM switch {} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
params = {"name": name,
"device_type": "atm_switch"}
if device_id:
params["device_id"] = device_id
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
def update(self, new_settings):
"""
@@ -141,53 +92,54 @@ class ATMSwitch(Node):
:param new_settings: settings dictionary
"""
ports_to_create = []
mapping = new_settings["mappings"]
updated = False
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
if "mappings" in new_settings:
ports_to_create = []
mapping = new_settings["mappings"]
for port in self._ports.copy():
if port.isFree():
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
for port in self._ports.copy():
if port.isFree():
updated = True
self._ports.remove(port)
else:
ports_to_create.remove(port.name())
for port_name in ports_to_create:
port = ATMPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(ATMPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
self._ports.remove(port)
else:
ports_to_create.remove(port.name())
log.debug("port {} has been added".format(port_name))
for port_name in ports_to_create:
port = ATMPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(ATMPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
self._settings["mappings"] = new_settings["mappings"].copy()
params = {}
if "name" in new_settings and new_settings["name"] != self.name():
if self.hasAllocatedName(new_settings["name"]):
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
return
params = {"id": self._atmsw_id,
"name": new_settings["name"]}
params["name"] = new_settings["name"]
updated = True
self._settings["mappings"] = new_settings["mappings"].copy()
if updated:
if params:
log.debug("{} is being updated: {}".format(self.name(), params))
self._server.send_message("dynamips.atmsw.update", params, self._updateCallback)
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
else:
log.info("ATM switch {} has been updated".format(self.name()))
self.updated_signal.emit()
def _updateCallback(self, result, error=False):
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -197,7 +149,7 @@ class ATMSwitch(Node):
if error:
log.error("error while updating {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.server_error_signal.emit(self.id(), result["message"])
else:
if "name" in result:
self._settings["name"] = result["name"]
@@ -205,33 +157,6 @@ class ATMSwitch(Node):
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
:param port_id: port identifier
"""
log.debug("{} is requesting an UDP port allocation".format(self.name()))
self._server.send_message("dynamips.atmsw.allocate_udp_port", {"id": self._atmsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
def _allocateUDPPortCallback(self, result, error=False):
"""
Callback for allocateUDPPort.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
port_id = result["port_id"]
lport = result["lport"]
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
def addNIO(self, port, nio):
"""
Adds a new NIO on the specified port for this ATM switch.
@@ -240,11 +165,7 @@ class ATMSwitch(Node):
:param nio: NIO instance
"""
params = {"id": self._atmsw_id,
"port": port.portNumber(),
"port_id": port.id()}
params["nio"] = self.getNIOInfo(nio)
params = {"nio": self.getNIOInfo(nio)}
params["mappings"] = {}
for source, destination in self._settings["mappings"].items():
source_port = source.split(":")[0]
@@ -255,124 +176,13 @@ class ATMSwitch(Node):
params["mappings"][destination] = source
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self._server.send_message("dynamips.atmsw.add_nio", params, self._addNIOCallback)
def _addNIOCallback(self, result, error=False):
"""
Callback for addNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.nio_cancel_signal.emit(self.id())
else:
log.debug("{} has added a new NIO: {}".format(self.name(), result))
self.nio_signal.emit(self.id(), result["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this switch.
:param port: Port instance
"""
params = {"id": self._atmsw_id,
"port": port.portNumber()}
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
self._server.send_message("dynamips.atmsw.delete_nio", params, self.deleteNIOCallback)
def deleteNIOCallback(self, result, error=False):
"""
Callback for deleteNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"id": self._atmsw_id,
"port_id": port.id(),
"port": port.portNumber(),
"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.atmsw.start_capture", params, self._startPacketCaptureCallback)
def _startPacketCaptureCallback(self, result, error=False):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
params = {"id": self._atmsw_id,
"port_id": port.id(),
"port": port.portNumber()}
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.atmsw.stop_capture", params, self._stopPacketCaptureCallback)
def _stopPacketCaptureCallback(self, result, error=False):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
break
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def info(self):
"""
@@ -382,14 +192,15 @@ class ATMSwitch(Node):
"""
info = """ATM switch {name} is always-on
Node ID is {id}, server's ATM switch ID is {atmsw_id}
Local node ID is {id}
Server's Device ID is {device_id}
Hardware is Dynamips emulated simple ATM switch
Switch's server runs on {host}:{port}
""".format(name=self.name(),
id=self.id(),
atmsw_id=self._atmsw_id,
host=self._server.host,
port=self._server.port)
device_id=self._device_id,
host=self._server.host(),
port=self._server.port())
port_info = ""
mapping = re.compile(r"""^([0-9]*):([0-9]*):([0-9]*)$""")
@@ -449,11 +260,11 @@ class ATMSwitch(Node):
"""
atmsw = {"id": self.id(),
"device_id": self._device_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {"name": self.name()},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
if self._settings["mappings"]:
atmsw["properties"]["mappings"] = self._settings["mappings"]
@@ -477,6 +288,10 @@ class ATMSwitch(Node):
settings = node_info["properties"]
name = settings.pop("name")
# pre-1.3 projects have no device id, set to 1 to have
# a proper project conversion on the server side
device_id = node_info.get("device_id", 1)
mappings = {}
if "mappings" in settings:
mappings = settings["mappings"]
@@ -487,7 +302,7 @@ class ATMSwitch(Node):
log.info("ATM switch {} is loading".format(name))
self.setName(name)
self.setup(name, ports, mappings)
self.setup(name, device_id, ports, mappings)
def name(self):
"""
@@ -518,7 +333,7 @@ class ATMSwitch(Node):
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
"""
@@ -534,17 +349,7 @@ class ATMSwitch(Node):
: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

@@ -23,34 +23,33 @@ from .router import Router
class C1700(Router):
"""
Dynamips c1700 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server, chassis="1720"):
Router.__init__(self, module, server, platform="c1700")
def __init__(self, module, server, project, chassis="1720"):
self._platform_settings = {"ram": 64,
"nvram": 32,
"disk0": 0,
"disk1": 0,
"chassis": "1720",
"iomem": 15,
"clock_divisor": 8,
"slot0": "C1700-MB-1FE"}
super().__init__(module, server, project, platform="c1700")
c1700_settings = {"ram": 128,
"nvram": 32,
"disk0": 0,
"disk1": 0,
"chassis": "1720",
"iomem": 15,
"clock_divisor": 8,
"slot0": "C1700-MB-1FE"}
# set the default adapter for slot 1 for these chassis
if chassis in ['1751', '1760']:
self._platform_settings["slot1"] = "C1700-MB-WIC1"
c1700_settings["slot1"] = "C1700-MB-WIC1"
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c1700_settings)
def __str__(self):

View File

@@ -23,11 +23,13 @@ from .router import Router
class C2600(Router):
"""
Dynamips c2600 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
# get the default slot0 adapter based on the chassis
@@ -42,25 +44,22 @@ class C2600(Router):
"2650XM": "C2600-MB-1FE",
"2651XM": "C2600-MB-2FE"}
def __init__(self, module, server, chassis="2610"):
Router.__init__(self, module, server, platform="c2600")
def __init__(self, module, server, project, chassis="2610"):
self._platform_settings = {"ram": 64,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 15,
"clock_divisor": 8}
super().__init__(module, server, project, platform="c2600")
c2600_settings = {"ram": 128,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 15,
"clock_divisor": 8}
# set the default adapter for slot 0
self._platform_settings["slot0"] = self.chassis_to_default_adapter[chassis]
c2600_settings["slot0"] = self.chassis_to_default_adapter[chassis]
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c2600_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C2691(Router):
"""
Dynamips c2691 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c2691")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
super().__init__(module, server, project, platform="c2691")
c2691_settings = {"ram": 192,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c2691_settings)
def __str__(self):

View File

@@ -23,33 +23,32 @@ from .router import Router
class C3600(Router):
"""
Dynamips c3600 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server, chassis="3640"):
Router.__init__(self, module, server, platform="c3600")
def __init__(self, module, server, project, chassis="3640"):
self._platform_settings = {"ram": 128,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 5,
"clock_divisor": 4}
super().__init__(module, server, project, platform="c3600")
c3600_settings = {"ram": 192,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 5,
"clock_divisor": 4}
# chassis 3660 has a default adapter
if chassis == "3660":
self._platform_settings["slot0"] = "Leopard-2FE"
c3600_settings["slot0"] = "Leopard-2FE"
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3600_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C3725(Router):
"""
Dynamips c3725 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3725")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
super().__init__(module, server, project, platform="c3725")
c3725_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3725_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C3745(Router):
"""
Dynamips c3745 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3745")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
super().__init__(module, server, project, platform="c3745")
c3745_settings = {"ram": 256,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3745_settings)
def __str__(self):

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