Compare commits

...

259 Commits

Author SHA1 Message Date
Julien Duponchelle
4e172fc7e3 2.0.2 2017-05-30 09:02:02 +02:00
Julien Duponchelle
8ed8a2c115 Show a default symbol in case of corrupted file
Fix https://github.com/GNS3/gns3-server/issues/1043
2017-05-24 12:26:18 +02:00
Julien Duponchelle
073665a75d When another gui is already running exit instead of proper close to avoid any issue
Fix #2059
2017-05-23 18:00:02 +02:00
Julien Duponchelle
4ccc67aa46 Fix duplicate on remote server use wrong location
Fix https://github.com/GNS3/gns3-server/issues/1040
2017-05-23 17:25:29 +02:00
Julien Duponchelle
6e2632e91f Display the location of settings when we disallow opening due to old release 2017-05-23 15:54:35 +02:00
Julien Duponchelle
38ddcde902 Improve search for dynamips in development on OSX 2017-05-23 14:24:54 +02:00
Julien Duponchelle
436563afcb Merge pull request #2079 from GNS3/pyup-update-pytest-3.0.7-to-3.1.0
Update pytest to 3.1.0
2017-05-23 08:29:55 +02:00
pyup-bot
7eaab3e38b Update pytest from 3.0.7 to 3.1.0 2017-05-23 02:25:48 +02:00
Julien Duponchelle
0927a2a8c9 Fix error display when loading a .png custom symbol 2017-05-22 13:38:04 +02:00
Julien Duponchelle
87e6159ff6 Fix a crash in the progress dialog
Fix #2064
2017-05-22 09:56:31 +02:00
Julien Duponchelle
effdcf5e24 Fix a race condition when exporting a closed project
Fix #2078
2017-05-22 09:54:17 +02:00
Julien Duponchelle
021cdd2e65 Fix RuntimeError: wrapped C/C++ object of type NodeItem has been deleted
Fix #2070
2017-05-19 17:39:34 +02:00
Julien Duponchelle
363c4a9966 2.0.2dev1 2017-05-16 09:15:18 +02:00
Julien Duponchelle
7082c75511 2.0.1 2017-05-16 08:48:56 +02:00
grossmj
16c4a837d7 Improve inline help. Fixes #1999.
Add a warning about wifi interfaces in the cloud. Fixes #1902.
2017-05-14 22:18:35 +02:00
grossmj
fd42ac410c Copy remote directory path into clipboard in "Show in FileManager". Fixes #1966. 2017-05-12 16:49:26 +08:00
Jeremy Grossmann
6efc177804 Merge pull request #2054 from GNS3/export_thread
Do not run import / export of project in seperate thread
2017-05-12 10:23:25 +08:00
Julien Duponchelle
6f9e6c9b92 Fix display of error in progress dialog when we don't have thread 2017-05-11 17:48:38 +02:00
Jeremy Grossmann
0411c68150 Merge pull request #2057 from GNS3/lost_slot_and_port
Fix lost slot and port in dynamips settings
2017-05-11 23:37:39 +08:00
Julien Duponchelle
4246e731e5 Fix lost slot and port in dynamips settings
When you reopen a project you no longer have the
wic and slot, until you move the node and retrieve an
update. The settings is just lost in the GUI but is fine
on server.

Fix #2053
2017-05-11 09:49:47 +02:00
Julien Duponchelle
50cca71279 Do not run import / export of project in seperate thread
This trigger warning because you need to do the HTTP request
to the API from the main thread.

Fix #2008
2017-05-10 18:13:45 +02:00
Julien Duponchelle
c78ef8f348 Assert when running an HTTP query outside the main thread
Ref #2008
2017-05-10 17:57:02 +02:00
Julien Duponchelle
c03a5a9e0a Proper error when you try to load the pid file as config file
Fix #2044
2017-05-09 17:27:38 +02:00
Julien Duponchelle
c93b7836d8 Log malformed svg text item
Fix #2045
2017-05-09 17:14:02 +02:00
Julien Duponchelle
690b22cc24 Fix a race condition when right click and delete a node at the same time
Fix #2043
2017-05-09 17:03:23 +02:00
Julien Duponchelle
3560251816 Fix a race condition when snapshoting a closed project
Fix #2046
2017-05-09 16:37:42 +02:00
Jeremy Grossmann
90e861289f Merge pull request #2016 from GNS3/missing_xattr
Catch missing function listxattr on some linux host
2017-05-08 21:04:47 +07:00
Jeremy Grossmann
6e144d6122 Update doctor_dialog.py 2017-05-08 21:04:01 +07:00
grossmj
2f168193d1 Catch remaining missing function listxattr on some Linux host. 2017-05-08 21:01:27 +07:00
Jeremy Grossmann
0fe5559564 Merge pull request #2007 from GNS3/fix_project_close
Fix project closing when we have multiple client connected
2017-05-08 20:42:35 +07:00
Julien Duponchelle
37f0744d7c Fix a race condition when creating node and closing project
Fix #2018
2017-05-05 19:04:06 +02:00
Julien Duponchelle
000f4a4790 Fix Bug with python before 3.4.3
Fix #2035
2017-05-05 18:58:40 +02:00
Julien Duponchelle
a09b7d6738 Fix error if you put a path in a .gns3a file for qemu
Fix #2038
2017-05-05 18:31:13 +02:00
Julien Duponchelle
a1fa8f9ec2 Fix AttributeError: 'NoneType' object has no attribute '_refreshVisibleWidgets'
Fix #2034
2017-05-05 18:29:36 +02:00
Jeremy Grossmann
cd6b0b793e Merge pull request #2009 from GNS3/dissalow_gns3_on_remote
Disallow opening a .gns3 on a remote server
2017-05-05 20:53:29 +07:00
Jeremy Grossmann
6453932421 Update main_window.py 2017-05-05 20:52:03 +07:00
Julien Duponchelle
37a23d9682 Do not crash if the logging code raise an exception
Fix #2017
2017-05-04 12:18:26 +02:00
Julien Duponchelle
e18e10c701 Fix some crash in dynamips device preference page
Fix #2029
2017-05-04 12:02:49 +02:00
Julien Duponchelle
a9240e2e46 Fix warning when loading IOU images on Windows
Fix #2012
2017-05-04 10:43:13 +02:00
Julien Duponchelle
4df0c33013 Do not crash if you don't have configure a packet capture program on Windows
Fix #2026
2017-05-04 10:38:35 +02:00
Julien Duponchelle
4096316ceb Ignore error when we can't kill the packet capture
The user probalby manually kill it.

Fix #2013
2017-05-04 10:30:41 +02:00
Julien Duponchelle
e09292c647 Fix AttributeError: 'NoneType' object has no attribute 'wasCanceled'
Fix #2023
2017-05-04 10:27:45 +02:00
Julien Duponchelle
31c37161fa Fix RuntimeError: wrapped C/C++ object of type QComboBox has been deleted
Fix #2027
2017-05-04 10:25:46 +02:00
Julien Duponchelle
a047cd7f4c Fix RuntimeError: wrapped C/C++ object of type QTreeWidgetItem has been deleted
Fix #2028
2017-05-04 10:13:33 +02:00
Julien Duponchelle
87cde665a8 Fix detection of https when use for the local server
Ref #995
2017-05-03 17:23:03 +02:00
Julien Duponchelle
6361c94bbe Silent the _COMPIZ_TOOLKIT_ACTION warning
This a Qt bug.

Fix #2020
2017-05-03 15:54:55 +02:00
Julien Duponchelle
8a0aeff0bb Cacth TypeError: native Qt signal is not callable
Fix #2011
2017-05-03 10:58:38 +02:00
Julien Duponchelle
508f8b3ad5 Fix AttributeError: 'C7200' object has no attribute 'warning_signal'
Fix #2014
2017-05-03 10:51:12 +02:00
Julien Duponchelle
bfe942c029 Catch missing function listxattr on some linux host
Fix #2010
2017-05-03 10:47:22 +02:00
Julien Duponchelle
b3a86594ff 2.0.1dev1 2017-05-03 10:19:25 +02:00
Julien Duponchelle
debe88bd37 2.0.0 2017-05-02 10:14:01 +02:00
Julien Duponchelle
a948fd07b1 Disallow opening a .gns3 on a remote server
This prevent opening a local .gns3 on a remote server.
Because this is not working you need to import the
project on the server using portable project.

Fix https://github.com/GNS3/gns3-server/issues/984
2017-05-02 09:27:40 +02:00
Julien Duponchelle
072f714e21 Fix project closing when we have multiple client connected
In all case when we close the main window we let the server
manage if he need to close or not the project.

Fix https://github.com/GNS3/gns3-server/issues/991
2017-05-02 09:07:14 +02:00
Julien Duponchelle
a2059d3e7c Clarify that we don't override vmware custom adapters 2017-04-27 10:06:52 +02:00
Julien Duponchelle
c4cc819d50 Strip space from path at project creation
Fix #1997
2017-04-21 14:49:52 +02:00
Julien Duponchelle
0796d9aae5 2.0.0dev13 2017-04-20 10:57:28 +02:00
Julien Duponchelle
c8148c1877 2.0.0rc4 2017-04-20 10:56:55 +02:00
Julien Duponchelle
d6021afa0f Catch all error during the generation of log messages.
This prevent error loop is something goes wrong.

Fix #1993
2017-04-20 09:26:57 +02:00
Julien Duponchelle
9018bdac07 Remove no longer working capturelog plugin for pytest 2017-04-19 09:41:56 +02:00
Julien Duponchelle
a48b898d92 Catch a rare node creation error
Fix #1991
2017-04-19 09:39:32 +02:00
Julien Duponchelle
949c1bbe37 Fix missing menu text at application startup
Fix #1990
2017-04-19 09:35:31 +02:00
Julien Duponchelle
0089edecc0 Run test on windows also 2017-04-18 16:48:45 +02:00
Julien Duponchelle
91da1d492b Fix a race condition in the drawing item
Fix #1987
2017-04-18 11:32:33 +02:00
Julien Duponchelle
0242f53c17 Catch system error when connecting to local server
Fix #1988
2017-04-18 11:29:42 +02:00
Julien Duponchelle
bab7a1016f Catch a rare error when killing the capture
Fix #1985
2017-04-14 10:39:43 +02:00
Julien Duponchelle
2ec91e1ef7 Improve pcap streaming speed
Fix #1982
2017-04-14 10:30:49 +02:00
Julien Duponchelle
38af30ec15 Merge branch 'master' into 2.0 2017-04-14 09:04:17 +02:00
Julien Duponchelle
3f8a0cb527 Merge master 2017-04-14 09:03:08 +02:00
Julien Duponchelle
fd1a86df03 1.5.5dev1 2017-04-14 09:00:57 +02:00
Julien Duponchelle
c954a16ead 1.5.4 2017-04-13 17:27:37 +02:00
Julien Duponchelle
9418b997eb Mac Qt wheel is not working in our build envirronement 2017-04-13 16:07:52 +02:00
Julien Duponchelle
9682cad4f4 Add security issues section 2017-04-13 12:37:34 +02:00
Julien Duponchelle
60ee9e1374 Limit ubridge permission to the admin group on OSX 2017-04-13 10:04:08 +02:00
Julien Duponchelle
dbd5b9366a Upgrade to 5.7.1 2017-04-13 10:01:40 +02:00
Julien Duponchelle
f63314d4d0 Fix version number 2017-04-13 09:56:17 +02:00
Julien Duponchelle
96eaec0100 Recent projects list bug
Fix #1976
2017-04-10 17:59:17 +02:00
Julien Duponchelle
5d554976e2 Fix a race condition in the preferences dialog
Fix #1981
2017-04-10 11:42:13 +02:00
Julien Duponchelle
d1d8390b73 Try to fix some windows Z issues
Fix #1955
2017-04-07 11:06:51 +02:00
Julien Duponchelle
5f3f6462c2 Catch a garbage collection issue in the right click on a link 2017-04-03 16:33:14 +02:00
Julien Duponchelle
821809bf6f Fix a compatibility issue with Python 3.4
Fix #1973
2017-03-31 19:30:32 +02:00
Julien Duponchelle
a712fab7a4 2.0.0dev12 2017-03-31 09:44:27 +02:00
Julien Duponchelle
4ab21ea9f9 2.0.0rc3 2017-03-31 09:41:48 +02:00
Julien Duponchelle
1a4902279b Check hibernation only if it's a remote main server
Fix #1971
2017-03-29 17:19:00 +02:00
Julien Duponchelle
35412171b9 Improve timeout handling
Ref #1959
2017-03-29 12:34:10 +02:00
Julien Duponchelle
73939847b9 Improve logging when we display a qt message box
Ref #1959
2017-03-29 12:12:22 +02:00
Julien Duponchelle
5acfc5bc56 Fix a problem with settings not pushed to the server 2017-03-28 16:22:17 +02:00
Julien Duponchelle
d67d0d146f Try to detect computer hibernation
I'm not really happy with the patch but it the most simple way
I found. If between two request we have more than 120 seconds
of differences we disconnect.

Fix #1968
2017-03-28 14:41:43 +02:00
Julien Duponchelle
23da6a4c31 Fix crash when we send some errors to the user console 2017-03-27 18:16:11 +02:00
Julien Duponchelle
fd5f999756 Use QtFile for managing file capture
This prevent application freeze when we run a capture.

Fix #1959
2017-03-27 14:19:07 +02:00
Julien Duponchelle
c05304b86f Allow to delete a profile from the profile select dialog
Fix #1961
2017-03-27 10:26:29 +02:00
Julien Duponchelle
6361980591 Filter hidden folder in the profil directory
Fix #1960
2017-03-23 12:02:32 +01:00
Julien Duponchelle
d5ea98ba2a Prevent user putting port in the remote host name
Fix #1948
2017-03-22 11:56:27 +01:00
Julien Duponchelle
3ca01384c9 Fix RuntimeError: wrapped C/C++ object of type EllipseItem has been deleted
Fix #1957
2017-03-21 18:19:47 +01:00
Julien Duponchelle
f35bb6a281 Fix a rare error in LinkItem
Fix #1950
2017-03-21 16:56:23 +01:00
Julien Duponchelle
0c4a7693e6 Fix Image field in nodes list is stale after changing an image
Fix #1956
2017-03-21 16:54:10 +01:00
Julien Duponchelle
20506525c5 Fix RuntimeError: Set changed size during iteration
Fix #1951
2017-03-21 15:13:49 +01:00
Julien Duponchelle
bb00a9f64c Better detection of remote server changes
Fix #1949
2017-03-21 14:38:20 +01:00
Julien Duponchelle
ef36792379 Add a notice about the fact you need to apply server settings
Fix #1952
2017-03-21 11:35:03 +01:00
Julien Duponchelle
873fd409bd Check python version only for setup.py install 2017-03-21 10:10:14 +01:00
Julien Duponchelle
03221e8ab7 Fix refresh issue with link in topology summary
Fix #1938
2017-03-20 18:24:16 +01:00
Julien Duponchelle
55f9836dc9 Remove duplicate code 2017-03-20 17:34:17 +01:00
Julien Duponchelle
3ab5144fe2 Catch appliance error when creating an appliance new version
Fix #1941
2017-03-20 17:10:49 +01:00
Julien Duponchelle
c91a22c9f8 If a node can't be deleted do not remove it
Fix #1933
2017-03-16 19:42:38 +01:00
Julien Duponchelle
a8ba909568 If something is wrong during packet capture do not disconnect us from the server
Fix #1520
2017-03-16 18:14:55 +01:00
Julien Duponchelle
d33e9ed833 If something is wrong during packet capture do not disconnect us from the server
Fix #1520
2017-03-16 14:37:32 +01:00
Julien Duponchelle
aa31af1dca Fix saving dynamips
Fix #1939
2017-03-16 14:33:16 +01:00
Julien Duponchelle
4d07a7391f Try to fix the hang dialog on some computers
Ref #1915
2017-03-16 12:19:09 +01:00
Julien Duponchelle
417395718d Fix a rare crash in progress dialog 2017-03-15 16:36:13 +01:00
Julien Duponchelle
212048c4d1 If we pass --profile skip the profile select dialog
Fix #1934
2017-03-15 16:09:35 +01:00
Julien Duponchelle
11c27063b4 Raise an error if the progress dialog is not created from the main thread
Ref #1915
2017-03-15 15:14:12 +01:00
Julien Duponchelle
b8696fa54e Log qt log to python log 2017-03-15 14:50:19 +01:00
Julien Duponchelle
ba9785f83f Revert "Rollback to Qt 5.7.1"
This reverts commit 5038a72610.
2017-03-15 14:46:57 +01:00
Julien Duponchelle
4ae742529c Fix image are not uploaded to remote main server
Fix #1926, #1927, #1925
2017-03-15 14:33:25 +01:00
Julien Duponchelle
92cc335708 Fix race condition when editing a project
Fix #1932
2017-03-15 14:30:00 +01:00
Julien Duponchelle
2099a4ae9a Poll settings each 5 seconds
Fix #1923, #1924
2017-03-15 12:55:14 +01:00
Julien Duponchelle
5038a72610 Rollback to Qt 5.7.1
Ref #1915
2017-03-15 11:51:22 +01:00
Julien Duponchelle
b9e320996b Merge pull request #1935 from GNS3/pyup-update-pytest-3.0.6-to-3.0.7
Update pytest to 3.0.7
2017-03-15 10:20:50 +01:00
pyup-bot
9c86cd71c8 Update pytest from 3.0.6 to 3.0.7 2017-03-14 23:53:42 +01:00
Julien Duponchelle
7fdf42b442 Avoid progress dialog not disapear
Fix #1915
2017-03-14 15:40:04 +01:00
Julien Duponchelle
61d86e919e Remove wrong mention about the fact super putty is include 2017-03-14 12:02:02 +01:00
Julien Duponchelle
bc71985ee3 Avoid a crash when an ios router don't have a chassis
Fix #1920
2017-03-13 17:25:58 +01:00
Julien Duponchelle
6bebf2d14d Fix a potentatial crash in the progress dialog
Fix #1919
2017-03-13 17:16:34 +01:00
Julien Duponchelle
490021aa47 Fix appliance file 2017-03-13 15:48:59 +01:00
Julien Duponchelle
c8db5e7e49 2.0.0 dev 11 2017-03-13 15:42:06 +01:00
Julien Duponchelle
8efaacbc3d Support official docker images in appliances 2017-03-13 15:41:14 +01:00
Julien Duponchelle
c5da24b954 2.0.0rc2 2017-03-10 20:40:10 +01:00
Julien Duponchelle
4456cfd68e Merge branch 'master' into 2.0 2017-03-10 20:39:03 +01:00
Julien Duponchelle
3468909db1 Deploy on pypi when we tag 2017-03-10 20:37:57 +01:00
Julien Duponchelle
212728eb94 Fix rare crash in GNS3 VM preference page
Fix #1912
2017-03-09 10:51:38 +01:00
Julien Duponchelle
cf4f73a7e1 Fix an error on Windows when loading SVG files
Fix #1913
2017-03-09 10:25:18 +01:00
Julien Duponchelle
5948e5c4cb Prevent a potential crash 2017-03-08 18:12:46 +01:00
Julien Duponchelle
791aa27158 Workaround a rare crash when sending analytics
It seem PyQT is confuse between the signal and error function.

Fix #1861
2017-03-08 14:52:47 +01:00
Julien Duponchelle
0b8ab56ffc Catch error when you try to create a node a not existing server
Fix #1905
2017-03-08 14:44:30 +01:00
Julien Duponchelle
12dcfda756 Fix an error when your local server crash and computer return non unicode
Fix #1904
2017-03-08 14:40:29 +01:00
Julien Duponchelle
ef44beac41 Fix KeyError: 'slot1'
Fix #1907
2017-03-08 09:54:17 +01:00
Julien Duponchelle
97a71904f7 Fix a rare crash in import appliance
Fix #1908
2017-03-08 09:53:27 +01:00
Julien Duponchelle
94f4059d67 Rollback to PyQT 5.8 because 5.8.1 seem to have trouble at install 2017-03-07 15:49:26 +01:00
Julien Duponchelle
14ed2546bc Merge pull request #1903 from GNS3/pyup-update-pyqt5-5.8-to-5.8.1
Update pyqt5 to 5.8.1
2017-03-07 13:25:40 +01:00
pyup-bot
908258c163 Update pyqt5 from 5.8 to 5.8.1 2017-03-07 12:09:10 +01:00
Julien Duponchelle
704191ba25 2.0.0 dev 10 2017-03-06 19:42:25 +01:00
Julien Duponchelle
be7fc9abe2 2.0.0rc1 2017-03-06 19:39:46 +01:00
Julien Duponchelle
e0daf1dbb1 Fix syntax error 2017-03-01 09:04:05 +01:00
Julien Duponchelle
a8877d4f8a UltraVNC support 2017-02-28 18:08:48 +01:00
Julien Duponchelle
2db850a3f3 Fix tests 2017-02-28 15:58:25 +01:00
Julien Duponchelle
ec7cdedb86 Display less noisy dialog when we can't connect to the remote server
Fix #1887
2017-02-28 14:06:52 +01:00
Julien Duponchelle
606fa15a55 Prevent the usage of gns3vm as a remote server name 2017-02-28 13:50:31 +01:00
Jeremy Grossmann
3eca9b0e54 Merge pull request #1881 from GNS3/catch_local_process_errors
Monitor and display local server stderr
2017-02-28 02:46:27 +08:00
Julien Duponchelle
3abaf74580 Fix the VMware wizard for not using a remote server by default
Fix #1893
2017-02-27 19:00:02 +01:00
Julien Duponchelle
9713748633 Prevent the GNS3 VM to appear in remote compute in the VM wizard
Fix #1894
2017-02-27 18:08:34 +01:00
Julien Duponchelle
405e86aff4 Remove iouyap settings
Fix #1892
2017-02-27 15:26:05 +01:00
Julien Duponchelle
4da824dc2b Fix missing permission error management
Fix #1888
2017-02-24 09:49:10 +01:00
Julien Duponchelle
0ec177c644 Avoid a crash when create a new dynamips version in the appliance wizard
Fix #1884
2017-02-23 15:13:00 +01:00
Julien Duponchelle
9ef4f86050 Disallow user to add the same server as a remote server and as local server
Fix #1883
2017-02-23 11:13:14 +01:00
Julien Duponchelle
96b830817e Fix 'module' object has no attribute 'run'
Fix #1878
2017-02-23 09:11:31 +01:00
Julien Duponchelle
0db8b00fb9 Monitor and display local server stderr
It's the best solution I found. Streaming in real time
the log will require to start an aditionnal thread

Fix #1880
2017-02-22 17:51:23 +01:00
Julien Duponchelle
005dde6c2b Fix some import errors 2017-02-22 17:23:58 +01:00
Julien Duponchelle
260f9b352e Remove placeholder string from appliance wizard 2017-02-22 11:02:25 +01:00
Julien Duponchelle
852b0bc498 Avoiding calling multiple time /computes at the same time. And reduce timeout
Fix #1848
2017-02-22 10:29:41 +01:00
Julien Duponchelle
3a503a5fc0 Support for appliance v4 2017-02-22 09:04:22 +01:00
Jeremy Grossmann
88b408695a Merge pull request #1875 from GNS3/hdpi_enable
Disable HDPI by default on Linux and allow to configure it
2017-02-20 20:39:19 -08:00
grossmj
776b45363b Some tweaks for enabling/disabling HDPI mode. 2017-02-21 12:33:09 +08:00
Julien Duponchelle
adb270b64c Do not display error at first step of the setup wizard
Fix #1827
2017-02-20 20:04:43 +01:00
Julien Duponchelle
5329b8fd72 Disable HDPI by default on Linux and allow to configure it
Fix #1870
2017-02-20 19:21:02 +01:00
Julien Duponchelle
d6379e4bb9 Fix an issue when you edit a VPCS node from the node view
Fix https://github.com/GNS3/gns3-gui/issues/1874
2017-02-20 18:09:48 +01:00
Julien Duponchelle
6ca18d5b29 Catch a race condition in managing error static assets download
Fix #1872
2017-02-20 15:11:44 +01:00
Julien Duponchelle
50c008ddec Handle error if you try to import an appliance without having the images
Fix #1871
2017-02-20 12:29:37 +01:00
Julien Duponchelle
4f56f100fa Improve crash proof code of the progress dialog
Fix #1873
2017-02-20 12:16:32 +01:00
Julien Duponchelle
0c16a5b0d1 2.0.0dev9 2017-02-16 11:27:49 +01:00
Julien Duponchelle
8f33ad3c70 2.0.0b4 2017-02-16 11:26:32 +01:00
Julien Duponchelle
7dca1b404c Fix a rare crash 2017-02-15 19:28:38 +01:00
Julien Duponchelle
99ff98ff47 Merge pull request #1858 from GNS3/pyup-update-pyqt5-5.7.1-to-5.8
Update pyqt5 to 5.8
2017-02-15 18:55:09 +01:00
pyup-bot
083d6e1298 Update pyqt5 from 5.7.1 to 5.8 2017-02-15 18:44:11 +01:00
Julien Duponchelle
71716c451e Drop from console view the show command not supported by 2.0
Fix #1855
2017-02-15 18:15:09 +01:00
Julien Duponchelle
3c4a244b75 Try to avoid segfault in some PyQT version
Fix #1856
2017-02-15 16:19:13 +01:00
Julien Duponchelle
85892a3bd6 Display git commit version in version number 2017-02-15 12:52:17 +01:00
Julien Duponchelle
1d3d721cf3 Support for strike and underline
Fix #1851
2017-02-14 17:45:48 +01:00
Julien Duponchelle
37a72af75f Do not use native font selector on mac it could crash 2017-02-14 17:01:21 +01:00
Julien Duponchelle
c93713b9e7 Use a dedicated QNetwork manager for notification
I hope this will solve issue for some users where the connection
return random error when they change the project.

Ref #1848
2017-02-14 15:15:29 +01:00
Julien Duponchelle
77b3118cbc Try to workaround unknow error on some user computers
Ref #1848
2017-02-14 12:27:34 +01:00
Julien Duponchelle
bc0cbfd040 Fix a display error in console error message 2017-02-14 11:37:20 +01:00
Julien Duponchelle
0cfc66b4d4 Use signal for writting on console to avoid some potential segfault 2017-02-14 11:23:41 +01:00
Julien Duponchelle
1b9b1cbe3c Fix tests about HTTP errors 2017-02-14 10:19:34 +01:00
Julien Duponchelle
407187c826 Fix a rare warning
Fix https://github.com/GNS3/gns3-server/issues/901
2017-02-14 09:53:12 +01:00
Julien Duponchelle
a6eb1e65ac Add more debug when we have an http error
Ref #1848
2017-02-13 19:15:19 +01:00
Julien Duponchelle
efbb19a862 Disable timeout on project open
Ref #1848
2017-02-13 17:39:13 +01:00
Julien Duponchelle
2a94816b58 Support for gvncviewer
Fix #1845
2017-02-13 15:26:12 +01:00
Julien Duponchelle
aab307a519 Fix a rare crash in the file editor dialog
Fix #1849
2017-02-13 15:22:03 +01:00
Julien Duponchelle
1279e16484 Fix a race condition when we display the error
Ref #1848
2017-02-13 13:08:38 +01:00
Julien Duponchelle
c2e20f9bd6 Fix an issue with invalid hostname detected as an IPV6 2017-02-10 18:13:48 +01:00
Julien Duponchelle
4acdaf6b5a When you update a a node from the node view send settings to controller 2017-02-10 15:51:54 +01:00
Julien Duponchelle
25313cbcde Fix error when permission on the loaded image is broken
Fix #1843
2017-02-08 11:19:41 +01:00
Julien Duponchelle
0de0eb12eb Fix tests 2017-02-07 17:36:21 +01:00
Julien Duponchelle
7d0fe52600 Fix crash with invalid image file in appliance wizard
Fix #1837
2017-02-06 17:27:30 +01:00
Julien Duponchelle
ba1d3b2423 Fix error when loading an handmade appliance file
Fix #1839
2017-02-06 17:10:30 +01:00
Julien Duponchelle
cae6afe85d Fix no error if your VNC client is not configured
Fix #1838
2017-02-06 16:59:06 +01:00
Julien Duponchelle
5865ac267b Avoid high cpu usage when connection is lost
Fix #1840
2017-02-06 11:27:36 +01:00
Julien Duponchelle
162993839c Support {name} in cloud template
Fix #1833
2017-02-03 15:15:47 +01:00
Julien Duponchelle
4359b490cc Fix text of the export dialog
Fix #1834
2017-02-03 14:47:07 +01:00
Julien Duponchelle
e4a8e67229 Fix error message when a project is already open
Fix #1832
2017-02-03 14:45:44 +01:00
Julien Duponchelle
6605270e64 Fix missing info in tooltip of ethernet switch
Fix  https://github.com/GNS3/gns3-gui/issues/1828
2017-01-31 19:28:03 +01:00
Julien Duponchelle
09e2bfeed0 The server manage the vmname when we update the linked virtual box VM
Ref https://github.com/GNS3/gns3-gui/issues/1821
2017-01-31 18:50:52 +01:00
Julien Duponchelle
b24733466d Fix z value for text
Fix #1822
2017-01-30 16:00:43 +01:00
Julien Duponchelle
c31be48f20 Avoid a segfault when display an error 2017-01-30 10:51:31 +01:00
Julien Duponchelle
a9ed27f42c Add sata options in the appliance schema
Fix #1817
2017-01-27 11:01:11 +01:00
Julien Duponchelle
1440caa532 Fix a rare crash when exporting IOU configurations
Fix #1800
2017-01-25 13:55:19 +01:00
Julien Duponchelle
513eb21940 Allow additionnal properties in registry files 2017-01-25 12:14:36 +01:00
Julien Duponchelle
de348d39da Fix a potential crash when a symbol is not found 2017-01-25 12:04:40 +01:00
Julien Duponchelle
0a3697962b Strip unused code for OVA support in the registry 2017-01-24 12:14:25 +01:00
Julien Duponchelle
e0edcf3d23 Increase the timeout for killing local server
Fix #1813
2017-01-24 11:20:50 +01:00
Julien Duponchelle
57dbff6a8e Merge pull request #1804 from GNS3/pyup-update-pytest-3.0.5-to-3.0.6
Update pytest to 3.0.6
2017-01-24 09:57:13 +01:00
Julien Duponchelle
0149bc90f2 Fix error when changing the layer of a drawing item
Fix #1810
2017-01-23 17:30:55 +01:00
Julien Duponchelle
f7292deb0f Fix double click for open file on OSX
Fix #1808
2017-01-23 17:22:17 +01:00
Julien Duponchelle
be01448c36 Add debug to see the arguments use to start the application
Ref #1808
2017-01-23 13:58:19 +01:00
Julien Duponchelle
484617ce25 Put the selected engine in the first position of the listbox
This avoid trigerring unexpected signals

Fix #1803
2017-01-23 11:31:55 +01:00
Julien Duponchelle
8ec53a6004 Fix rare crash with dynamips
Fix #1806
2017-01-23 10:15:29 +01:00
Julien Duponchelle
cfd1bbd9d1 Fix rare crash in the progress dialog
Fix #1802
2017-01-23 10:13:43 +01:00
Julien Duponchelle
25ae214b6b Fix a rare crash in console view
Fix #1807
2017-01-23 10:11:36 +01:00
Julien Duponchelle
456160beb1 Fix rare crash
Fix #1805
2017-01-23 10:09:49 +01:00
Julien Duponchelle
9affe2d9f4 Fix crash when you drag a file inside GNS3
Fix #1798
2017-01-23 10:08:03 +01:00
pyup-bot
3ed2f89b3b Update pytest from 3.0.5 to 3.0.6 2017-01-22 23:58:47 +01:00
Julien Duponchelle
9bb353fdbd 2.0.0dev8 2017-01-19 11:23:19 +01:00
Julien Duponchelle
c414ea28e4 2.0.0b3 2017-01-19 11:20:31 +01:00
Julien Duponchelle
cb0253f7cb Fix error if you already have an image with a different name on remote server
Fix #1794
2017-01-18 17:13:00 +01:00
Julien Duponchelle
f28663c626 Ask pyup to not monitor pywin32 2017-01-18 09:32:39 +01:00
Julien Duponchelle
3ef018f90e Allow any pywin32 version 2017-01-17 10:59:25 +01:00
Julien Duponchelle
e5ae7f77fa Drop gns3 converter from requirements 2017-01-17 10:58:36 +01:00
Julien Duponchelle
b6bac0cd3b Show correct server name in tooltip
Fix #1783
2017-01-16 20:46:49 +01:00
Julien Duponchelle
6a8e435210 Menu item to open controller webpage
Fix #1784
2017-01-16 17:43:06 +01:00
grossmj
442318af49 Fixes potential exception when adding network module to an IOS router. Fixes #1774. 2017-01-16 13:53:25 +08:00
Julien Duponchelle
ad93b46c94 Merge pull request #1776 from GNS3/pyup-update-pypiwin32-219-to-220
Update pypiwin32 to 220
2017-01-13 09:13:40 +01:00
pyup-bot
f7a9cc09ea Update pypiwin32 from 219 to 220 2017-01-12 18:47:41 +01:00
Julien Duponchelle
0d5507cf3d Merge branch '2.0' of github.com:GNS3/gns3-gui into 2.0 2017-01-12 08:55:17 +01:00
Julien Duponchelle
f885e33cbd Merge branch 'master' into 2.0 2017-01-12 08:53:57 +01:00
Julien Duponchelle
4813a8681f 1.5.4dev1 2017-01-12 08:51:46 +01:00
Julien Duponchelle
829f750c76 1.5.3 2017-01-12 08:14:42 +01:00
Julien Duponchelle
08b9f4a6d2 Merge pull request #1772 from GNS3/sata-qemu
Sata disk interface support for Qemu VMs.
2017-01-11 16:53:58 +01:00
Julien Duponchelle
7fdf022b36 Do not export a file config file if empty 2017-01-10 15:56:17 +01:00
Julien Duponchelle
f12935076e Allow to set console type in qemu wizard
/bin/bash: q: command not found
2017-01-09 13:02:12 +01:00
Julien Duponchelle
7a472d1574 Fix overwrite of projects
Fix #1743
2017-01-09 12:35:36 +01:00
Julien Duponchelle
4a82dc8705 Fix creation of new appliance version when filename is different
Fix #1755
2017-01-09 12:08:09 +01:00
Julien Duponchelle
52acaadfce Fix you can't configure port 0 on ethernet switch
Fix #1765
2017-01-09 10:23:06 +01:00
Julien Duponchelle
ef1967ff00 Fix a race condition when saving as a project and closing it
Fix #1770
2017-01-09 10:17:03 +01:00
Julien Duponchelle
6584f3b2d4 Reorder multi link when you delete one
Fix #1750
2017-01-06 11:42:26 +01:00
Julien Duponchelle
8ad55290b1 Ensure we can't connect to occupy port
Fix https://github.com/GNS3/gns3-gui/issues/1759
2017-01-06 10:30:24 +01:00
Julien Duponchelle
8ff34d63c0 Fix AttributeError: 'QImageSvgRenderer' object has no attribute '_svg'
Fix #1760
2017-01-06 09:12:13 +01:00
Julien Duponchelle
da88947028 Fix Unsaved preferences in GNS3 VM warning 2017-01-05 17:40:05 +01:00
Julien Duponchelle
854dc5db71 Fix Unsaved preferences in GNS3 VM warning
Fix #1740
2017-01-05 15:50:41 +01:00
Julien Duponchelle
e8ac144011 Merge pull request #1753 from GNS3/pyup-update-pyqt5-5.7-to-5.7.1
Update pyqt5 to 5.7.1
2017-01-05 08:22:10 +01:00
grossmj
8b20f4d568 Force margins in configuration tabs. 2017-01-05 14:52:39 +08:00
grossmj
cb88a4f6d9 Sata disk interface support for Qemu VMs. 2017-01-05 11:24:49 +08:00
grossmj
270f12dc1e Remove "sata" disk interface. Does not exist in Qemu. Ref #1749 2017-01-02 15:02:19 +08:00
grossmj
46ff586055 Add SATA and none disk interfaces on Qemu VM configuration page. Fixes #1749. 2017-01-01 23:25:31 +08:00
pyup-bot
78b999be57 Update pyqt5 from 5.7 to 5.7.1 2016-12-29 12:06:02 +01:00
Julien Duponchelle
4f702d9339 Fix TypeError: argument of type 'NoneType' is not iterable
Fix #1733
2016-12-21 14:34:58 +01:00
Julien Duponchelle
6f210c0e91 2.0.0 dev7 2016-12-21 09:38:50 +01:00
Julien Duponchelle
a677cff0a2 Fix an error when you edit readme and no projet is opened
Fix #1732
2016-12-21 09:37:23 +01:00
Julien Duponchelle
5b1050e427 1.5.3 dev2 2016-12-20 11:48:07 +01:00
Julien Duponchelle
271e987972 Upgrade Qt 5.7 2016-12-20 11:40:32 +01:00
Julien Duponchelle
1df34148b2 1.5.3 rc1 2016-12-20 09:32:38 +01:00
145 changed files with 135069 additions and 134617 deletions

View File

@@ -1,19 +1,16 @@
sudo: required
services:
- docker
- docker
notifications:
email: false
#email:
# - julien@gns3.net
#irc:
# channels:
# - "chat.freenode.net#gns3"
# on_success: change
# on_failure: always
script:
- docker build -t gns3-gui-test .
- docker run gns3-gui-test
- docker run gns3-gui-test
deploy:
provider: pypi
user: noplay
password:
secure: FofcqlJjgqf2jaDaXpLHeigVoexbrOz3WwnDuiJpwJxeFUlPY8s2cQs/Bm+dzxzZaOaGiVE0A83v/Xa10yD5tflThHt4sqYJK3iQCinA7wgeAlDimB4xrWUNplfNJZ/Eod5Ssa++E02W+3i29PxpXY//mjCY7qDxaoxul1gnFJY=
on:
tags: true
repo: GNS3/gns3-gui

239
CHANGELOG
View File

@@ -1,5 +1,225 @@
# Change Log
## 2.0.2 30/05/2017
* Show a default symbol in case of corrupted file
* When another gui is already running exit instead of proper close to avoid any issue
* Fix duplicate on remote server use wrong location
* Display the location of settings when we disallow opening due to old release
* Improve search for dynamips in development on OSX
* Fix error display when loading a .png custom symbol
* Fix a crash in the progress dialog
* Fix a race condition when exporting a closed project
* Fix RuntimeError: wrapped C/C++ object of type NodeItem has been deleted
## 2.0.1 16/05/2017
* Improve inline help. Fixes #1999. Add a warning about wifi interfaces in the cloud. Fixes #1902.
* Copy remote directory path into clipboard in "Show in FileManager". Fixes #1966.
* Fix display of error in progress dialog when we don't have thread
* Fix lost slot and port in dynamips settings
* Do not run import / export of project in seperate thread
* Assert when running an HTTP query outside the main thread
* Proper error when you try to load the pid file as config file
* Log malformed svg text item
* Fix a race condition when right click and delete a node at the same time
* Fix a race condition when snapshoting a closed project
* Update doctor_dialog.py
* Catch remaining missing function listxattr on some Linux host.
* Fix a race condition when creating node and closing project
* Fix error if you put a path in a .gns3a file for qemu
* Fix AttributeError: 'NoneType' object has no attribute '_refreshVisibleWidgets'
* Do not crash if the logging code raise an exception
* Fix some crash in dynamips device preference page
* Fix warning when loading IOU images on Windows
* Do not crash if you don't have configure a packet capture program on Windows
* Ignore error when we can't kill the packet capture
* Fix AttributeError: 'NoneType' object has no attribute 'wasCanceled'
* Fix RuntimeError: wrapped C/C++ object of type QComboBox has been deleted
* Fix RuntimeError: wrapped C/C++ object of type QTreeWidgetItem has been deleted
* Fix detection of https when use for the local server
* Silent the _COMPIZ_TOOLKIT_ACTION warning
* Cacth TypeError: native Qt signal is not callable
* Fix AttributeError: 'C7200' object has no attribute 'warning_signal'
* Catch missing function listxattr on some linux host
* Disallow opening a .gns3 on a remote server
* Fix project closing when we have multiple client connected
## 2.0.0 02/05/2017
* Clarify that we don't override vmware custom adapters
* Strip space from path at project creation
## 2.0.0rc4 20/04/2017
* Catch all error during the generation of log messages.
* Catch a rare node creation error
* Fix missing menu text at application startup
* Fix a race condition in the drawing item
* Catch system error when connecting to local server
* Catch a rare error when killing the capture
* Improve pcap streaming speed
* Upgrade to 5.7.1
* Recent projects list bug
* Fix a race condition in the preferences dialog
* Try to fix some windows Z issues
* Catch a garbage collection issue in the right click on a link
* Fix a compatibility issue with Python 3.4
## 1.5.4 13/04/2017
* Limit ubridge permission to the admin group on OSX
* Upgrade to Qt 5.7.1 on Windows
## 2.0.0rc3 31/03/2017
* Improve timeout handling
* Improve logging when we display a qt message box
* Try to detect computer hibernation
* Fix crash when we send some errors to the user console
* Use QtFile for managing file capture
* Allow to delete a profile from the profile select dialog
* Filter hidden folder in the profil directory
* Prevent user putting port in the remote host name
* Fix RuntimeError: wrapped C/C++ object of type EllipseItem has been deleted
* Fix a rare error in LinkItem
* Fix Image field in nodes list is stale after changing an image
* Fix RuntimeError: Set changed size during iteration
* Better detection of remote server changes
* Add a notice about the fact you need to apply server settings
* Check python version only for setup.py install
* Catch appliance error when creating an appliance new version
* If a node can't be deleted do not remove it
* If something is wrong during packet capture do not disconnect us from the server
* Fix saving dynamips
* Try to fix the hang dialog on some computers
* Fix a rare crash in progress dialog
* If we pass --profile skip the profile select dialog
* Raise an error if the progress dialog is not created from the main thread
* Log qt log to python log
* Fix image are not uploaded to remote main server
* Fix race condition when editing a project
* Poll settings each 5 seconds
* Avoid progress dialog not disapear
* Remove wrong mention about the fact super putty is include
* Avoid a crash when an ios router don't have a chassis
* Fix a potentatial crash in the progress dialog
* Support official docker images in appliances
## 2.0.0rc2 10/03/2017
* Deploy on pypi when we tag
* Fix rare crash in GNS3 VM preference page
* Fix an error on Windows when loading SVG files
* Prevent a potential crash
* Workaround a rare crash when sending analytics
* Catch error when you try to create a node a not existing server
* Fix an error when your local server crash and computer return non unicode
* Fix KeyError: 'slot1'
* Fix a rare crash in import appliance
* Rollback to PyQT 5.8 because 5.8.1 seem to have trouble at install
* Update pyqt5 from 5.8 to 5.8.1
## 2.0.0 RC 1 06/03/2017
* UltraVNC support
* Display less noisy dialog when we can't connect to the remote server
* Prevent the usage of gns3vm as a remote server name
* Fix the VMware wizard for not using a remote server by default
* Prevent the GNS3 VM to appear in remote compute in the VM wizard
* Remove iouyap settings
* Fix missing permission error management
* Avoid a crash when create a new dynamips version in the appliance wizard
* Disallow user to add the same server as a remote server and as local server
* Fix 'module' object has no attribute 'run'
* Monitor and display local server stderr
* Fix some import errors
* Remove placeholder string from appliance wizard
* Avoiding calling multiple time /computes at the same time. And reduce timeout
* Support for appliance v4
* Some tweaks for enabling/disabling HDPI mode.
* Do not display error at first step of the setup wizard
* Disable HDPI by default on Linux and allow to configure it
* Fix an issue when you edit a VPCS node from the node view
* Catch a race condition in managing error static assets download
* Handle error if you try to import an appliance without having the images
* Improve crash proof code of the progress dialog
## 2.0.0 beta 4 19/01/2017
* Update pyqt5 from 5.7.1 to 5.8
* Drop from console view the show command not supported by 2.0
* Try to avoid segfault in some PyQT version
* Support for strike and underline
* Do not use native font selector on mac it could crash
* Use a dedicated QNetwork manager for notification
* Fix a display error in console error message
* Use signal for writting on console to avoid some potential segfault
* Fix a rare warning
* Add more debug when we have an http error
* Disable timeout on project open
* Support for gvncviewer
* Fix a rare crash in the file editor dialog
* Fix a race condition when we display the error
* Fix an issue with invalid hostname detected as an IPV6
* When you update a a node from the node view send settings to controller
* Fix error when permission on the loaded image is broken
* Fix crash with invalid image file in appliance wizard
* Fix error when loading an handmade appliance file
* Fix no error if your VNC client is not configured
* Avoid high cpu usage when connection is lost
* Support {name} in cloud template
* Fix text of the export dialog
* Fix error message when a project is already open
* Fix missing info in tooltip of ethernet switch
* The server manage the vmname when we update the linked virtual box VM
* Fix z value for text
* Avoid a segfault when display an error
* Add sata options in the appliance schema
* Fix a rare crash when exporting IOU configurations
* Allow additionnal properties in registry files
* Fix a potential crash when a symbol is not found
* Strip unused code for OVA support in the registry
* Increase the timeout for killing local server
* Fix error when changing the layer of a drawing item
* Fix double click for open file on OSX
* Add debug to see the arguments use to start the application
* Put the selected engine in the first position of the listbox
* Fix rare crash with dynamips
* Fix rare crash in the progress dialog
* Fix a rare crash in console view
* Fix crash when you drag a file inside GNS3
## 2.0.0 beta 3 19/01/2017
* Fix error if you already have an image with a different name on remote server
* Drop gns3 converter from requirements
* Show correct server name in tooltip
* Menu item to open controller webpage
* Fixes potential exception when adding network module to an IOS router. Fixes #1774.
* Do not export a file config file if empty
* Allow to set console type in qemu wizard
* Fix overwrite of projects
* Fix creation of new appliance version when filename is different
* Fix you can't configure port 0 on ethernet switch
* Fix a race condition when saving as a project and closing it
* Reorder multi link when you delete one
* Ensure we can't connect to occupy port
* Fix AttributeError: 'QImageSvgRenderer' object has no attribute '_svg'
* Fix Unsaved preferences in GNS3 VM warning
* Force margins in configuration tabs.
* Sata disk interface support for Qemu VMs.
* Remove "sata" disk interface. Does not exist in Qemu. Ref #1749
* Add SATA and none disk interfaces on Qemu VM configuration page. Fixes #1749.
* Update pyqt5 from 5.7 to 5.7.1
* Fix TypeError: argument of type 'NoneType' is not iterable
* Fix an error when you edit readme and no projet is opened
* Upgrade Qt 5.7
## 1.5.3 12/01/2017
* Upgrade Qt 5.7
## 2.0.0 beta 2 20/12/2016
* AUX console button text change in MainWindow.
@@ -201,6 +421,25 @@
* Edit the scene size
* New API
## 1.5.3 rc1 20/12/2016
* Fix Error when editing IOS image created using .gns3a file
* Fix error when opening a project from the cli with a gns3 installed via setup.py
* Fix a crash at startup on Mac when coming from old GNS3 version
* Fix an error during import of some 0.8x projects
* Ask for restart after installing vmrun
* Improve warning when connection issue to GNS3 VM
* Changes wording in VM wizards.
* Changed sentence.
* Display an error if settings come from a more recent version of GNS3
* Fix Error when no GNS3 VM is configured and you click on new Docker or IOU
* Disallow / in docker container name
* Update iTerm3 console settings
* Fix rename ethernet switch doesn't release the name
* Support for VNC display number in command line replacement
* Fix a crash when a directory with image is not accessible at gns3a import
## 1.5.2 18/08/2016
* Make more clear that VMware VM are not ESXi

View File

@@ -40,3 +40,8 @@ Or start the app with --debug flag.
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
https://github.com/Kozea/wdb
Security issues
----------------
Please contact us using contact informations available here:
http://docs.gns3.com/1ON9JBXSeR7Nt2-Qum2o3ZX0GU86BZwlmNSUgvmqNWGY/index.html

19
appveyor.yml Normal file
View File

@@ -0,0 +1,19 @@
version: '{build}-{branch}'
image: Visual Studio 2015
platform: x64
environment:
PYTHON: "C:\\Python36-x64"
DISTUTILS_USE_SDK: "1"
install:
- cinst nmap
- "%PYTHON%\\python.exe -m pip install -r dev-requirements.txt"
- "%PYTHON%\\python.exe -m pip install -r win-requirements.txt"
build: off
test_script:
- "%PYTHON%\\python.exe -m pytest -v"

View File

@@ -1,7 +1,6 @@
-rrequirements.txt
pep8==1.7.0
pytest==3.0.5
pytest==3.1.0
pytest-pythonpath==0.7.1 # useful for running tests outside tox
pytest-timeout==1.2.0
pytest-capturelog==0.7

View File

@@ -29,14 +29,20 @@ log = logging.getLogger(__name__)
class Application(QtWidgets.QApplication):
file_open_signal = QtCore.pyqtSignal(str)
def __init__(self, argv):
def __init__(self, argv, hdpi=True):
self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
# both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
# only available starting Qt version 5.6
self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
if hdpi:
if sys.platform.startswith("linux"):
log.warning("HDPI mode is enabled. HDPI support on Linux is not fully stable and GNS3 may crash depending of your version of Linux. To disabled HDPI mode please edit ~/.config/GNS3/gns3_gui.conf and set 'hdpi' to 'false'")
self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
else:
log.info("HDPI mode is disabled")
self.setAttribute(QtCore.Qt.AA_DisableHighDpiScaling)
super().__init__(argv)

View File

@@ -46,7 +46,6 @@ class BaseNode(QtCore.QObject):
loaded_signal = QtCore.Signal()
deleted_signal = QtCore.Signal()
error_signal = QtCore.Signal(int, str)
warning_signal = QtCore.Signal(int, str)
server_error_signal = QtCore.Signal(int, str)
_instance_count = 1

View File

@@ -48,17 +48,22 @@ class ComputeManager(QtCore.QObject):
self._timer = QtCore.QTimer()
self._timer.setInterval(1000)
self._refreshingComputes = False
self._timer.timeout.connect(self._refreshComputesSlot)
self._timer.start()
def _refreshComputesSlot(self):
if self._refreshingComputes:
return
if self._controller.connected() and datetime.datetime.now().timestamp() - self._last_computes_refresh > 5:
self._last_computes_refresh = datetime.datetime.now().timestamp()
self._controller.get("/computes", self._listComputesCallback, showProgress=True)
self._refreshingComputes = True
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=15)
def _controllerConnectedSlot(self):
if self._controller.connected():
self._controller.get("/computes", self._listComputesCallback)
self._refreshingComputes = True
self._controller.get("/computes", self._listComputesCallback, showProgress=False, timeout=15)
def _controllerDisconnectedSlot(self):
for compute_id in list(self._computes):
@@ -66,6 +71,7 @@ class ComputeManager(QtCore.QObject):
self.deleted_signal.emit(compute_id)
def _listComputesCallback(self, result, error=False, **kwargs):
self._refreshingComputes = False
if error is True:
log.error("Error while getting compute list: {}".format(result["message"]))
return

View File

@@ -249,44 +249,6 @@ class ConsoleCmd(cmd.Cmd):
print("{}: no such device".format(node_name))
continue
def _show_run(self, params):
"""
Handles the 'show run' command.
: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
print(json.dumps(topology, sort_keys=True, indent=4))
elif len(params) >= 2:
# this is a 'show run <device_name>'
params.pop(0)
for param in params:
node_name = param
base_node_id = None
# get the node ID
for node in self._topology.nodes():
if node.name() == node_name:
base_node_id = node.id()
break
if base_node_id is None:
print("{}: no such device".format(node_name))
continue
if "nodes" in topology["topology"]:
for node in topology["topology"]["nodes"]:
if node["id"] == base_node_id:
print(json.dumps(node, sort_keys=True, indent=4))
break
def do_show(self, args):
"""
Show detail information about every device in current lab:
@@ -294,15 +256,6 @@ class ConsoleCmd(cmd.Cmd):
Show detail information about a device:
show device <device_name>
Show the whole topology:
show run
Show topology info of a device:
show run <device_name>
Show the GNS3 VM status
show gns3vm
"""
if '?' in args or args.strip() == "":
@@ -312,10 +265,6 @@ class ConsoleCmd(cmd.Cmd):
params = args.split()
if params[0] == "device":
self._show_device(params)
elif params[0] == "run":
self._show_run(params)
elif params[0] == "gns3vm":
self._show_gnsvm(params)
else:
print(self.do_show.__doc__)

View File

@@ -15,11 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import platform
import sys
import sip
import struct
import inspect
import datetime
import platform
from .qt import QtCore, Qt
from .topology import Topology
@@ -39,6 +40,9 @@ class ConsoleLogHandler(logging.StreamHandler):
"""
def emit(self, record):
if sip.isdeleted(self._console_view):
return
message = self.format(record)
level_no = record.levelno
if level_no >= logging.ERROR:
@@ -187,11 +191,9 @@ class ConsoleView(PyCutExt, ConsoleCmd):
"""
text = "Server notification: {}".format(message)
self.write(text, error=True)
self.write("\n")
if details:
self.write(details)
self.write("\n")
text += "\n" + details
self.write_message_signal.emit(text, "info")
def writeError(self, base_node_id, message):
"""
@@ -208,8 +210,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
text = "Error:{name} {message}".format(name=name,
message=message)
self.write(text, error=True)
self.write("\n")
self.write_message_signal.emit(text, "error")
def writeWarning(self, base_node_id, message):
"""
@@ -226,8 +227,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
text = "Warning:{name} {message}".format(name=name,
message=message)
self.write(text, warning=True)
self.write("\n")
self.write_message_signal.emit(text, "warning")
def writeServerError(self, base_node_id, message):
"""
@@ -248,8 +248,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
text = "Server error {server}:{name} {message}".format(server=server,
name=name,
message=message)
self.write(text.strip(), error=True)
self.write("\n")
self.write_message_signal.emit(text.strip(), "error")
def _run(self):
"""

View File

@@ -46,6 +46,7 @@ class Controller(QtCore.QObject):
# If it's the first error we display an alert box to the user
self._first_error = True
self._error_dialog = None
self._display_error = True
self._projects = []
# If we do multiple call in order to download the same symbol we queue them
@@ -85,10 +86,19 @@ class Controller(QtCore.QObject):
"""
self._http_client = http_client
if self._http_client:
if self.isRemote():
self._http_client.setMaxTimeDifferenceBetweenQueries(120)
self._http_client.connection_connected_signal.connect(self._httpClientConnectedSlot)
self._http_client.connection_disconnected_signal.connect(self._httpClientDisconnectedSlot)
self._connectingToServer()
def setDisplayError(self, val):
"""
Allow error to be visible or not
"""
self._display_error = val
self._first_error = True
def _connectingToServer(self):
"""
Connection process as started
@@ -111,15 +121,15 @@ class Controller(QtCore.QObject):
if self._first_error:
self._connecting = False
self.connection_failed_signal.emit()
if "message" in result:
if "message" in result and self._display_error:
self._error_dialog = QtWidgets.QMessageBox(self.parent())
self._error_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
self._error_dialog.setWindowTitle("Connection to server")
self._error_dialog.setText("Error when connecting to the GNS3 server:\n{}".format(result["message"]))
self._error_dialog.setIcon(QtWidgets.QMessageBox.Critical)
self._error_dialog.show()
# Try to connect again in 1 seconds
QtCore.QTimer.singleShot(1000, qpartial(self.get, '/version', self._versionGetSlot, showProgress=self._first_error))
# Try to connect again in x seconds
QtCore.QTimer.singleShot(5000, qpartial(self.get, '/version', self._versionGetSlot, showProgress=self._first_error))
self._first_error = False
else:
self._first_error = True
@@ -227,7 +237,8 @@ class Controller(QtCore.QObject):
def _getStaticCallback(self, url, path, result, error=False, raw_body=None, **kwargs):
if error:
log.error("Error while downloading file: {}".format(url))
self._static_asset_download_queue = {}
if path in self._static_asset_download_queue:
del self._static_asset_download_queue[path]
return
try:
with open(path, "wb+") as f:
@@ -254,6 +265,20 @@ class Controller(QtCore.QObject):
icon.addFile(path)
callback(icon)
def deleteProject(self, project_id, callback=None):
Controller.instance().delete("/projects/{}".format(project_id), qpartial(self._deleteProjectCallback, callback=callback, project_id=project_id))
def _deleteProjectCallback(self, result, error=False, project_id=None, callback=None, **kwargs):
if error:
log.error("Error while deleting project: {}".format(result["message"]))
else:
self.refreshProjectList()
self._projects = [p for p in self._projects if p["project_id"] != project_id]
if callback:
callback(result, error=error, **kwargs)
@qslot
def refreshProjectList(self, *args):
self.get("/projects", self._projectListCallback)

View File

@@ -51,7 +51,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://fe92a87122904a05a846470c3f2557ff:d59b5e4980c8481d8c9fb90cf3516c7d@sentry.io/38506"
DSN = "sync+https://063691a489374eda912ad454a1d80777:5ddb34d6b23c4a08b040efce23aaac78@sentry.io/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):

View File

@@ -16,14 +16,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import sip
from ..qt import QtWidgets, QtCore, QtGui, qpartial, qslot
from ..ui.appliance_wizard_ui import Ui_ApplianceWizard
from ..image_manager import ImageManager
from ..modules import Qemu
from ..registry.appliance import Appliance
from ..registry.appliance import Appliance, ApplianceError
from ..registry.registry import Registry
from ..registry.config import Config, ConfigException
from ..registry.image import Image
@@ -367,7 +365,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
new_version, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Creating a new version allows to import unknown files to use with this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal)
if ok:
self._appliance.create_new_version(new_version)
try:
self._appliance.create_new_version(new_version)
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Create new version", str(e))
return
self.images_changed_signal.emit()
@qslot
@@ -380,18 +382,22 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
current = self.uiApplianceVersionTreeWidget.currentItem()
if not current:
return
disk = current.data(1, QtCore.Qt.UserRole)
path, _ = QtWidgets.QFileDialog.getOpenFileName()
if len(path) == 0:
return
image = Image(self._appliance.emulator(), path)
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct file. The MD5 sum is {} and should be {}.".format(image.md5sum, disk["md5sum"]))
image = Image(self._appliance.emulator(), path, filename=disk["filename"])
try:
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct file. The MD5 sum is {} and should be {}.".format(image.md5sum, disk["md5sum"]))
return
except OSError as e:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "Can't access to the image file {}: {}.".format(path, str(e)))
return
config = Config()
image.upload(self._compute_id, callback=self._imageUploadedCallback)
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
@@ -434,7 +440,11 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if version is None:
appliance_configuration = self._appliance.copy()
else:
appliance_configuration = self._appliance.search_images_for_version(version)
try:
appliance_configuration = self._appliance.search_images_for_version(version)
except ApplianceError as e:
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
return False
while len(appliance_configuration["name"]) == 0 or not config.is_name_available(appliance_configuration["name"]):
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "The name \"{}\" is already used by another appliance".format(appliance_configuration["name"]))

View File

@@ -49,6 +49,10 @@ class ConfigurationDialog(QtWidgets.QDialog, Ui_configurationDialog):
self._settings = settings
self._configuration_page = configuration_page
@property
def settings(self):
return self._settings
def on_uiButtonBox_clicked(self, button):
"""
Slot called when a button of the uiButtonBox is clicked.

View File

@@ -138,14 +138,18 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
request_setuid = False
if sys.platform.startswith("linux"):
if "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return(2, "Ubridge require CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
else:
# capabilities not supported
request_setuid = True
try:
if "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Ubridge requires CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
else:
# capabilities not supported
request_setuid = True
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
return (1, "Could not determine if CAP_NET_RAW capability is set for uBridge (Python bug)".format(path=path))
if sys.platform.startswith("darwin") or request_setuid:
if os.stat(path).st_uid != 0 or not os.stat(path).st_mode & stat.S_ISUID:
@@ -164,11 +168,15 @@ class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
if not os.path.exists(path):
return (2, "Dynamips path {path} doesn't exists".format(path=path))
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Dynamips require CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
try:
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
caps = os.getxattr(path, "security.capability")
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
return (2, "Dynamips requires CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
return (1, "Could not determine if CAP_NET_RAW capability is set for Dynamips (Python bug)".format(path=path))
return (0, None)
def checkGNS3InstalledTwice(self):

View File

@@ -89,6 +89,9 @@ class EditComputeDialog(QtWidgets.QDialog, Ui_EditComputeDialog):
if not re.match(r"^[a-zA-Z0-9\.{}-]+$".format("\u0370-\u1CDF\u2C00-\u30FF\u4E00-\u9FBF"), host):
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server hostname {}".format(host))
return
if name == "gns3vm":
QtWidgets.QMessageBox.critical(self, "Remote compute", "{} is a reserved name".format(name))
return
if len(name) == 0:
QtWidgets.QMessageBox.critical(self, "Remote compute", "Invalid remote server name {}".format(name))
return

View File

@@ -15,9 +15,6 @@
# 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 QtWidgets
from ..topology import Topology
from ..ui.edit_project_dialog_ui import Ui_EditProjectDialog

View File

@@ -67,6 +67,6 @@ class FileEditorDialog(QtWidgets.QDialog, Ui_FileEditorDialog):
def _getCallback(self, result, error=False, raw_body=None, **kwargs):
if not error:
self.uiFileTextEdit.setText(raw_body.decode("utf-8"))
elif result["status"] == 404:
elif result.get("status") == 404:
if self._default:
self.uiFileTextEdit.setText(self._default)

View File

@@ -137,9 +137,17 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
if page != self.uiEmptyPageWidget:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(True)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(True)
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset).setEnabled(False)
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).setEnabled(False)
# hide the contextual help button if there is no help text
if page.whatsThis():
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).show()
else:
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help).hide()
def on_uiButtonBox_clicked(self, button):
"""
@@ -153,6 +161,8 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
self.applySettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Reset):
self.resetSettings()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Help):
self.showHelp()
elif button == self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Cancel):
QtWidgets.QDialog.reject(self)
else:
@@ -215,6 +225,14 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
child = item.child(index)
child.setSettings(child.node().settings().copy())
def showHelp(self):
"""
Show contextual help for the current page.
"""
page = self.uiConfigStackedWidget.currentWidget()
if page != self.uiEmptyPageWidget and page.whatsThis():
QtWidgets.QMessageBox.information(self, "{} help".format(page.windowTitle()), page.whatsThis().strip())
class ConfigurationPageItem(QtWidgets.QTreeWidgetItem):

View File

@@ -27,6 +27,9 @@ from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
from ..pages.gns3_vm_preferences_page import GNS3VMPreferencesPage
from ..modules import MODULES
import logging
log = logging.getLogger(__name__)
class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
@@ -41,6 +44,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
super().__init__(parent)
self.setupUi(self)
self._modified_pages = set()
# We adapt the max size to the screen resolution
# We need to manually do that otherwise on small screen the windows
@@ -102,6 +106,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
parent = self.uiTreeWidget
for cls in preference_pages:
preferences_page = cls()
preferences_page.setParent(self)
preferences_page.loadPreferences()
name = preferences_page.windowTitle()
item = QtWidgets.QTreeWidgetItem(parent)
@@ -141,17 +146,23 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
"""
# Found the page with the change
widget = self.sender()
widget = sender = self.sender()
while widget.parent() != self.uiStackedWidget:
widget = widget.parent()
self.addModifiedPage(widget)
if self.addModifiedPage(widget):
log.debug("%s value has changed", sender.objectName())
def addModifiedPage(self, widget):
"""
:returns: True is the page is initialized and element added
"""
# The widget can trigger signal before the end of init due to async api call
if not hasattr(widget, 'pageInitialized') or widget.pageInitialized():
self._applyButton.setEnabled(True)
self._modified_pages.add(widget)
return True
return False
def _showPreferencesPageSlot(self, current, previous):
"""
@@ -183,7 +194,7 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
"""
success = True
for preferences_page in self._modified_pages:
for preferences_page in list(self._modified_pages):
ok = preferences_page.savePreferences()
# if page.savePreferences() returns None, assume success
if ok is not None and not ok:

View File

@@ -17,6 +17,7 @@
import os
import sys
import shutil
from gns3.qt import QtWidgets
from gns3.local_config import LocalConfig
@@ -41,6 +42,7 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
self.setupUi(self)
self.uiNewPushButton.clicked.connect(self._newPushButtonSlot)
self.uiDeletePushButton.clicked.connect(self._deletePushButtonSlot)
# Center on screen
screen = QtWidgets.QApplication.desktop().screenGeometry()
@@ -52,15 +54,20 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
else:
home = os.path.expanduser("~")
path = os.path.join(home, ".config", "GNS3")
profiles_path = os.path.join(path, "profiles")
self.profiles_path = os.path.join(path, "profiles")
self.uiShowAtStartupCheckBox.setChecked(LocalConfig.instance().multiProfiles())
self._refresh()
def _refresh(self):
self.uiProfileSelectComboBox.clear()
self.uiProfileSelectComboBox.addItem("default")
try:
if os.path.exists(profiles_path):
for profil in sorted(os.listdir(os.path.join(path, "profiles"))):
self.uiProfileSelectComboBox.addItem(profil)
if os.path.exists(self.profiles_path):
for profil in sorted(os.listdir(self.profiles_path)):
if not profil.startswith("."):
self.uiProfileSelectComboBox.addItem(profil)
except OSError:
pass
@@ -78,8 +85,19 @@ class ProfileSelectDialog(QtWidgets.QDialog, Ui_ProfileSelectDialog):
self.uiProfileSelectComboBox.setCurrentText(profile)
self.accept()
def _deletePushButtonSlot(self):
profile = self.uiProfileSelectComboBox.currentText()
if profile == "default":
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", "You can't delete the default profile")
else:
try:
shutil.rmtree(os.path.join(self.profiles_path, profile))
self._refresh()
except (OSError, PermissionError) as e:
QtWidgets.QMessageBox.critical(self.parentWidget(), "Delete profile", str(e))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
dialog = ProfileSelectDialog()
dialog.show()

View File

@@ -71,6 +71,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
self.uiRefreshProjectsPushButton.clicked.connect(Controller.instance().refreshProjectList)
Controller.instance().project_list_updated_signal.connect(self._updateProjectListSlot)
self._updateProjectListSlot()
Controller.instance().refreshProjectList()
def _settingsClickedSlot(self):
"""
@@ -82,7 +83,8 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
def _projectsTreeWidgetDoubleClickedSlot(self, item, column):
self.done(True)
def _deleteProjectSlot(self):
@qslot
def _deleteProjectSlot(self, *args):
if len(self.uiProjectsTreeWidget.selectedItems()) == 0:
QtWidgets.QMessageBox.critical(self, "Delete project", "No project selected")
return
@@ -101,13 +103,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
projects_to_delete.add(project_id)
for project_id in projects_to_delete:
Controller.instance().delete("/projects/{}".format(project_id), self._deleteProjectCallback)
def _deleteProjectCallback(self, result, error=False, **kwargs):
if error:
log.error("Error while deleting project: {}".format(result["message"]))
return
Controller.instance().refreshProjectList()
Controller.instance().deleteProject(project_id)
def _duplicateProjectSlot(self):
if len(self.uiProjectsTreeWidget.selectedItems()) == 0:
@@ -136,7 +132,15 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
new_project_name)
name = name.strip()
if reply and len(name) > 0:
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id), self._duplicateCallback, body={"name": name})
if Controller.instance().isRemote():
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name})
else:
project_location = os.path.join(Topology.instance().projectsDirPath(), name)
Controller.instance().post("/projects/{project_id}/duplicate".format(project_id=project_id),
self._duplicateCallback,
body={"name": name, "path": project_location})
def _duplicateCallback(self, result, error=False, **kwargs):
if error:
@@ -225,10 +229,10 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
menu = QtWidgets.QMenu()
menu.triggered.connect(self._menuTriggeredSlot)
if Controller.instance().isRemote():
for action in self._main_window._recent_project_actions:
for action in self._main_window.recent_project_actions:
menu.addAction(action)
else:
for action in self._main_window._recent_file_actions:
for action in self._main_window.recent_file_actions:
menu.addAction(action)
menu.exec_(QtGui.QCursor.pos())
@@ -250,7 +254,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
self._project_settings.pop("project_path", None)
self._project_settings.pop("project_files_dir", None)
else:
project_location = self.uiLocationLineEdit.text()
project_location = self.uiLocationLineEdit.text().strip()
if not project_location:
QtWidgets.QMessageBox.critical(self, "New project", "Project location is empty")
return False
@@ -269,7 +273,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
if existing_project["status"] == "opened":
QtWidgets.QMessageBox.critical(self,
"New project",
"Project {} is running you can not overwrite it".format(self._project_settings["project_name"]))
"Project {} is open you can not overwrite it".format(self._project_settings["project_name"]))
return False
reply = QtWidgets.QMessageBox.warning(self,
@@ -279,7 +283,7 @@ class ProjectDialog(QtWidgets.QDialog, Ui_ProjectDialog):
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
Controller.instance().delete("/projects/{}".format(existing_project["project_id"]), self._overwriteProjectCallback)
Controller.instance().deleteProject(existing_project["project_id"], self._overwriteProjectCallback)
# In all cases we cancel the new project and if project success to delete
# we will call done again

View File

@@ -183,6 +183,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
super().initializePage(page_id)
if self.page(page_id) == self.uiServerWizardPage:
Controller.instance().setDisplayError(False)
Controller.instance().get("/gns3vm", self._getSettingsCallback)
elif self.page(page_id) == self.uiVMWizardPage:
if self._GNS3VMSettings()["engine"] == "vmware":
@@ -247,6 +248,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
"""
if Controller.instance().connected():
self.uiLocalServerStatusLabel.setText("Connection to local server successful")
Controller.instance().get("/gns3vm", self._getSettingsCallback)
elif Controller.instance().connecting():
self.uiLocalServerStatusLabel.setText("Please wait connection to the GNS3 server")
else:
@@ -278,6 +280,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
Validates the settings.
"""
Controller.instance().setDisplayError(True)
if self.currentPage() == self.uiVMWizardPage:
vmname = self.uiVMListComboBox.currentText()
if vmname:
@@ -390,6 +393,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
:param result: ignored
"""
Controller.instance().setDisplayError(True)
settings = self.parentWidget().settings()
if result:
settings["hide_setup_wizard"] = True

View File

@@ -19,16 +19,8 @@
Dialog to manage the snapshots.
"""
import shutil
import re
import time
import os
from ..qt import QtCore, QtWidgets
from ..utils.progress_dialog import ProgressDialog
from ..utils.process_files_worker import ProcessFilesWorker
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
from ..node import Node
from ..controller import Controller
from datetime import datetime
@@ -92,7 +84,7 @@ class SnapshotsDialog(QtWidgets.QDialog, Ui_SnapshotsDialog):
"""
snapshot_name, ok = QtWidgets.QInputDialog.getText(self, "Snapshot", "Snapshot name:", QtWidgets.QLineEdit.Normal, "Unnamed")
if ok and snapshot_name:
if ok and snapshot_name and self._project:
Controller.instance().post("/projects/{}/snapshots".format(self._project.id()), self._createSnapshotsCallback, {"name": snapshot_name})
def _createSnapshotsCallback(self, result, error=False, server=None, context={}, **kwargs):

View File

@@ -19,7 +19,7 @@
Text editor to edit Note items.
"""
from ..qt import QtCore, QtWidgets
from ..qt import QtCore, QtWidgets, qslot
from ..ui.text_editor_dialog_ui import Ui_TextEditorDialog
@@ -70,16 +70,19 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
color.blue(),
color.alpha()))
def _setFontSlot(self):
@qslot
def _setFontSlot(self, *args):
"""
Slot to select the font.
"""
selected_font, ok = QtWidgets.QFontDialog.getFont(self.uiPlainTextEdit.font(), self)
selected_font, ok = QtWidgets.QFontDialog.getFont(self.uiPlainTextEdit.font(), self,
options=QtWidgets.QFontDialog.DontUseNativeDialog)
if ok:
self.uiPlainTextEdit.setFont(selected_font)
def _setColorSlot(self):
@qslot
def _setColorSlot(self, *args):
"""
Slot to select the color.
"""
@@ -88,7 +91,8 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
if color.isValid():
self._setColor(color)
def _applyPreferencesSlot(self):
@qslot
def _applyPreferencesSlot(self, *args):
"""
Applies the new text settings.
"""

View File

@@ -37,6 +37,7 @@ class VMWizard(QtWidgets.QWizard):
self.setModal(True)
self._devices = devices
self._local_server_disable = False
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
if sys.platform.startswith("darwin"):
@@ -104,13 +105,14 @@ class VMWizard(QtWidgets.QWizard):
for compute in ComputeManager.instance().computes():
if compute.id() == "local":
self.uiLocalRadioButton.setEnabled(True)
elif compute.id() == "vm" and hasattr(self, "uiVMRadioButton"):
self.uiVMRadioButton.setEnabled(True)
elif compute.id() == "vm":
if hasattr(self, "uiVMRadioButton"):
self.uiVMRadioButton.setEnabled(True)
else:
self.uiRemoteRadioButton.setEnabled(True)
self.uiRemoteServersComboBox.addItem(compute.name(), compute.id())
if self.uiLocalRadioButton.isEnabled() and self.uiLocalRadioButton.isVisible():
if self.uiLocalRadioButton.isEnabled() and not self._local_server_disable:
self.uiLocalRadioButton.setChecked(True)
elif hasattr(self, "uiVMRadioButton") and self.uiVMRadioButton.isEnabled():
self.uiVMRadioButton.setChecked(True)
@@ -124,6 +126,7 @@ class VMWizard(QtWidgets.QWizard):
"""
Turn off the local server
"""
self._local_server_disable = True
self.uiLocalRadioButton.hide()
self.uiLocalRadioButton.setEnabled(False)
self.setStartId(0)

View File

@@ -24,17 +24,15 @@ import os
import sip
import pickle
from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets, qpartial
from .qt import QtCore, QtGui, QtNetwork, QtWidgets, qpartial, qslot
from .items.node_item import NodeItem
from .dialogs.node_properties_dialog import NodePropertiesDialog
from .link import Link
from .node import Node
from .modules import MODULES
from .modules.builtin.cloud import Cloud
from .modules.module_error import ModuleError
from .settings import GRAPHICS_VIEW_SETTINGS
from .topology import Topology
from .ports.port import Port
from .dialogs.style_editor_dialog import StyleEditorDialog
from .dialogs.text_editor_dialog import TextEditorDialog
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
@@ -44,7 +42,6 @@ from .dialogs.file_editor_dialog import FileEditorDialog
from .local_config import LocalConfig
from .progress import Progress
from .utils.server_select import server_select
from .utils.normalize_filename import normalize_filename
from .compute_manager import ComputeManager
# link items
@@ -280,6 +277,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
"""
link = self._topology.getLink(link_id)
if not link:
return
source_item = None
destination_item = None
source_port = link.sourcePort()
@@ -300,41 +299,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.deleteLinkSlot(link_id)
return
# Multi-link management
#
# multi is the offset of the link
# +------+ multi = -1 Link 2 +-------+
# | +-----------------------------+ |
# | R1 | | R2 |
# | | multi = 0 Link 1 | |
# | +-----------------------------+ |
# | | multi = 1 Link 3 | |
# +------+-----------------------------+-------+
if source_item == destination_item:
multi = 0
else:
multi = 0
link_items = source_item.links()
for link_item in link_items:
if link_item.destinationItem().node().id() == destination_item.node().id():
multi += 1
if link_item.sourceItem().node().id() == destination_item.node().id():
multi += 1
# MAX 7 links on the scene between 2 nodes
if multi > 7:
multi = 0
# Pair item represent the bottom links
elif multi % 2 == 0:
multi = multi // 2
else:
multi = -multi // 2
if link.sourcePort().linkType() == "Serial":
link_item = SerialLinkItem(source_item, source_port, destination_item, destination_port, link, multilink=multi)
link_item = SerialLinkItem(source_item, source_port, destination_item, destination_port, link)
else:
link_item = EthernetLinkItem(source_item, source_port, destination_item, destination_port, link, multilink=multi)
link_item = EthernetLinkItem(source_item, source_port, destination_item, destination_port, link)
self.scene().addItem(link_item)
def deleteLinkSlot(self, link_id):
@@ -362,6 +330,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
source_port = source_item.connectToPort()
if not source_port:
return
if source_port.link() is not None:
QtWidgets.QMessageBox.warning(self, "Create link", "Can't create the link the port is not free")
return
if source_port.linkType() == "Serial":
self._newlink = SerialLinkItem(source_item, source_port, self.mapToScene(event.pos()), None, adding_flag=True)
else:
@@ -375,6 +348,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not destination_port:
return
if destination_port.link() is not None:
QtWidgets.QMessageBox.warning(self, "Create link", "Can't create the link the destination port is not free")
return
if self._newlink in self.scene().items():
self.scene().removeItem(self._newlink)
self._newlink = None
@@ -414,7 +391,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
else:
item.setSelected(True)
elif is_not_link and event.button() == QtCore.Qt.RightButton and not self._adding_link:
if item:
if item and not sip.isdeleted(item):
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
if not event.modifiers() & QtCore.Qt.ControlModifier:
@@ -424,7 +401,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
item.setFlag(item.ItemIsSelectable, True)
item.setSelected(True)
self._showDeviceContextualMenu(QtGui.QCursor.pos())
if item.zValue() < 0:
if not sip.isdeleted(item) and item.zValue() < 0:
item.setFlag(item.ItemIsSelectable, False)
else:
@@ -440,7 +417,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
self._userNodeLinking(event, item)
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
pos = self.mapToScene(event.pos())
note = self.createDrawingItem("text", pos.x(), pos.y(), 0)
note = self.createDrawingItem("text", pos.x(), pos.y(), 1)
pos_x = note.pos().x()
pos_y = note.pos().y() - (note.boundingRect().height() / 2)
note.setPos(pos_x, pos_y)
@@ -573,7 +550,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not self._adding_link:
if isinstance(item, NodeItem) and item.node().initialized():
item.setSelected(True)
if item.node().status() == Node.stopped:
if item.node().status() == Node.stopped or item.node().isAlwaysOn():
self.configureSlot()
return
else:
@@ -652,7 +629,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
QtWidgets.QMessageBox.critical(self, "Project files", "Please drop only one file")
return
path = event.mimeData().urls()[0].toLocalFile()
if os.path.isfile(path) and self._main_window.checkForUnsavedChanges():
if os.path.isfile(path):
self._main_window.loadPath(path)
event.acceptProposedAction()
else:
@@ -947,12 +924,14 @@ class GraphicsView(QtWidgets.QGraphicsView):
break
if os.path.exists(node_dir):
log.debug("Open %s in file manage")
log.debug("Open %s in file manager")
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(node_dir)) is False:
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(node_dir))
break
else:
QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}".format(node_dir, node.compute().name()))
reply = QtWidgets.QMessageBox.information(self, "Show in file manager", "The device directory is located in {} on {}\n\nCopy path to clipboard?".format(node_dir, node.compute().name()), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.clipboard().setText(node_dir)
break
def consoleToNode(self, node, aux=False):
@@ -1424,7 +1403,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
mainwindow = MainWindow.instance()
if "server" in node_data:
return ComputeManager.instance().getCompute(node_data["server"])
try:
return ComputeManager.instance().getCompute(node_data["server"])
except KeyError:
raise ModuleError("Compute {} doesn't exists".format(node_data["server"]))
server = server_select(mainwindow, node_data.get("node_type"))
if server is None:
@@ -1451,7 +1433,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
if not node_module:
raise ModuleError("Could not find any module for {}".format(node_class))
if self._topology.project() is None:
return
node = node_module.instantiateNode(node_class, self.allocateCompute(node_data, instance), self._topology.project())
# If no server is available a ValueError is raised
except (ModuleError, ValueError) as e:
@@ -1461,7 +1444,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
pos = self.mapToScene(pos)
node_item = self.createNodeItem(node, node_data["symbol"], pos.x(), pos.y())
node.setGraphics(node_item)
node_module.createNode(node, node_data["name"])
try:
node_module.createNode(node, node_data["name"])
except ModuleError as e:
QtWidgets.QMessageBox.critical(self, "Node creation", "{}".format(e))
return
return node_item
def createNodeItem(self, node, symbol, x, y):
@@ -1471,15 +1458,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
self.scene().addItem(node_item)
self._topology.addNode(node)
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
node.error_signal.connect(self._displayNodeErrorSlot)
node.warning_signal.connect(self._main_window.uiConsoleTextEdit.writeWarning)
node.server_error_signal.connect(self._main_window.uiConsoleTextEdit.writeServerError)
node.server_error_signal.connect(self._displayNodeErrorSlot)
return node_item
def _displayNodeErrorSlot(self, node_id, message):
@qslot
def _displayNodeErrorSlot(self, node_id, message, *args):
"""
Show error send by a node to the user
"""
@@ -1488,7 +1473,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
if node:
if node.name():
name = node.name()
QtWidgets.QMessageBox.critical(self._main_window, name, message.strip())
if self._main_window and not sip.isdeleted(self._main_window):
QtWidgets.QMessageBox.critical(self._main_window, name, message.strip())
def createDrawingItem(self, type, x, y, z, rotation=0, svg=None, drawing_id=None):

View File

@@ -15,14 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sip
import json
import copy
import ipaddress
import http
import uuid
import pathlib
import urllib.request
import base64
import datetime
import ipaddress
import urllib.request
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork, qpartial, sip_is_deleted
@@ -62,10 +64,13 @@ class HTTPClient(QtCore.QObject):
self._protocol = settings.get("protocol", "http")
self._host = settings["host"]
if self._host == "0.0.0.0":
self._host = "127.0.0.1"
elif ":" in self._host and str(ipaddress.IPv6Address(self._host)) == "::":
self._host = "::1"
try:
if self._host is None or self._host == "0.0.0.0":
self._host = "127.0.0.1"
elif ":" in self._host and ipaddress.IPv6Address(self._host) and str(ipaddress.IPv6Address(self._host)) == "::":
self._host = "::1"
except ipaddress.AddressValueError:
log.error("Invalid host name %s", self._host)
self._port = int(settings["port"])
self._user = settings.get("user", None)
self._password = settings.get("password", None)
@@ -75,17 +80,24 @@ class HTTPClient(QtCore.QObject):
self._shutdown = False # Shutdown in progress
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", None)
# In order to detect computer hibernation we detect the date of the last
# query and disconnect if time is too long between two query
self._last_query_timestamp = None
self._max_time_difference_between_queries = None
if network_manager:
self._network_manager = network_manager
else:
self._network_manager = QtNetwork.QNetworkAccessManager()
# A buffer used by progress download
self._buffer = {}
# List of query waiting for the connection
self._query_waiting_connections = []
def setMaxTimeDifferenceBetweenQueries(self, value):
self._max_time_difference_between_queries = value
def host(self):
"""
Host display to user
@@ -185,7 +197,9 @@ class HTTPClient(QtCore.QObject):
Called when a query download progress
"""
if not sip_is_deleted(HTTPClient._progress_callback):
HTTPClient._progress_callback.progress_signal.emit(query_id, sent, total)
# abs() for maxium because sometimes the system send negative
# values
HTTPClient._progress_callback.progress_signal.emit(query_id, sent, abs(total))
@classmethod
def setProgressCallback(cls, progress_callback):
@@ -208,6 +222,7 @@ class HTTPClient(QtCore.QObject):
Closes the connection with the server.
"""
self._connected = False
self._progress_callback.reset()
def _request(self, url):
"""
@@ -228,7 +243,17 @@ class HTTPClient(QtCore.QObject):
:param query: The Server to connect
"""
def createHTTPQuery(self, method, path, callback, body={}, context={}, downloadProgressCallback=None, showProgress=True, ignoreErrors=False, progressText=None, timeout=120, server=None, prefix="/v2", params={}, **kwargs):
def createHTTPQuery(self, method, path, callback, body={}, context={},
downloadProgressCallback=None,
showProgress=True,
ignoreErrors=False,
progressText=None,
timeout=120,
server=None,
prefix="/v2",
params={},
networkManager=None,
**kwargs):
"""
Call the remote server, if not connected, check connection before
@@ -244,15 +269,39 @@ class HTTPClient(QtCore.QObject):
:param server: The server where the query will run
:param timeout: Delay in seconds before raising a timeout
:param prefix: Prefix to the path
:param networkManager: QNetworkAccessManager None use the default
:param params: Query arguments parameters
:returns: QNetworkReply
"""
if "dev" in __version__:
assert QtCore.QThread.currentThread() == self.thread(), "HTTP request not started from the main thread"
# Shutdown in progress do not execute the query
if self._shutdown:
return
request = qpartial(self._executeHTTPQuery, method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText, server=server, timeout=timeout, prefix=prefix, params=params)
# We try to detect computer hibernation
# if time between two query is too long we trigger a disconnect
if self._max_time_difference_between_queries:
now = datetime.datetime.now().timestamp()
if self._last_query_timestamp is not None and now > self._last_query_timestamp + self._max_time_difference_between_queries:
log.warning("Synchronisation lost with the server.")
self.disconnect()
self._last_query_timestamp = None
return
self._last_query_timestamp = now
request = qpartial(self._executeHTTPQuery, method, path, qpartial(callback), body, context,
downloadProgressCallback=downloadProgressCallback,
showProgress=showProgress,
ignoreErrors=ignoreErrors,
progressText=progressText,
networkManager=networkManager,
server=server,
timeout=timeout,
prefix=prefix,
params=params)
if self._connected:
return request()
@@ -261,7 +310,7 @@ class HTTPClient(QtCore.QObject):
# If we are not connected and we enqueue the first query we open the conection
if len(self._query_waiting_connections) == 1:
log.info("Connection to {}".format(self.url()))
self._executeHTTPQuery("GET", "/version", self._callbackConnect, {}, server=server, timeout=5)
self._executeHTTPQuery("GET", "/version", self._callbackConnect, {}, server=server, timeout=5, showProgress=False)
def _connectionError(self, callback, msg="", server=None):
"""
@@ -318,7 +367,7 @@ class HTTPClient(QtCore.QObject):
self._query_waiting_connections = []
return
if params["version"] != __version__:
if params["version"].split("-")[0] != __version__.split("-")[0]:
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
log.error(msg)
# Stable release
@@ -392,7 +441,7 @@ class HTTPClient(QtCore.QObject):
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, server=None, timeout=120, prefix="/v2", params={}, **kwargs):
def _executeHTTPQuery(self, method, path, callback, body, context={}, downloadProgressCallback=None, showProgress=True, ignoreErrors=False, progressText=None, server=None, timeout=120, prefix="/v2", params={}, networkManager=None, **kwargs):
"""
Call the remote server
@@ -403,6 +452,7 @@ class HTTPClient(QtCore.QObject):
: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 networkManager: The network manager to use. If None use default
: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)
:param server: The server where the query is executed
@@ -411,10 +461,6 @@ class HTTPClient(QtCore.QObject):
:returns: QNetworkReply
"""
# TODO: remove it when all call are migrated
if "compute/" in path:
log.warning("Legacy compute direct call %s", path)
try:
ip = self._host.rsplit('%', 1)[0]
ipaddress.IPv6Address(ip) # remove any scope ID
@@ -442,12 +488,16 @@ class HTTPClient(QtCore.QObject):
# 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)
if not networkManager:
networkManager = self._network_manager
response = networkManager.sendCustomRequest(request, method.encode(), body)
context = copy.copy(context)
context["query_id"] = str(uuid.uuid4())
response.finished.connect(qpartial(self._processResponse, response, server, callback, context, body, ignoreErrors))
response.error.connect(qpartial(self._processError, response, server, callback, context, body, ignoreErrors))
if downloadProgressCallback is not None:
response.readyRead.connect(qpartial(self._readyReadySlot, response, downloadProgressCallback, context, server))
@@ -504,39 +554,41 @@ class HTTPClient(QtCore.QObject):
Beware it's call for all request you need to check the status of the response
"""
# We check if we received HTTP headers
if not len(response.rawHeaderList()) > 0:
response.abort()
if not sip.isdeleted(response) and response.isRunning() and not len(response.rawHeaderList()) > 0:
if not response.error() != QtNetwork.QNetworkReply.NoError:
log.warn("Timeout request {}".format(response.url().toString()))
response.abort()
def disconnect(self):
"""
Disconnect from the remote server
"""
self.connection_disconnected_signal.emit()
self.close()
def _requestCanceled(self, response, context):
if response.isRunning():
log.warn("Aborting request for {}".format(response.url()))
if response.isRunning() and not response.error() != QtNetwork.QNetworkReply.NoError:
log.warn("Aborting request for {}".format(response.url().toString()))
response.abort()
if "query_id" in context:
self._notify_progress_end_query(context["query_id"])
def _processResponse(self, response, server, 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()
def _processError(self, response, server, callback, context, request_body, ignore_errors, error_code):
if error_code != QtNetwork.QNetworkReply.NoError:
error_message = response.errorString()
if not ignore_errors:
log.debug("Response error: %s (error: %d)", error_message, error_code)
log.debug("Response error: %s for %s (error: %d)", error_message, response.url().toString(), error_code)
if error_code < 200:
if not ignore_errors:
self.connection_disconnected_signal.emit()
self.close()
if "query_id" in context:
self._notify_progress_end_query(context["query_id"])
if error_code < 200 or error_code == 403:
if error_code == QtNetwork.QNetworkReply.OperationCanceledError: # It's legit to cancel do not disconnect
error_message = "Operation timeout" # It's more clear than cancel, because cancel is trigger by us when we timeout
elif not ignore_errors:
self.disconnect()
if callback is not None:
callback({"message": error_message}, error=True, server=server, context=context)
return
@@ -567,7 +619,15 @@ class HTTPClient(QtCore.QObject):
log.error(json.loads(body)["message"])
except (ValueError, KeyError):
log.error(error_message)
else:
def _processResponse(self, response, server, callback, context, request_body, ignore_errors):
if request_body is not None:
request_body.close()
if "query_id" in context:
self._notify_progress_end_query(context["query_id"])
if response.error() == QtNetwork.QNetworkReply.NoError:
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
log.debug("Decoding response from {} response {}".format(response.url().toString(), status))
try:
@@ -587,16 +647,15 @@ class HTTPClient(QtCore.QObject):
callback(params, error=True, server=server, context=context)
else:
callback(params, server=server, context=context, raw_body=raw_body)
# 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
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 getSynchronous(self, endpoint, timeout=2):
"""

View File

@@ -18,7 +18,6 @@
import os
import copy
import pathlib
import glob
from gns3.qt import QtWidgets
from gns3.local_server_config import LocalServerConfig
@@ -46,7 +45,7 @@ class ImageManager:
:returns path: Final path
"""
if server and server != "local":
if (server and server != "local") or Controller.instance().isRemote():
return self._uploadImageToRemoteServer(path, server, node_type)
else:
destination_directory = self.getDirectoryForType(node_type)

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from ..qt import QtCore, QtWidgets, qslot
import uuid
import logging
@@ -58,7 +58,8 @@ class DrawingItem:
return self._id
def create(self):
self._project.post("/drawings", self._createDrawingCallback, body=self.__json__())
if self._project:
self._project.post("/drawings", self._createDrawingCallback, body=self.__json__())
def _createDrawingCallback(self, result, error=False, **kwargs):
"""
@@ -79,6 +80,7 @@ class DrawingItem:
if self._id:
self._project.put("/drawings/" + self._id, self.updateDrawingCallback, body=self.__json__())
@qslot
def updateDrawingCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -188,6 +190,9 @@ class DrawingItem:
self.updateDrawing()
return QtWidgets.QGraphicsItem.itemChange(self, change, value)
def updateNode(self):
self.updateDrawing()
def drawLayerInfo(self, painter):
"""
Draws the layer position.

View File

@@ -36,12 +36,11 @@ class EthernetLinkItem(LinkItem):
:param destination_port: destination Port instance
:param link: Link instance (contains back-end stuff for this link)
:param adding_flag: indicates if this link is being added (no destination yet)
:param multilink: used to draw multiple link between the same source and destination
"""
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False):
super().__init__(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)
self._source_collision_offset = 0.0
self._destination_collision_offset = 0.0

View File

@@ -19,9 +19,7 @@
Graphical representation of an image on the QGraphicsScene.
"""
import xml.etree.ElementTree as ET
from ..qt import QtWidgets, QtCore, QtSvg
from ..qt import QtSvg
from ..qt.qimage_svg_renderer import QImageSvgRenderer
from .drawing_item import DrawingItem
@@ -32,8 +30,7 @@ class ImageItem(QtSvg.QGraphicsSvgItem, DrawingItem):
Class to insert an image on the scene.
"""
def __init__(self, image_path=None, pos=None, svg=None, **kws):
def __init__(self, image_path=None, pos=None, svg=None, **kws):
self._image_path = image_path
# Because we call the Qt C++ code we need to handle the case of pos is None otherwise we will get a conversion error
@@ -42,7 +39,6 @@ class ImageItem(QtSvg.QGraphicsSvgItem, DrawingItem):
else:
super().__init__(**kws)
if self._image_path:
renderer = QImageSvgRenderer(image_path)
self.setSharedRenderer(renderer)
@@ -77,4 +73,3 @@ class ImageItem(QtSvg.QGraphicsSvgItem, DrawingItem):
Return an SVG version of the shape
"""
return self.renderer().svg()

View File

@@ -20,11 +20,9 @@ Base class for link items (Ethernet, serial etc.).
Link items are graphical representation of a link on the QGraphicsScene
"""
import sip
import math
from ..qt import QtCore, QtGui, QtWidgets, QtSvg, qslot
from ..node import Node
from ..packet_capture import PacketCapture
@@ -51,13 +49,12 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
:param destination_port: destination Port instance
:param link: Link instance (contains back-end stuff for this link)
:param adding_flag: indicates if this link is being added (no destination yet)
:param multilink: used to draw multiple link between the same source and destination
"""
_draw_port_labels = False
delete_link_item_signal = QtCore.pyqtSignal(str)
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False):
super().__init__()
self.setAcceptHoverEvents(True)
@@ -78,10 +75,6 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
# default pen size
self._pen_width = 2.0
# indicates the link position when there are multiple links
# between the same source and destination
self._multilink = multilink
# source & destination items and ports
self._source_item = source_item
self._destination_item = destination_item
@@ -351,6 +344,7 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
self.setHovered(False)
@qslot
def adjust(self):
"""
Computes the source point and destination point.
@@ -378,15 +372,54 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
# compute the length of the line
self.length = math.sqrt(self.dx * self.dx + self.dy * self.dy)
multilink = self._computeMultiLink()
# multi-link management
if not self._adding_flag and self._multilink and self.length:
if not self._adding_flag and multilink and self.length:
angle = math.radians(90)
self.dxrot = math.cos(angle) * self.dx - math.sin(angle) * self.dy
self.dyrot = math.sin(angle) * self.dx + math.cos(angle) * self.dy
offset = QtCore.QPointF((self.dxrot * (self._multilink * 5)) / self.length, (self.dyrot * (self._multilink * 5)) / self.length)
offset = QtCore.QPointF((self.dxrot * (multilink * 5)) / self.length, (self.dyrot * (multilink * 5)) / self.length)
self.source = QtCore.QPointF(self.source + offset)
self.destination = QtCore.QPointF(self.destination + offset)
def _computeMultiLink(self):
# Multi-link management
#
# multi is the offset of the link
# +------+ multi = -1 Link 2 +-------+
# | +-----------------------------+ |
# | R1 | | R2 |
# | | multi = 0 Link 1 | |
# | +-----------------------------+ |
# | | multi = 1 Link 3 | |
# +------+-----------------------------+-------+
if self._source_item == self._destination_item:
multi = 0
elif not hasattr(self._destination_item, "node"): # Could be temporary a qpointf during link creation
multi = 0
else:
multi = 0
link_items = self._source_item.links()
for link_item in link_items:
if link_item == self:
break
if link_item.destinationItem().node().id() == self._destination_item.node().id():
multi += 1
if link_item.sourceItem().node().id() == self._destination_item.node().id():
multi += 1
# MAX 7 links on the scene between 2 nodes
if multi > 7:
multi = 0
# Pair item represent the bottom links
elif multi % 2 == 0:
multi = multi // 2
else:
multi = -multi // 2
return multi
def setMousePoint(self, scene_point):
"""
Sets new mouse point coordinates.

View File

@@ -142,8 +142,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
def symbol(self):
return self._symbol
def _symbolLoadedCallback(self, path):
renderer = QImageSvgRenderer(path)
@qslot
def _symbolLoadedCallback(self, path, *args):
renderer = QImageSvgRenderer(path, fallback=":/icons/cancel.svg")
renderer.setObjectName(path)
self.setSharedRenderer(renderer)
if self._node.settings().get("symbol") != self._symbol:
@@ -178,8 +179,16 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
if not sip.isdeleted(link_item):
self._links.append(link_item)
link_item.link().delete_link_signal.connect(self._removeLink)
link_item.link().updated_link_signal.connect(self._linkUpdatedSlot)
self._node.updated_signal.emit()
@qslot
def _linkUpdatedSlot(self, *args):
"""
When a link change we also notify the listener of the node
"""
self._node.updated_signal.emit()
@qslot
def _removeLink(self, link_id, *args):
"""
@@ -189,8 +198,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
for link_item in self._links:
if link_item.link().id == link_id:
if link_item.link().id() == link_id:
self._links.remove(link_item)
return
def links(self):
"""

View File

@@ -208,6 +208,10 @@ class NoteItem(QtWidgets.QGraphicsTextItem):
font.setItalic(True)
elif key == "font-weight" and val == "bold":
font.setBold(True)
elif key == "text-decoration" and val == "underline":
font.setUnderline(True)
elif key == "text-decoration" and val == "line-through":
font.setStrikeOut(True)
elif key == "fill":
new_color = colorFromSvg(val)
color = self.defaultTextColor()
@@ -256,6 +260,11 @@ class NoteItem(QtWidgets.QGraphicsTextItem):
if self.font().bold():
style += "font-weight: bold;"
if self.font().strikeOut():
style += "text-decoration: line-through;"
elif self.font().underline():
style += "text-decoration: underline;"
style += "fill: {};".format("#" + hex(self.defaultTextColor().rgba())[4:])
style += "fill-opacity: {};".format(self.defaultTextColor().alphaF())

View File

@@ -37,12 +37,11 @@ class SerialLinkItem(LinkItem):
:param destination_port: destination Port instance
:param link: Link instance (contains back-end stuff for this link)
:param adding_flag: indicates if this link is being added (no destination yet)
:param multilink: used to draw multiple link between the same source and destination
"""
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False, multilink=0):
def __init__(self, source_item, source_port, destination_item, destination_port, link=None, adding_flag=False):
super().__init__(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)
def adjust(self):
"""

View File

@@ -20,7 +20,7 @@ Base class for shape items (Rectangle, ellipse etc.).
"""
import xml.etree.ElementTree as ET
from ..qt import QtCore, QtGui, QtWidgets, QtSvg
from ..qt import QtCore, QtGui, QtWidgets
from .drawing_item import DrawingItem
from .utils import colorFromSvg
@@ -35,12 +35,11 @@ class ShapeItem(DrawingItem):
QtCore.Qt.SolidLine: "",
QtCore.Qt.NoPen: None,
QtCore.Qt.DashLine: "25, 25",
QtCore.Qt.DotLine: "5, 25",
QtCore.Qt.DashDotLine: "5, 25, 25",
QtCore.Qt.DotLine: "5, 25",
QtCore.Qt.DashDotLine: "5, 25, 25",
QtCore.Qt.DashDotDotLine: "25, 25, 5, 25, 5"
}
"""
Base class to draw shapes on the scene.
"""
@@ -192,10 +191,10 @@ class ShapeItem(DrawingItem):
element.set("fill-opacity", str(self.brush().color().alphaF()))
dasharray = self.QT_DASH_TO_SVG[pen.style()]
if dasharray is None: # No border to the element
if dasharray is None: # No border to the element
return element
elif dasharray == "":
pass # Solid line
pass # Solid line
else:
element.set("stroke-dasharray", dasharray)
element.set("stroke-width", str(pen.width()))
@@ -239,7 +238,7 @@ class ShapeItem(DrawingItem):
stroke = svg[0].get("stroke-dasharray")
if stroke:
for (qt_stroke, svg_stroke) in self.QT_DASH_TO_SVG.items():
if svg_stroke == stroke:
if svg_stroke == stroke:
pen.setStyle(qt_stroke)
self.setPen(pen)

View File

@@ -26,10 +26,15 @@ from .drawing_item import DrawingItem
from .utils import colorFromSvg
import logging
log = logging.getLogger(__name__)
class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
"""
Text item for the QGraphicsView.
"""
def __init__(self, svg=None, **kws):
super().__init__(**kws)
@@ -44,7 +49,10 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
self.setFont(qt_font)
if svg:
svg = self.fromSvg(svg)
try:
svg = self.fromSvg(svg)
except ET.ParseError as e:
log.warning(str(e))
if self._id is None:
self.create()
@@ -118,6 +126,10 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
text.set("font-style", "italic")
if self.font().bold():
text.set("font-weight", "bold")
if self.font().strikeOut():
text.set("text-decoration", "line-through")
elif self.font().underline():
text.set("text-decoration", "underline")
text.set("fill", "#" + hex(self.defaultTextColor().rgba())[4:])
text.set("fill-opacity", str(self.defaultTextColor().alphaF()))
text.text = self.toPlainText()
@@ -151,6 +163,10 @@ class TextItem(QtWidgets.QGraphicsTextItem, DrawingItem):
font.setItalic(True)
if text.get("font-weight") == "bold":
font.setBold(True)
if text.get("text-decoration") == "underline":
font.setUnderline(True)
if text.get("text-decoration") == "line-through":
font.setStrikeOut(True)
self.setFont(font)
self.setPlainText(text.text)

View File

@@ -23,7 +23,6 @@ import os
import re
import sip
import uuid
import tempfile
from .qt import QtCore, QtWidgets
from .controller import Controller
@@ -78,6 +77,7 @@ class Link(QtCore.QObject):
self._link_id = link_id
self._capturing = False
self._capture_file_path = None
self._capture_file = None
self._initialized = False
# Boolean if True we are creatin the first instance of this node
@@ -105,7 +105,10 @@ class Link(QtCore.QObject):
# If the controller is remote the capture path should be rewrite to something local
if Controller.instance().isRemote():
if self._capture_file_path is None and result.get("capture_file_path", None) is not None:
(handle, self._capture_file_path) = tempfile.mkstemp()
self._capture_file = QtCore.QTemporaryFile()
self._capture_file.open(QtCore.QFile.WriteOnly)
self._capture_file.setAutoRemove(True)
self._capture_file_path = self._capture_file.fileName()
Controller.instance().get(
"/projects/{project_id}/links/{link_id}/pcap".format(
project_id=self.project().id(),
@@ -113,6 +116,7 @@ class Link(QtCore.QObject):
None,
showProgress=False,
downloadProgressCallback=self._downloadPcapProgress,
ignoreErrors=True, # If something is wrong avoid disconnect us from server
timeout=None)
else:
self._capture_file_path = result["capture_file_path"]
@@ -313,15 +317,14 @@ class Link(QtCore.QObject):
"""
if not self._capture_file_path:
return
try:
with open(self._capture_file_path, 'ab') as f:
f.write(content)
except OSError as e:
log.error("Can't write file {}: {}".format(self._capture_file_path, e), True)
return
self._capture_file.write(content)
self._capture_file.flush()
def stopCapture(self):
if Controller.instance().isRemote():
if self._capture_file:
self._capture_file.close()
self._capture_file = None
if self._capture_file_path:
try:
os.remove(self._capture_file_path)

View File

@@ -23,7 +23,7 @@ import copy
import psutil
from .qt import QtCore, QtWidgets
from .qt import QtCore, QtWidgets, qslot
from .version import __version__
from .utils import parse_version
from .controller import Controller
@@ -39,6 +39,8 @@ class LocalConfig(QtCore.QObject):
"""
config_changed_signal = QtCore.Signal()
# When this signal is emit the config is saved on controller
save_on_controller_signal = QtCore.Signal()
def __init__(self, config_file=None):
"""
@@ -48,8 +50,27 @@ class LocalConfig(QtCore.QObject):
super().__init__()
self._profile = None
self._config_file = config_file
# Security to avoid pushing to the controller settings before
# we get the original settings from controller
self._settings_retrieved_from_controller = False
self._migrateOldConfigPath()
self._resetLoadConfig()
self._monitoring_changes = False
Controller.instance().connected_signal.connect(self.refreshConfigFromController)
self.save_on_controller_signal.connect(self._saveOnController)
def _monitorChanges(self):
"""
Poll the remote server waiting for settings update
"""
if self._monitoring_changes:
return
self._monitoring_changes = True
self._timer = QtCore.QTimer()
self._timer.setInterval(5000)
self._refreshingSettings = False
self._timer.timeout.connect(self.refreshConfigFromController)
self._timer.start()
def _resetLoadConfig(self):
"""
@@ -97,8 +118,7 @@ class LocalConfig(QtCore.QObject):
# overwrite system wide settings with user specific ones
self._settings.update(user_settings)
self._migrateOldConfig()
self._writeConfig()
Controller.instance().connected_signal.connect(self.refreshConfigFromController)
self.writeConfig()
def profile(self):
"""
@@ -116,28 +136,36 @@ class LocalConfig(QtCore.QObject):
self._config_file = None
self._resetLoadConfig()
@qslot
def refreshConfigFromController(self):
"""
Refresh the configuration from the controller
"""
controller = Controller.instance()
if controller.connected():
controller.get("/settings", self._getSettingsCallback)
self._refreshingSettings = True
controller.get("/settings", self._getSettingsCallback, showProgress=False)
self._monitorChanges()
def _getSettingsCallback(self, result, error=False, **kwargs):
self._refreshingSettings = False
if error:
log.error("Can't get settings from controller")
return
if result == {} and self._settings != {}:
self._saveOnController()
self._settings_retrieved_from_controller = True
self.save_on_controller_signal.emit()
return
self._settings.update(result)
# Update already loaded section
for section in self._settings.keys():
if isinstance(self._settings[section], dict):
self.loadSectionSettings(section, self._settings[section])
self.config_changed_signal.emit()
# The server return an uuid to keep track of settings version
if self._settings.get("modification_uuid") != result.get("modification_uuid"):
self._settings.update(result)
# Update already loaded section
for section in self._settings.keys():
if isinstance(self._settings[section], dict):
self.loadSectionSettings(section, self._settings[section])
self.config_changed_signal.emit()
self._settings_retrieved_from_controller = True
def configDirectory(self):
"""
@@ -181,8 +209,8 @@ class LocalConfig(QtCore.QObject):
# settings from 1.6.1 with 1.5.1 you will have an error
if "version" in self._settings:
if parse_version(self._settings["version"])[:2] > parse_version(__version__)[:2]:
app = QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
QtWidgets.QMessageBox.critical(None, "Version error", "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data.".format(self._settings["version"]))
QtWidgets.QApplication(sys.argv) # We need to create an application because settings are loaded before Qt init
QtWidgets.QMessageBox.critical(None, "Version error", "Your settings are for version {} of GNS3. You cannot use a previous version of GNS3 without risking losing data. If you want to reset delete the settings in {}".format(self._settings["version"], self.configDirectory()))
# Exit immediately not clean but we want to avoid any side effect that could corrupt the file
sys.exit(1)
@@ -212,7 +240,7 @@ class LocalConfig(QtCore.QObject):
from .settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, DEFAULT_TELNET_CONSOLE_COMMAND
if "MainWindow" in self._settings:
if self._settings["MainWindow"]["telnet_console_command"] not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
if self._settings["MainWindow"].get("telnet_console_command") not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
self._settings["MainWindow"]["telnet_console_command"] = DEFAULT_TELNET_CONSOLE_COMMAND
# Migrate 1.X to 2.0
@@ -257,7 +285,7 @@ class LocalConfig(QtCore.QObject):
return dict()
def _writeConfig(self):
def writeConfig(self):
"""
Write the configuration file.
"""
@@ -272,16 +300,17 @@ class LocalConfig(QtCore.QObject):
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))
self._saveOnController()
self.save_on_controller_signal.emit()
def _saveOnController(self):
@qslot
def _saveOnController(self, *args):
"""
Save some settings on controller for the transition from
GUI to a central controller. Will be removed later
"""
if Controller.instance().connected():
if Controller.instance().connected() and self._settings_retrieved_from_controller:
# We save only non user specific sections
section_to_save_on_controller = ["Builtin", "Docker", "IOU", "Qemu", "VMware", "VPCS", "VirtualBox", "GraphicsView"]
section_to_save_on_controller = ["Builtin", "Docker", "IOU", "Qemu", "VMware", "VPCS", "VirtualBox", "GraphicsView", "Dynamips"]
controller_settings = {}
for key, val in self._settings.items():
if key in section_to_save_on_controller:
@@ -338,7 +367,7 @@ class LocalConfig(QtCore.QObject):
if self._settings != settings:
self._settings.update(settings)
self._writeConfig()
self.writeConfig()
self.config_changed_signal.emit()
def loadSectionSettings(self, section, default_settings):
@@ -374,7 +403,7 @@ class LocalConfig(QtCore.QObject):
if changed:
log.info("Section %s has missing default values. Adding keys %s Saving configuration", section, ','.join(set(default_settings.keys()) - set(settings.keys())))
self._writeConfig()
self.writeConfig()
return copy.deepcopy(settings)
@@ -392,7 +421,7 @@ class LocalConfig(QtCore.QObject):
if self._settings[section] != settings:
self._settings[section].update(copy.deepcopy(settings))
log.info("Section %s has changed. Saving configuration", section)
self._writeConfig()
self.writeConfig()
else:
log.debug("Section %s has not changed. Skip saving configuration", section)
@@ -404,6 +433,14 @@ class LocalConfig(QtCore.QObject):
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["experimental_features"]
def hdpi(self):
"""
:returns: Boolean. True if hdpi is allowed
"""
from gns3.settings import GENERAL_SETTINGS
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["hdpi"]
def multiProfiles(self):
"""
:returns: Boolean. True if multi_profiles is enabled

View File

@@ -92,7 +92,6 @@ class LocalServer(QtCore.QObject):
self._settings = {}
self.localServerSettings()
self._port = self._settings.get("port", 3080)
if not self._settings.get("auto_start", True):
if self._settings.get("host") is None:
self._http_client = HTTPClient(self._settings)
@@ -100,6 +99,12 @@ class LocalServer(QtCore.QObject):
else:
self._http_client = None
self._stopping = False
self._timer = QtCore.QTimer()
self._timer.setInterval(5000)
self._timer.timeout.connect(self._checkLocalServerRunningSlot)
self._timer.start()
def _pid_path(self):
"""
:returns: Path of the PID file
@@ -167,6 +172,10 @@ class LocalServer(QtCore.QObject):
else:
# capabilities not supported
request_setuid = True
except AttributeError:
# Due to a Python bug, os.listxattr could be missing: https://github.com/GNS3/gns3-gui/issues/2010
log.warning("Could not determine if CAP_NET_RAW capability is set for uBridge (Python bug)")
return True
except OSError as e:
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set CAP_NET_RAW capability to uBridge {}: {}".format(path, str(e)))
return False
@@ -430,6 +439,7 @@ class LocalServer(QtCore.QObject):
Starts the local server process.
"""
self._stopping = False
path = self.localServerPath()
command = '"{executable}" --local'.format(executable=path)
@@ -461,11 +471,11 @@ class LocalServer(QtCore.QObject):
try:
if sys.platform.startswith("win"):
# use the string on Windows
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
self._local_server_process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, stderr=subprocess.PIPE)
else:
# use arguments on other platforms
args = shlex.split(command)
self._local_server_process = subprocess.Popen(args)
self._local_server_process = subprocess.Popen(args, stderr=subprocess.PIPE)
except (OSError, subprocess.SubprocessError) as e:
log.warning('Could not start local server "{}": {}'.format(command, e))
return False
@@ -473,6 +483,16 @@ class LocalServer(QtCore.QObject):
log.info("Local server process has started (PID={})".format(self._local_server_process.pid))
return True
def _checkLocalServerRunningSlot(self):
if self._local_server_process and not self._stopping:
if not self.localServerProcessIsRunning():
log.error("Local server process has stopped")
try:
log.error(self._local_server_process.stderr.read().decode())
except (OSError, UnicodeDecodeError):
pass
self._local_server_process = None
def localServerProcessIsRunning(self):
"""
Returns either the local server is running.
@@ -494,7 +514,7 @@ class LocalServer(QtCore.QObject):
:returns: boolean
"""
status, json_data = getSynchronous(self._settings["host"], self._port, "version",
status, json_data = getSynchronous(self._settings["protocol"], self._settings["host"], self._port, "version",
timeout=2, user=self._settings["user"], password=self._settings["password"])
if json_data is None or status != 200:
@@ -514,6 +534,7 @@ class LocalServer(QtCore.QObject):
"""
if self.localServerProcessIsRunning():
self._stopping = True
log.info("Stopping local server (PID={})".format(self._local_server_process.pid))
# local server is running, let's stop it
if self._http_client:
@@ -539,8 +560,8 @@ class LocalServer(QtCore.QObject):
except (PermissionError, SystemError):
pass
try:
# wait for the server to stop for maximum 2 seconds
self._local_server_process.wait(timeout=10)
# wait for the server to stop for maximum x seconds
self._local_server_process.wait(timeout=60)
except subprocess.TimeoutExpired:
proceed = QtWidgets.QMessageBox.question(self.parent(),
"Local server",
@@ -573,5 +594,6 @@ def main():
local_server.localServerAutoStart()
local_server.stopLocalServer()
if __name__ == '__main__':
main()

View File

@@ -85,14 +85,33 @@ class ColouredStreamHandler(logging.StreamHandler):
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", "{")
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {name}:{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", "{")
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {name}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{")
logging.basicConfig(level=level, handlers=[stream_handler])
log = logging.getLogger()
log.addHandler(stream_handler)
log_factory = logging.getLogRecordFactory()
def factory(name, level, fn, lno, msg, args, exc_info, func=None, sinfo=None, **kwargs):
"""
Reformat the log message to get something more clean
"""
# When qt message box is display the correct line number is a part of
# the name
if ":" in name:
name, lno = name.split(":")
lno = int(lno)
name = name.replace("gns3.", "")
try:
return log_factory(name, level, fn, lno, msg, args, exc_info, func=func, sinfo=sinfo, **kwargs)
except Exception as e: # To avoid recursion we just print the message if something is wrong when logging
print(msg)
return
logging.setLogRecordFactory(factory)
try:
try:
os.makedirs(os.path.dirname(logfile))

View File

@@ -48,7 +48,7 @@ import signal
import psutil
try:
from gns3.qt import QtCore, QtGui, QtWidgets
from gns3.qt import QtCore, QtWidgets
except ImportError:
raise SystemExit("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
from gns3.main_window import MainWindow
@@ -60,7 +60,6 @@ from gns3.application import Application
from gns3.utils import parse_version
from gns3.dialogs.profile_select import ProfileSelectDialog
import logging
log = logging.getLogger(__name__)
@@ -221,11 +220,12 @@ def main():
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
global app
app = Application(sys.argv)
local_config = LocalConfig.instance()
if local_config.multiProfiles():
global app
app = Application(sys.argv, hdpi=local_config.hdpi())
if local_config.multiProfiles() and not options.profile:
profile_select = ProfileSelectDialog()
profile_select.show()
profile_select.exec_()
@@ -236,23 +236,24 @@ def main():
local_config.setConfigFilePath(options.config)
elif options.profile:
local_config.setProfile(options.profile)
profile = options.profile
# save client logging info to a file
logfile = os.path.join(LocalConfig.instance().configDirectory(), "gns3_gui.log")
# on debug enable logging to stdout
if options.debug:
root_logger = init_logger(logging.DEBUG, logfile)
init_logger(logging.DEBUG, logfile)
elif options.quiet:
root_logger = init_logger(logging.ERROR, logfile)
init_logger(logging.ERROR, logfile)
else:
root_logger = init_logger(logging.INFO, logfile)
init_logger(logging.INFO, logfile)
current_year = datetime.date.today().year
log.info("GNS3 GUI version {}".format(__version__))
log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
log.info("Application started with {}".format("".join(sys.argv)))
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(LocalConfig.instance().configDirectory(), exception_file_path)

View File

@@ -41,11 +41,9 @@ from .dialogs.doctor_dialog import DoctorDialog
from .dialogs.edit_project_dialog import EditProjectDialog
from .dialogs.setup_wizard import SetupWizard
from .settings import GENERAL_SETTINGS
from .utils.progress_dialog import ProgressDialog
from .items.node_item import NodeItem
from .items.link_item import LinkItem
from .items.shape_item import ShapeItem
from .items.image_item import ImageItem
from .topology import Topology
from .http_client import HTTPClient
from .progress import Progress
@@ -92,10 +90,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._open_project_path = None
self._loadSettings()
self._connections()
self._max_recent_files = 5
self._maxrecent_files = 5
self._project_dialog = None
self._recent_file_actions = []
self._recent_project_actions = []
self.recent_file_actions = []
self.recent_project_actions = []
self._start_time = time.time()
local_config = LocalConfig.instance()
local_config.config_changed_signal.connect(self._localConfigChangedSlot)
@@ -112,7 +110,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiDocksMenu.addAction(self.uiTopologySummaryDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiComputeSummaryDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiConsoleDockWidget.toggleViewAction())
self.uiDocksMenu.addAction(self.uiNodesDockWidget.toggleViewAction())
action = self.uiNodesDockWidget.toggleViewAction()
action.setIconText("All devices")
self.uiDocksMenu.addAction(action)
# make sure the dock widget is not open
self.uiNodesDockWidget.setVisible(False)
@@ -124,25 +124,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._pictures_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
# add recent file actions to the File menu
for i in range(0, self._max_recent_files):
for i in range(0, self._maxrecent_files):
action = QtWidgets.QAction(self.uiFileMenu)
action.setVisible(False)
action.triggered.connect(self.openRecentFileSlot)
self._recent_file_actions.append(action)
self.uiFileMenu.insertActions(self.uiQuitAction, self._recent_file_actions)
self._recent_file_actions_separator = self.uiFileMenu.insertSeparator(self.uiQuitAction)
self._recent_file_actions_separator.setVisible(False)
self.recent_file_actions.append(action)
self.uiFileMenu.insertActions(self.uiQuitAction, self.recent_file_actions)
self.recent_file_actions_separator = self.uiFileMenu.insertSeparator(self.uiQuitAction)
self.recent_file_actions_separator.setVisible(False)
self.updateRecentFileActions()
# add recent projects to the File menu
for i in range(0, self._max_recent_files):
for i in range(0, self._maxrecent_files):
action = QtWidgets.QAction(self.uiFileMenu)
action.setVisible(False)
action.triggered.connect(self.openRecentProjectSlot)
self._recent_project_actions.append(action)
self._recent_project_actions_separator = self.uiFileMenu.addSeparator()
self._recent_project_actions_separator.setVisible(False)
self.uiFileMenu.addActions(self._recent_project_actions)
self.recent_project_actions.append(action)
self.recent_project_actions_separator = self.uiFileMenu.addSeparator()
self.recent_project_actions_separator.setVisible(False)
self.uiFileMenu.addActions(self.recent_project_actions)
# set the window icon
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico"))
@@ -213,6 +213,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
self.uiShowGridAction.triggered.connect(self._showGridActionSlot)
# tool menu connections
self.uiWebInterfaceAction.triggered.connect(self._openWebInterfaceActionSlot)
# control menu connections
self.uiStartAllAction.triggered.connect(self._startAllActionSlot)
self.uiSuspendAllAction.triggered.connect(self._suspendAllActionSlot)
@@ -288,6 +291,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
def _openWebInterfaceActionSlot(self):
if Controller.instance().connected():
QtGui.QDesktopServices.openUrl(QtCore.QUrl(Controller.instance().httpClient().fullUrl()))
def _showGridActionSlot(self):
"""
Called when we ask to display the grid
@@ -383,7 +390,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
action = self.sender()
if action:
if action and action.data():
if len(action.data()) == 2:
project_id, project_path = action.data()
Topology.instance().createLoadProject({
@@ -411,7 +418,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# Portable GNS3 project
Topology.instance().importProject(path)
elif path.endswith(".net"):
QtWidgets.QMessageBox.critical(self, "Open project", "Importing legacy project is not supported in 2.0.\nYou need to open it with GNS3 1.x in order to convert it or manually run the gns3 converter.")
QtWidgets.QMessageBox.critical(self, "Open project", "Importing legacy project is not supported in 2.0.\nYou must open it using GNS3 1.x in order to convert it or manually run the gns3 converter.")
return
elif path.endswith(".gns3appliance") or path.endswith(".gns3a"):
@@ -424,7 +431,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._appliance_wizard.show()
self._appliance_wizard.exec_()
elif path.endswith(".gns3"):
Topology.instance().loadProject(path)
if Controller.instance().isRemote():
QtWidgets.QMessageBox.critical(self, "Open project", "Cannot open a .gns3 file on a remote server, please use a portable project (.gns3p) instead")
return
else:
Topology.instance().loadProject(path)
else:
try:
extension = path.split('.')[1]
@@ -432,10 +443,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
except IndexError:
QtWidgets.QMessageBox.critical(self, "File open", "Missing file extension for {}".format(path))
def _projectChangedSlot(self):
@qslot
def _projectChangedSlot(self, *args):
"""
Called when a project finish to load
"""
project = Topology.instance().project()
if project is not None and self._project_dialog:
self._project_dialog.reject()
self._project_dialog = None
self._refreshVisibleWidgets()
def _refreshVisibleWidgets(self):
@@ -649,7 +665,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Slot called when starting all the nodes.
"""
project = Topology.instance().project()
if project is not None:
project.start_all_nodes()
@@ -932,21 +947,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
log.debug("Close the Main Window")
self._analytics_client.sendScreenView("Main Window", session_start=False)
project = Topology.instance().project()
if not project:
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
elif project.closed() or not project.autoClose():
log.debug("Project is closed killing server and closing main windows")
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
else:
log.debug("Project is not closed asking for project closing")
project.project_closed_signal.connect(self._finish_application_closing)
project.close(local_server_shutdown=True)
event.ignore()
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
def _finish_application_closing(self, close_windows=True):
"""
@@ -994,7 +997,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
reply = QtWidgets.QMessageBox.warning(self, "GNS3", "Another GNS3 GUI is already running. Continue?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
self.close()
sys.exit(1)
return
if not sys.platform.startswith("win") and os.geteuid() == 0:
@@ -1023,7 +1026,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if self._open_file_at_startup:
self.loadPath(self._open_file_at_startup)
self._open_file_at_startup = None
else:
elif Topology.instance().project() is None:
self._newProjectActionSlot()
if self._settings["check_for_update"]:
@@ -1064,7 +1067,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
pass
recent_projects.insert(0, key)
if len(recent_projects) > self._max_recent_files:
if len(recent_projects) > self._maxrecent_files:
recent_projects.pop()
# write the recent file list
@@ -1087,9 +1090,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
project_id, project_name = project.split(":", maxsplit=1)
if project_id not in [p["project_id"] for p in Controller.instance().projects()]:
size -= 1
continue
action = self._recent_project_actions[index]
action = self.recent_project_actions[index]
if project_path and os.path.exists(project_path):
action.setText(" {}. {} [{}]".format(index + 1, project_name, project_path))
action.setData((project_id, project_path, ))
@@ -1100,16 +1104,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if Controller.instance().isRemote():
for index in range(0, size):
self._recent_project_actions[index].setVisible(True)
for index in range(size + 1, self._max_recent_files):
self._recent_project_actions[index].setVisible(False)
self.recent_project_actions[index].setVisible(True)
for index in range(size + 1, self._maxrecent_files):
self.recent_project_actions[index].setVisible(False)
if size:
self._recent_project_actions_separator.setVisible(True)
self.recent_project_actions_separator.setVisible(True)
else:
for action in self._recent_project_actions:
for action in self.recent_project_actions:
action.setVisible(False)
self._recent_project_actions_separator.setVisible(False)
self.recent_project_actions_separator.setVisible(False)
def updateRecentFileSettings(self, path):
"""
@@ -1129,7 +1133,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if path in recent_files:
recent_files.remove(path)
recent_files.insert(0, path)
if len(recent_files) > self._max_recent_files:
if len(recent_files) > self._maxrecent_files:
recent_files.pop()
# write the recent file list
@@ -1146,7 +1150,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
for file_path in self._settings["recent_files"]:
try:
if file_path and os.path.exists(file_path):
action = self._recent_file_actions[index]
action = self.recent_file_actions[index]
action.setText(" {}. {}".format(index + 1, os.path.basename(file_path)))
action.setData(file_path)
action.setVisible(True)
@@ -1157,15 +1161,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
pass
if not Controller.instance().isRemote():
for index in range(size + 1, self._max_recent_files):
self._recent_file_actions[index].setVisible(False)
for index in range(size + 1, self._maxrecent_files):
self.recent_file_actions[index].setVisible(False)
if size:
self._recent_file_actions_separator.setVisible(True)
self.recent_file_actions_separator.setVisible(True)
else:
for index in range(0, self._max_recent_files):
self._recent_file_actions[index].setVisible(False)
self._recent_file_actions_separator.setVisible(False)
for index in range(0, self._maxrecent_files):
self.recent_file_actions[index].setVisible(False)
self.recent_file_actions_separator.setVisible(False)
def _controllerConnectedSlot(self):
self.updateRecentFileActions()
@@ -1202,11 +1206,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Topology.instance().importProject(path)
def _editProjectActionSlot(self):
if Topology.instance().project() is None:
return
dialog = EditProjectDialog(self)
dialog.show()
dialog.exec_()
def _deleteProjectActionSlot(self):
if Topology.instance().project() is None:
return
reply = QtWidgets.QMessageBox.warning(
self,
"GNS3",

View File

@@ -240,22 +240,26 @@ class Builtin(Module):
if isinstance(node, Cloud):
for key, info in self._cloud_nodes.items():
if node_name == info["name"]:
node.create(ports=info["ports_mapping"], default_name_format=info["default_name_format"])
default_name_format = info["default_name_format"].replace('{name}', node_name)
node.create(ports=info["ports_mapping"], default_name_format=default_name_format)
return
elif isinstance(node, Nat):
for key, info in self._nat_nodes.items():
if node_name == info["name"]:
node.create(default_name_format=info["default_name_format"])
default_name_format = info["default_name_format"].replace('{name}', node_name)
node.create(default_name_format=default_name_format)
return
elif isinstance(node, EthernetHub):
for key, info in self._ethernet_hubs.items():
if node_name == info["name"]:
node.create(ports=info["ports_mapping"], default_name_format=info["default_name_format"])
default_name_format = info["default_name_format"].replace('{name}', node_name)
node.create(ports=info["ports_mapping"], default_name_format=default_name_format)
return
elif isinstance(node, EthernetSwitch):
for key, info in self._ethernet_switches.items():
if node_name == info["name"]:
node.create(ports=info["ports_mapping"], default_name_format=info["default_name_format"])
default_name_format = info["default_name_format"].replace('{name}', node_name)
node.create(ports=info["ports_mapping"], default_name_format=default_name_format)
return
node.create()
@@ -334,7 +338,7 @@ class Builtin(Module):
"symbol": node_class.defaultSymbol(),
"builtin": True,
"node_type": node_class.URL_PREFIX
}
}
)
# add custom cloud node templates

View File

@@ -98,7 +98,7 @@ class EthernetHub(Node):
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
host=self.compute().id())
host=self.compute().name())
port_info = ""
for port in self._ports:

View File

@@ -98,7 +98,7 @@ class EthernetSwitch(Node):
""".format(name=self.name(),
id=self.id(),
node_id=self._node_id,
host=self.compute().id())
host=self.compute().name())
port_info = ""
for port in self._ports:
@@ -126,7 +126,7 @@ class EthernetSwitch(Node):
port_ethertype_info=port_ethertype_info,
port_vlan_info=port_vlan_info)
port_info += " {port_description}\n".format(port_description=port.description())
break
break
return info + port_info

View File

@@ -19,13 +19,12 @@
Configuration page for clouds.
"""
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.qt import QtCore, QtWidgets
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.controller import Controller
from gns3.node import Node
from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
from ..cloud import Cloud
class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
@@ -48,6 +47,7 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
# connect Ethernet slots
self.uiEthernetListWidget.itemSelectionChanged.connect(self._EthernetChangedSlot)
self.uiEthernetWarningPushButton.clicked.connect(self._EthernetWarningSlot)
self.uiAddEthernetPushButton.clicked.connect(self._EthernetAddSlot)
self.uiAddAllEthernetPushButton.clicked.connect(self._EthernetAddAllSlot)
self.uiDeleteEthernetPushButton.clicked.connect(self._EthernetDeleteSlot)
@@ -79,6 +79,13 @@ class CloudConfigurationPage(QtWidgets.QWidget, Ui_cloudConfigPageWidget):
else:
self.uiDeleteEthernetPushButton.setEnabled(False)
def _EthernetWarningSlot(self):
"""
Shows a warning about Wifi Ethernet interfaces.
"""
QtWidgets.QMessageBox.warning(self, "Ethernet interfaces", "Wifi interfaces may not work properly. It is recommended to use wired Ethernet or Loopback interfaces only.")
def _EthernetAddSlot(self, interface=None):
"""
Adds a new Ethernet interface.

View File

@@ -19,7 +19,7 @@
Configuration page for Ethernet hubs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -19,7 +19,7 @@
Configuration page for Ethernet switches.
"""
from gns3.qt import QtGui, QtCore, QtWidgets
from gns3.qt import QtCore, QtWidgets
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -6,13 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>459</width>
<height>419</height>
<width>540</width>
<height>553</height>
</rect>
</property>
<property name="windowTitle">
<string>ATM Switch</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is a simple ATM switch. Only IOS c7200 routers with at least a configured PA-A1 adapter can connect to it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="3">
<widget class="QGroupBox" name="uiGeneralGroupBox">

View File

@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/atm_switch_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
atmSwitchConfigPageWidget.resize(459, 419)
atmSwitchConfigPageWidget.resize(459, 430)
self.gridLayout_2 = QtWidgets.QGridLayout(atmSwitchConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiGeneralGroupBox = QtWidgets.QGroupBox(atmSwitchConfigPageWidget)
@@ -170,6 +168,7 @@ class Ui_atmSwitchConfigPageWidget(object):
def retranslateUi(self, atmSwitchConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
atmSwitchConfigPageWidget.setWindowTitle(_translate("atmSwitchConfigPageWidget", "ATM Switch"))
atmSwitchConfigPageWidget.setWhatsThis(_translate("atmSwitchConfigPageWidget", "<html><head/><body><p>This is a simple ATM switch. Only IOS c7200 routers with at least a configured PA-A1 adapter can connect to it.</p></body></html>"))
self.uiGeneralGroupBox.setTitle(_translate("atmSwitchConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("atmSwitchConfigPageWidget", "Name:"))
self.uiVPICheckBox.setText(_translate("atmSwitchConfigPageWidget", "Use VPI only (VP tunnel)"))
@@ -186,3 +185,4 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))

View File

@@ -6,13 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>758</width>
<height>299</height>
<width>821</width>
<height>363</height>
</rect>
</property>
<property name="windowTitle">
<string>Cloud configuration</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A cloud node allows you to connect your project to the &amp;quot;real world&amp;quot; (a network or host) using either an Ethernet interface, a TAP interface (Linux only) or even an UDP tunnel. &lt;span style=&quot; font-weight:600;&quot;&gt;Please be aware that Wifi interfaces may not work properly.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="uiTabWidget">
@@ -40,21 +43,21 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QPushButton" name="uiAddEthernetPushButton">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="3">
<widget class="QPushButton" name="uiAddAllEthernetPushButton">
<property name="text">
<string>&amp;Add all</string>
</property>
</widget>
</item>
<item row="0" column="3">
<item row="0" column="4">
<widget class="QPushButton" name="uiDeleteEthernetPushButton">
<property name="enabled">
<bool>false</bool>
@@ -64,7 +67,7 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<item row="1" column="0" colspan="5">
<widget class="QListWidget" name="uiEthernetListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
@@ -74,7 +77,17 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="0" column="1">
<widget class="QPushButton" name="uiEthernetWarningPushButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="dialog-warning"/>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="uiShowSpecialInterfacesCheckBox">
<property name="text">
<string>&amp;Show special Ethernet interfaces</string>
@@ -88,6 +101,7 @@
<zorder>uiDeleteEthernetPushButton</zorder>
<zorder>uiAddAllEthernetPushButton</zorder>
<zorder>uiShowSpecialInterfacesCheckBox</zorder>
<zorder>uiEthernetWarningPushButton</zorder>
</widget>
<widget class="QWidget" name="TAPTab">
<attribute name="title">

View File

@@ -2,8 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created: Fri Jun 10 16:26:54 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
@@ -33,21 +32,34 @@ class Ui_cloudConfigPageWidget(object):
self.gridLayout_3.addWidget(self.uiEthernetComboBox, 0, 0, 1, 1)
self.uiAddEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddEthernetPushButton.setObjectName("uiAddEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddEthernetPushButton, 0, 1, 1, 1)
self.gridLayout_3.addWidget(self.uiAddEthernetPushButton, 0, 2, 1, 1)
self.uiAddAllEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiAddAllEthernetPushButton.setObjectName("uiAddAllEthernetPushButton")
self.gridLayout_3.addWidget(self.uiAddAllEthernetPushButton, 0, 2, 1, 1)
self.gridLayout_3.addWidget(self.uiAddAllEthernetPushButton, 0, 3, 1, 1)
self.uiDeleteEthernetPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiDeleteEthernetPushButton.setEnabled(False)
self.uiDeleteEthernetPushButton.setObjectName("uiDeleteEthernetPushButton")
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 3, 1, 1)
self.gridLayout_3.addWidget(self.uiDeleteEthernetPushButton, 0, 4, 1, 1)
self.uiEthernetListWidget = QtWidgets.QListWidget(self.EthernetTab)
self.uiEthernetListWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.uiEthernetListWidget.setObjectName("uiEthernetListWidget")
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 4)
self.gridLayout_3.addWidget(self.uiEthernetListWidget, 1, 0, 1, 5)
self.uiEthernetWarningPushButton = QtWidgets.QPushButton(self.EthernetTab)
self.uiEthernetWarningPushButton.setText("")
icon = QtGui.QIcon.fromTheme("dialog-warning")
self.uiEthernetWarningPushButton.setIcon(icon)
self.uiEthernetWarningPushButton.setObjectName("uiEthernetWarningPushButton")
self.gridLayout_3.addWidget(self.uiEthernetWarningPushButton, 0, 1, 1, 1)
self.uiShowSpecialInterfacesCheckBox = QtWidgets.QCheckBox(self.EthernetTab)
self.uiShowSpecialInterfacesCheckBox.setObjectName("uiShowSpecialInterfacesCheckBox")
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 1)
self.gridLayout_3.addWidget(self.uiShowSpecialInterfacesCheckBox, 2, 0, 1, 2)
self.uiEthernetListWidget.raise_()
self.uiEthernetComboBox.raise_()
self.uiAddEthernetPushButton.raise_()
self.uiDeleteEthernetPushButton.raise_()
self.uiAddAllEthernetPushButton.raise_()
self.uiShowSpecialInterfacesCheckBox.raise_()
self.uiEthernetWarningPushButton.raise_()
self.uiTabWidget.addTab(self.EthernetTab, "")
self.TAPTab = QtWidgets.QWidget()
self.TAPTab.setObjectName("TAPTab")
@@ -225,6 +237,7 @@ class Ui_cloudConfigPageWidget(object):
def retranslateUi(self, cloudConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
cloudConfigPageWidget.setWindowTitle(_translate("cloudConfigPageWidget", "Cloud configuration"))
cloudConfigPageWidget.setWhatsThis(_translate("cloudConfigPageWidget", "<html><head/><body><p>A cloud node allows you to connect your project to the &quot;real world&quot; (a network or host) using either an Ethernet interface, a TAP interface (Linux only) or even an UDP tunnel. <span style=\" font-weight:600;\">Please be aware that Wifi interfaces may not work properly.</span></p></body></html>"))
self.uiAddEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add"))
self.uiAddAllEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add all"))
self.uiDeleteEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete"))

View File

@@ -105,13 +105,13 @@
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
<number>0</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>1</number>
<number>0</number>
</property>
</widget>
</item>

View File

@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/ethernet_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/builtin/ui/ethernet_switch_configuration_page.ui'
#
# Created: Fri Jun 10 20:45:43 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ethernetSwitchConfigPageWidget(object):
def setupUi(self, ethernetSwitchConfigPageWidget):
ethernetSwitchConfigPageWidget.setObjectName("ethernetSwitchConfigPageWidget")
ethernetSwitchConfigPageWidget.resize(545, 435)
@@ -69,9 +70,9 @@ class Ui_ethernetSwitchConfigPageWidget(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiPortSpinBox.sizePolicy().hasHeightForWidth())
self.uiPortSpinBox.setSizePolicy(sizePolicy)
self.uiPortSpinBox.setMinimum(1)
self.uiPortSpinBox.setMinimum(0)
self.uiPortSpinBox.setMaximum(65535)
self.uiPortSpinBox.setProperty("value", 1)
self.uiPortSpinBox.setProperty("value", 0)
self.uiPortSpinBox.setObjectName("uiPortSpinBox")
self.gridlayout.addWidget(self.uiPortSpinBox, 0, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(self.uiEthernetSwitchSettingsGroupBox)
@@ -174,4 +175,3 @@ class Ui_ethernetSwitchConfigPageWidget(object):
self.uiPortsTreeWidget.headerItem().setText(3, _translate("ethernetSwitchConfigPageWidget", "EtherType"))
self.uiAddPushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Delete"))

View File

@@ -7,12 +7,15 @@
<x>0</x>
<y>0</y>
<width>499</width>
<height>405</height>
<height>414</height>
</rect>
</property>
<property name="windowTitle">
<string>Frame Relay Switch</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is a simple Frame Relay switch. Only serial links can be connected to it. &lt;span style=&quot; font-weight:600;&quot;&gt;Note that only the Frame-Relay LMI AINSI type is supported.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="uiGeneralGroupBox">

View File

@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/frame_relay_switch_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/frame_relay_switch_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_frameRelaySwitchConfigPageWidget(object):
def setupUi(self, frameRelaySwitchConfigPageWidget):
frameRelaySwitchConfigPageWidget.setObjectName("frameRelaySwitchConfigPageWidget")
frameRelaySwitchConfigPageWidget.resize(499, 405)
frameRelaySwitchConfigPageWidget.resize(499, 414)
self.gridLayout_2 = QtWidgets.QGridLayout(frameRelaySwitchConfigPageWidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiGeneralGroupBox = QtWidgets.QGroupBox(frameRelaySwitchConfigPageWidget)
@@ -136,6 +134,7 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
def retranslateUi(self, frameRelaySwitchConfigPageWidget):
_translate = QtCore.QCoreApplication.translate
frameRelaySwitchConfigPageWidget.setWindowTitle(_translate("frameRelaySwitchConfigPageWidget", "Frame Relay Switch"))
frameRelaySwitchConfigPageWidget.setWhatsThis(_translate("frameRelaySwitchConfigPageWidget", "<html><head/><body><p>This is a simple Frame Relay switch. Only serial links can be connected to it. <span style=\" font-weight:600;\">Note that only the Frame-Relay LMI AINSI type is supported.</span></p></body></html>"))
self.uiGeneralGroupBox.setTitle(_translate("frameRelaySwitchConfigPageWidget", "General"))
self.uiNameLabel.setText(_translate("frameRelaySwitchConfigPageWidget", "Name:"))
self.uiFrameRelayMappingGroupBox.setTitle(_translate("frameRelaySwitchConfigPageWidget", "Mapping"))
@@ -149,3 +148,4 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
self.uiDestinationDLCILabel.setText(_translate("frameRelaySwitchConfigPageWidget", "DLCI:"))
self.uiAddPushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Add"))
self.uiDeletePushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Delete"))

View File

@@ -19,7 +19,7 @@
Configuration page for Docker images.
"""
from gns3.qt import QtWidgets, QtGui
from gns3.qt import QtWidgets
from ..ui.docker_vm_configuration_page_ui import Ui_dockerVMConfigPageWidget
from ....dialogs.file_editor_dialog import FileEditorDialog
@@ -27,9 +27,10 @@ from ....dialogs.node_properties_dialog import ConfigurationError
from ....dialogs.symbol_selection_dialog import SymbolSelectionDialog
class DockerVMConfigurationPage(
QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
"""QWidget configuration page for Docker images."""
class DockerVMConfigurationPage(QtWidgets.QWidget, Ui_dockerVMConfigPageWidget):
"""
QWidget configuration page for Docker images
"""
def __init__(self):

View File

@@ -24,6 +24,9 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="1">
<widget class="QLineEdit" name="uiNameLineEdit"/>
</item>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/docker/ui/docker_vm_configuration_page.ui'
#
# Created: Tue May 31 21:37:32 2016
# Created: Thu Jan 5 14:49:45 2017
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,6 +20,7 @@ class Ui_dockerVMConfigPageWidget(object):
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setContentsMargins(10, 10, 10, 10)
self.gridLayout.setObjectName("gridLayout")
self.uiNameLineEdit = QtWidgets.QLineEdit(self.tab)
self.uiNameLineEdit.setObjectName("uiNameLineEdit")

View File

@@ -24,9 +24,7 @@ import re
from gns3.node import Node
from gns3.utils.normalize_filename import normalize_filename
from gns3.image_manager import ImageManager
from ..settings import PLATFORMS_DEFAULT_RAM
from ..adapters import ADAPTER_MATRIX
from ..wics import WIC_MATRIX
@@ -139,7 +137,7 @@ class Router(Node):
:param result: server response
"""
self._dynamips_id = result["dynamips_id"]
self._dynamips_id = result.get("dynamips_id")
def update(self, new_settings):
"""
@@ -454,15 +452,12 @@ class Router(Node):
try:
contents = os.listdir(directory)
except OSError as e:
self.warning_signal.emit(self.id(), "Configuration could not be loaded from directory {}: {}".format(directory, e))
return
startup_config = normalize_filename(self.name()) + "_startup-config.cfg"
private_config = normalize_filename(self.name()) + "_private-config.cfg"
new_settings = {}
if startup_config in contents:
new_settings["startup_config"] = os.path.join(directory, startup_config)
else:
self.warning_signal.emit(self.id(), "no startup-config file could be found, expected file name: {}".format(startup_config))
if private_config in contents:
new_settings["private_config"] = os.path.join(directory, private_config)

View File

@@ -54,6 +54,8 @@ class DynamipsPreferencesPage(QtWidgets.QWidget, Ui_DynamipsPreferencesPageWidge
file_filter = "Executable (*.exe);;All files (*.*)"
dynamips_path = shutil.which("dynamips")
if sys.platform.startswith("darwin") and dynamips_path is None:
dynamips_path = "/Applications/GNS3.app/Contents/Resources/dynamips"
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select Dynamips", dynamips_path, file_filter)
if not path:
return

View File

@@ -188,7 +188,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
widget.setEnabled(False)
# load the available adapters to the correct slot for the corresponding platform and chassis
for slot_number, slot_adapters in ADAPTER_MATRIX[platform][chassis].items():
for slot_number, slot_adapters in ADAPTER_MATRIX[platform].get(chassis, {}).items():
self._widget_slots[slot_number].setEnabled(True)
if isinstance(slot_adapters, str):
@@ -598,11 +598,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if node:
settings["slot" + str(slot_number)] = node.settings().get("slot" + str(slot_number))
if settings["slot" + str(slot_number)] and settings["slot" + str(slot_number)] != module:
if settings.get("slot" + str(slot_number)) and settings.get("slot" + str(slot_number)) != module:
if node:
self._checkForLinkConnectedToAdapter(slot_number, settings, node)
settings["slot" + str(slot_number)] = module
elif settings["slot" + str(slot_number)]:
elif "slot" + str(slot_number) in settings and settings["slot" + str(slot_number)]:
if node:
self._checkForLinkConnectedToAdapter(slot_number, settings, node)
settings["slot" + str(slot_number)] = ""
@@ -619,7 +619,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if node:
self._checkForLinkConnectedToWIC(wic_number, settings, node)
settings["wic" + str(wic_number)] = wic_name
elif settings["wic" + str(wic_number)]:
elif "wic" + str(wic_number) in settings and settings["wic" + str(wic_number)]:
if node:
self._checkForLinkConnectedToWIC(wic_number, settings, node)
settings["wic" + str(wic_number)] = ""

View File

@@ -24,6 +24,9 @@
<string>General</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="margin">
<number>10</number>
</property>
<item row="15" column="1">
<spacer>
<property name="orientation">
@@ -238,6 +241,9 @@
<string>Memories and disks</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="uiRamLabel">
<property name="text">
@@ -413,6 +419,9 @@
<string>Slots</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiAdaptersGroupBox">
<property name="title">
@@ -611,6 +620,9 @@
<string>Advanced</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiSystemGroupBox">
<property name="title">
@@ -787,6 +799,9 @@
<string>Environment</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiPowerSuppliesGroupBox">
<property name="title">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
#
# Created: Tue May 31 21:42:36 2016
# Created: Thu Jan 5 14:49:45 2017
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,6 +20,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiGeneralPageWidget = QtWidgets.QWidget()
self.uiGeneralPageWidget.setObjectName("uiGeneralPageWidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiGeneralPageWidget)
self.gridLayout_2.setContentsMargins(10, 10, 10, 10)
self.gridLayout_2.setObjectName("gridLayout_2")
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 15, 1, 1, 1)
@@ -135,6 +136,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiMemoriesPageWidget = QtWidgets.QWidget()
self.uiMemoriesPageWidget.setObjectName("uiMemoriesPageWidget")
self.gridLayout_5 = QtWidgets.QGridLayout(self.uiMemoriesPageWidget)
self.gridLayout_5.setContentsMargins(10, 10, 10, 10)
self.gridLayout_5.setObjectName("gridLayout_5")
self.uiRamLabel = QtWidgets.QLabel(self.uiMemoriesPageWidget)
self.uiRamLabel.setObjectName("uiRamLabel")
@@ -216,6 +218,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiSlotsPageWidget = QtWidgets.QWidget()
self.uiSlotsPageWidget.setObjectName("uiSlotsPageWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiSlotsPageWidget)
self.verticalLayout.setContentsMargins(10, 10, 10, 10)
self.verticalLayout.setObjectName("verticalLayout")
self.uiAdaptersGroupBox = QtWidgets.QGroupBox(self.uiSlotsPageWidget)
self.uiAdaptersGroupBox.setObjectName("uiAdaptersGroupBox")
@@ -343,6 +346,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiAdvancedPageWidget = QtWidgets.QWidget()
self.uiAdvancedPageWidget.setObjectName("uiAdvancedPageWidget")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.uiAdvancedPageWidget)
self.verticalLayout_4.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.uiSystemGroupBox = QtWidgets.QGroupBox(self.uiAdvancedPageWidget)
self.uiSystemGroupBox.setObjectName("uiSystemGroupBox")
@@ -426,6 +430,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiEnvironmentPageWidget = QtWidgets.QWidget()
self.uiEnvironmentPageWidget.setObjectName("uiEnvironmentPageWidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiEnvironmentPageWidget)
self.verticalLayout_3.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiPowerSuppliesGroupBox = QtWidgets.QGroupBox(self.uiEnvironmentPageWidget)
self.uiPowerSuppliesGroupBox.setObjectName("uiPowerSuppliesGroupBox")

View File

@@ -63,14 +63,6 @@ class IOU(Module):
"""
self._settings = LocalConfig.instance().loadSectionSettings(self.__class__.__name__, IOU_SETTINGS)
if sys.platform.startswith("linux") and not os.path.exists(self._settings["iouyap_path"]):
iouyap_path = shutil.which("iouyap")
if iouyap_path:
self._settings["iouyap_path"] = os.path.abspath(iouyap_path)
else:
self._settings["iouyap_path"] = ""
self._loadIOUDevices()
def _saveSettings(self):

View File

@@ -167,7 +167,7 @@ class IOUDevice(Node):
node_id=self._node_id,
state=state,
memories_info=memories_info,
host=self.compute().id(),
host=self.compute().name(),
console=self._settings["console"],
image_name=os.path.basename(self._settings["path"]),
nb_ethernet=self._settings["ethernet_adapters"],
@@ -225,7 +225,7 @@ class IOUDevice(Node):
except OSError as e:
self.error_signal.emit(self.id(), "could not export startup-config to {}: {}".format(startup_config_path, e))
if "private_config_content" in result:
if "private_config_content" in result and result["private_config_content"] is not None and len(result["private_config_content"]) > 0:
private_config_path = os.path.join(export_directory, normalize_filename(self.name())) + "_private-config.cfg"
try:
with open(private_config_path, "wb") as f:
@@ -268,8 +268,6 @@ class IOUDevice(Node):
new_settings = {}
if startup_config in contents:
new_settings["startup_config"] = os.path.join(directory, startup_config)
else:
self.warning_signal.emit(self.id(), "no startup-config file could be found, expected file name: {}".format(startup_config))
if private_config in contents:
new_settings["private_config"] = os.path.join(directory, private_config)

View File

@@ -21,7 +21,7 @@ Configuration page for IOU devices.
import os
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog

View File

@@ -22,7 +22,7 @@ Configuration page for IOU device preferences.
import copy
import os
from gns3.qt import QtCore, QtGui, QtWidgets, qpartial
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.main_window import MainWindow
from gns3.dialogs.configuration_dialog import ConfigurationDialog
@@ -80,7 +80,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
except KeyError:
# Compute doesn't exists
pass
QtWidgets.QTreeWidgetItem(section_item, ["Image:", iou_device["image"]])
QtWidgets.QTreeWidgetItem(section_item, ["Image:", iou_device["path"]])
if iou_device["startup_config"]:
QtWidgets.QTreeWidgetItem(section_item, ["Startup-config:", iou_device["startup_config"]])
@@ -169,7 +169,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
del self._iou_devices[key]
item.setText(0, iou_device["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(iou_device)
self._refreshInfo(dialog.settings)
def _iouDeviceDeleteSlot(self):
"""
@@ -237,7 +237,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
path, _ = QtWidgets.QFileDialog.getOpenFileName(parent,
"Select an IOU image",
cls._default_images_dir,
"All files (*)",
"All file (*);;IOU image (*.bin *.image)",
"IOU image (*.bin *.image)")
if not path:

View File

@@ -44,10 +44,9 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
# connect signals
self.uiIOURCPathToolButton.clicked.connect(self._iourcPathBrowserSlot)
self.uiIouyapPathToolButton.clicked.connect(self._iouyapPathBrowserSlot)
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
#if not sys.platform.startswith("linux"):
# if not sys.platform.startswith("linux"):
# self.uiUseLocalServercheckBox.setChecked(False)
# self.uiUseLocalServercheckBox.setEnabled(False)
@@ -76,41 +75,6 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
self.IOULicenceTextEdit.setPlainText(content)
def _iouyapPathBrowserSlot(self):
"""
Slot to open a file browser and select iouyap.
"""
filter = ""
if sys.platform.startswith("win"):
filter = "Executable (*.exe);;All files (*.*)"
iouyap_path = shutil.which("iouyap")
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select iouyap", iouyap_path, filter)
if not path:
return
if self._checkIouyapPath(path):
self.uiIouyapPathLineEdit.setText(os.path.normpath(path))
def _checkIouyapPath(self, path):
"""
Checks that the iouyap path is valid.
:param path: iouyap path
:returns: boolean
"""
if not os.path.exists(path):
QtWidgets.QMessageBox.critical(self, "iouyap", '"{}" does not exist'.format(path))
return False
if not os.access(path, os.X_OK):
QtWidgets.QMessageBox.critical(self, "iouyap", "{} is not an executable".format(os.path.basename(path)))
return False
return True
def _restoreDefaultsSlot(self):
"""
Slot to populate the page widgets with the default settings.
@@ -124,12 +88,8 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
"""
if state:
self.uiIouyapPathLineEdit.setEnabled(True)
self.uiIouyapPathToolButton.setEnabled(True)
self.uiLicensecheckBox.setEnabled(True)
else:
self.uiIouyapPathLineEdit.setEnabled(False)
self.uiIouyapPathToolButton.setEnabled(False)
self.uiLicensecheckBox.setEnabled(False)
def _populateWidgets(self, settings):
@@ -140,7 +100,6 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
"""
self.IOULicenceTextEdit.setPlainText(settings["iourc_content"])
self.uiIouyapPathLineEdit.setText(settings["iouyap_path"])
self.uiLicensecheckBox.setChecked(settings["license_check"])
def loadPreferences(self):
@@ -156,13 +115,9 @@ class IOUPreferencesPage(QtWidgets.QWidget, Ui_IOUPreferencesPageWidget):
Saves IOU preferences.
"""
iouyap_path = self.uiIouyapPathLineEdit.text().strip()
if iouyap_path and not self._checkIouyapPath(iouyap_path):
return
iourc_content = self.IOULicenceTextEdit.toPlainText().strip().replace("\r\n", "\n")
new_settings = {"iouyap_path": iouyap_path,
"iourc_content": iourc_content,
"license_check": self.uiLicensecheckBox.isChecked()}
new_settings = {
"iourc_content": iourc_content,
"license_check": self.uiLicensecheckBox.isChecked()}
IOU.instance().setSettings(new_settings)

View File

@@ -25,7 +25,6 @@ from gns3.node import Node
IOU_SETTINGS = {
"iourc_content": "",
"iouyap_path": "",
"license_check": True,
}

View File

@@ -24,6 +24,9 @@
<string>General settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiGeneralgroupBox">
<property name="styleSheet">
@@ -288,6 +291,9 @@
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
#
# Created: Tue May 31 21:46:30 2016
# Created: Thu Jan 5 14:49:45 2017
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -20,6 +20,7 @@ class Ui_iouDeviceConfigPageWidget(object):
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tab)
self.verticalLayout_2.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiGeneralgroupBox = QtWidgets.QGroupBox(self.tab)
self.uiGeneralgroupBox.setStyleSheet("")
@@ -150,6 +151,7 @@ class Ui_iouDeviceConfigPageWidget(object):
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.gridLayout_5 = QtWidgets.QGridLayout(self.tab_2)
self.gridLayout_5.setContentsMargins(10, 10, 10, 10)
self.gridLayout_5.setObjectName("gridLayout_5")
self.groupBox = QtWidgets.QGroupBox(self.tab_2)
self.groupBox.setObjectName("groupBox")

View File

@@ -15,117 +15,49 @@
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QTabWidget" name="uiTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="uiGeneralSettingsTabWidget">
<attribute name="title">
<string>Local settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>10</number>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPlainTextEdit" name="IOULicenceTextEdit">
<property name="toolTip">
<string>A license is required to run IOU. Copy &amp; paste the content of your iourc file here or use the browse button to select a file. The license will be pushed to remote servers.</string>
</property>
<item>
<widget class="QLabel" name="uiIouyapPathLabel">
<property name="text">
<string>Path to iouyap:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="uiIouyapPathLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiIouyapPathToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="uiLicensecheckBox">
<property name="text">
<string>Check for a valid IOU license key</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>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiLicenseTabWidget">
<attribute name="title">
<string>License</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPlainTextEdit" name="IOULicenceTextEdit">
<property name="toolTip">
<string>A license is required to run IOU. Copy &amp; paste the content of your iourc file here or use the browse button to select a file. The license will be pushed to remote servers.</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiIOURCPathToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>185</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiIOURCPathToolButton">
<property name="text">
<string>&amp;Browse...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="uiLicensecheckBox">
<property name="text">
<string>Check for a valid IOU license key</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>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>

View File

@@ -1,92 +1,53 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
#
# Created: Wed Dec 7 21:53:01 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.7.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_IOUPreferencesPageWidget(object):
def setupUi(self, IOUPreferencesPageWidget):
IOUPreferencesPageWidget.setObjectName("IOUPreferencesPageWidget")
IOUPreferencesPageWidget.resize(490, 532)
self.vboxlayout = QtWidgets.QVBoxLayout(IOUPreferencesPageWidget)
self.vboxlayout.setObjectName("vboxlayout")
self.uiTabWidget = QtWidgets.QTabWidget(IOUPreferencesPageWidget)
self.uiTabWidget.setObjectName("uiTabWidget")
self.uiGeneralSettingsTabWidget = QtWidgets.QWidget()
self.uiGeneralSettingsTabWidget.setObjectName("uiGeneralSettingsTabWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiGeneralSettingsTabWidget)
self.verticalLayout.setContentsMargins(10, 10, 10, 10)
self.verticalLayout.setObjectName("verticalLayout")
self.uiIouyapPathLabel = QtWidgets.QLabel(self.uiGeneralSettingsTabWidget)
self.uiIouyapPathLabel.setObjectName("uiIouyapPathLabel")
self.verticalLayout.addWidget(self.uiIouyapPathLabel)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.uiIouyapPathLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTabWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiIouyapPathLineEdit.sizePolicy().hasHeightForWidth())
self.uiIouyapPathLineEdit.setSizePolicy(sizePolicy)
self.uiIouyapPathLineEdit.setObjectName("uiIouyapPathLineEdit")
self.horizontalLayout_6.addWidget(self.uiIouyapPathLineEdit)
self.uiIouyapPathToolButton = QtWidgets.QToolButton(self.uiGeneralSettingsTabWidget)
self.uiIouyapPathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiIouyapPathToolButton.setObjectName("uiIouyapPathToolButton")
self.horizontalLayout_6.addWidget(self.uiIouyapPathToolButton)
self.verticalLayout.addLayout(self.horizontalLayout_6)
self.uiLicensecheckBox = QtWidgets.QCheckBox(self.uiGeneralSettingsTabWidget)
self.uiLicensecheckBox.setChecked(True)
self.uiLicensecheckBox.setObjectName("uiLicensecheckBox")
self.verticalLayout.addWidget(self.uiLicensecheckBox)
spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.uiTabWidget.addTab(self.uiGeneralSettingsTabWidget, "")
self.uiLicenseTabWidget = QtWidgets.QWidget()
self.uiLicenseTabWidget.setObjectName("uiLicenseTabWidget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiLicenseTabWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.IOULicenceTextEdit = QtWidgets.QPlainTextEdit(self.uiLicenseTabWidget)
self.IOULicenceTextEdit = QtWidgets.QPlainTextEdit(IOUPreferencesPageWidget)
self.IOULicenceTextEdit.setObjectName("IOULicenceTextEdit")
self.horizontalLayout_5.addWidget(self.IOULicenceTextEdit)
self.uiIOURCPathToolButton = QtWidgets.QToolButton(self.uiLicenseTabWidget)
self.uiIOURCPathToolButton = QtWidgets.QToolButton(IOUPreferencesPageWidget)
self.uiIOURCPathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiIOURCPathToolButton.setObjectName("uiIOURCPathToolButton")
self.horizontalLayout_5.addWidget(self.uiIOURCPathToolButton)
self.verticalLayout_2.addLayout(self.horizontalLayout_5)
spacerItem1 = QtWidgets.QSpacerItem(20, 185, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem1)
self.uiTabWidget.addTab(self.uiLicenseTabWidget, "")
self.vboxlayout.addWidget(self.uiTabWidget)
self.vboxlayout.addLayout(self.horizontalLayout_5)
self.uiLicensecheckBox = QtWidgets.QCheckBox(IOUPreferencesPageWidget)
self.uiLicensecheckBox.setChecked(True)
self.uiLicensecheckBox.setObjectName("uiLicensecheckBox")
self.vboxlayout.addWidget(self.uiLicensecheckBox)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.vboxlayout.addItem(spacerItem)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem2 = QtWidgets.QSpacerItem(164, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
spacerItem1 = QtWidgets.QSpacerItem(164, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.uiRestoreDefaultsPushButton = QtWidgets.QPushButton(IOUPreferencesPageWidget)
self.uiRestoreDefaultsPushButton.setObjectName("uiRestoreDefaultsPushButton")
self.horizontalLayout_2.addWidget(self.uiRestoreDefaultsPushButton)
self.vboxlayout.addLayout(self.horizontalLayout_2)
self.retranslateUi(IOUPreferencesPageWidget)
self.uiTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(IOUPreferencesPageWidget)
def retranslateUi(self, IOUPreferencesPageWidget):
_translate = QtCore.QCoreApplication.translate
IOUPreferencesPageWidget.setWindowTitle(_translate("IOUPreferencesPageWidget", "IOS on UNIX"))
self.uiIouyapPathLabel.setText(_translate("IOUPreferencesPageWidget", "Path to iouyap:"))
self.uiIouyapPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
self.uiLicensecheckBox.setText(_translate("IOUPreferencesPageWidget", "Check for a valid IOU license key"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("IOUPreferencesPageWidget", "Local settings"))
self.IOULicenceTextEdit.setToolTip(_translate("IOUPreferencesPageWidget", "A license is required to run IOU. Copy & paste the content of your iourc file here or use the browse button to select a file. The license will be pushed to remote servers."))
self.uiIOURCPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiLicenseTabWidget), _translate("IOUPreferencesPageWidget", "License"))
self.uiLicensecheckBox.setText(_translate("IOUPreferencesPageWidget", "Check for a valid IOU license key"))
self.uiRestoreDefaultsPushButton.setText(_translate("IOUPreferencesPageWidget", "Restore defaults"))

View File

@@ -146,6 +146,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
:return: settings dict
"""
console_type = self.uiQemuConsoleTypeComboBox.itemText(self.uiQemuConsoleTypeComboBox.currentIndex())
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
settings = {
"name": self.uiNameLineEdit.text(),
@@ -154,6 +155,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
"server": self._compute_id,
"category": Node.end_devices,
"hda_disk_image": self.uiHdaDiskImageLineEdit.text(),
"console_type": console_type
}
if self.uiLegacyASACheckBox.isChecked():

View File

@@ -26,7 +26,7 @@ from collections import OrderedDict
from gns3.modules.qemu.dialogs.qemu_image_wizard import QemuImageWizard
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node
from gns3.qt import QtGui, QtCore, QtWidgets, qpartial
from gns3.qt import QtCore, QtWidgets, qpartial
from gns3.modules.module_error import ModuleError
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.image_manager import ImageManager
@@ -67,7 +67,7 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
self.uiHdcDiskImageCreateToolButton.clicked.connect(self._hdcDiskImageCreateSlot)
self.uiHddDiskImageCreateToolButton.clicked.connect(self._hddDiskImageCreateSlot)
disk_interfaces = ["ide", "scsi", "sd", "mtd", "floppy", "pflash", "virtio"]
disk_interfaces = ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"]
self.uiHdaDiskInterfaceComboBox.addItems(disk_interfaces)
self.uiHdbDiskInterfaceComboBox.addItems(disk_interfaces)
self.uiHdcDiskInterfaceComboBox.addItems(disk_interfaces)

View File

@@ -148,7 +148,7 @@ class QemuVM(Node):
id=self.id(),
node_id=self._node_id,
state=state,
host=self.compute().id(),
host=self.compute().name(),
console=self._settings["console"],
console_type=self._settings["console_type"])

View File

@@ -24,6 +24,9 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<property name="margin">
<number>10</number>
</property>
<item row="4" column="0">
<widget class="QLabel" name="uiRamLabel">
<property name="text">
@@ -186,6 +189,9 @@
<string>HDD</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiHdaGroupBox">
<property name="title">
@@ -406,6 +412,9 @@
<string>CD/DVD</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiCdromGroupBox">
<property name="title">
@@ -459,6 +468,9 @@
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="margin">
<number>10</number>
</property>
<item row="6" column="1">
<widget class="QComboBox" name="uiAdapterTypesComboBox">
<property name="sizePolicy">
@@ -580,6 +592,9 @@
<string>Advanced settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="uiLinuxBootGroupBox">
<property name="title">

View File

@@ -1,16 +1,15 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.6
# Created: Thu Jan 5 14:49:45 2017
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(594, 645)
@@ -21,7 +20,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiGeneralSettingsTab = QtWidgets.QWidget()
self.uiGeneralSettingsTab.setObjectName("uiGeneralSettingsTab")
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiGeneralSettingsTab)
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.gridLayout_4.setContentsMargins(10, 10, 10, 10)
self.gridLayout_4.setObjectName("gridLayout_4")
self.uiRamLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
self.uiRamLabel.setObjectName("uiRamLabel")
@@ -102,7 +101,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiHddTab = QtWidgets.QWidget()
self.uiHddTab.setObjectName("uiHddTab")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiHddTab)
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_3.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.uiHdaGroupBox = QtWidgets.QGroupBox(self.uiHddTab)
self.uiHdaGroupBox.setObjectName("uiHdaGroupBox")
@@ -218,7 +217,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiCdromTab = QtWidgets.QWidget()
self.uiCdromTab.setObjectName("uiCdromTab")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.uiCdromTab)
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_4.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.uiCdromGroupBox = QtWidgets.QGroupBox(self.uiCdromTab)
self.uiCdromGroupBox.setObjectName("uiCdromGroupBox")
@@ -244,7 +243,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiNetworkTab = QtWidgets.QWidget()
self.uiNetworkTab.setObjectName("uiNetworkTab")
self.gridLayout_5 = QtWidgets.QGridLayout(self.uiNetworkTab)
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
self.gridLayout_5.setContentsMargins(10, 10, 10, 10)
self.gridLayout_5.setObjectName("gridLayout_5")
self.uiAdapterTypesComboBox = QtWidgets.QComboBox(self.uiNetworkTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
@@ -306,7 +305,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdvancedSettingsTab = QtWidgets.QWidget()
self.uiAdvancedSettingsTab.setObjectName("uiAdvancedSettingsTab")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiAdvancedSettingsTab)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setContentsMargins(10, 10, 10, 10)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiLinuxBootGroupBox = QtWidgets.QGroupBox(self.uiAdvancedSettingsTab)
self.uiLinuxBootGroupBox.setObjectName("uiLinuxBootGroupBox")
@@ -405,10 +404,6 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiACPIShutdownCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiACPIShutdownCheckBox.setObjectName("uiACPIShutdownCheckBox")
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 2, 0, 1, 2)
self.uiQemuOptionsLineEdit.raise_()
self.uiQemuOptionsLabel.raise_()
self.uiACPIShutdownCheckBox.raise_()
self.uiBaseVMCheckBox.raise_()
self.verticalLayout_2.addWidget(self.groupBox)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
@@ -496,3 +491,4 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
self.uiACPIShutdownCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable ACPI shutdown (experimental)"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced settings"))

View File

@@ -165,6 +165,50 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiConsoleTypeWizardPage">
<property name="title">
<string>Console type</string>
</property>
<property name="subTitle">
<string>Please choose the console type. Telnet will connect to the serial console of the machine. VNC will connect to graphical output of the machine.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="uiQemuConsoleTypeComboBox">
<item>
<property name="text">
<string>telnet</string>
</property>
</item>
<item>
<property name="text">
<string>vnc</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Note: You don't need to install anything on the VM itself.</string>
</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>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiDiskWizardPage">
<property name="title">
<string>Disk image</string>

View File

@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
#
# Created: Tue Sep 20 17:45:47 2016
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMWizard(object):
def setupUi(self, QemuVMWizard):
QemuVMWizard.setObjectName("QemuVMWizard")
QemuVMWizard.resize(623, 417)
@@ -90,6 +91,21 @@ class Ui_QemuVMWizard(object):
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
self.gridLayout_2.addWidget(self.uiRamSpinBox, 1, 1, 1, 1)
QemuVMWizard.addPage(self.uiBinaryMemoryWizardPage)
self.uiConsoleTypeWizardPage = QtWidgets.QWizardPage()
self.uiConsoleTypeWizardPage.setObjectName("uiConsoleTypeWizardPage")
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiConsoleTypeWizardPage)
self.verticalLayout.setObjectName("verticalLayout")
self.uiQemuConsoleTypeComboBox = QtWidgets.QComboBox(self.uiConsoleTypeWizardPage)
self.uiQemuConsoleTypeComboBox.setObjectName("uiQemuConsoleTypeComboBox")
self.uiQemuConsoleTypeComboBox.addItem("")
self.uiQemuConsoleTypeComboBox.addItem("")
self.verticalLayout.addWidget(self.uiQemuConsoleTypeComboBox)
self.label = QtWidgets.QLabel(self.uiConsoleTypeWizardPage)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
QemuVMWizard.addPage(self.uiConsoleTypeWizardPage)
self.uiDiskWizardPage = QtWidgets.QWizardPage()
self.uiDiskWizardPage.setObjectName("uiDiskWizardPage")
self.gridLayout_3 = QtWidgets.QGridLayout(self.uiDiskWizardPage)
@@ -104,8 +120,8 @@ class Ui_QemuVMWizard(object):
self.uiNewImageRadioButton_2.setChecked(False)
self.uiNewImageRadioButton_2.setObjectName("uiNewImageRadioButton_2")
self.horizontalLayout_3.addWidget(self.uiNewImageRadioButton_2)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem1)
self.gridLayout_3.addLayout(self.horizontalLayout_3, 0, 0, 1, 1)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
@@ -146,8 +162,8 @@ class Ui_QemuVMWizard(object):
self.uiNewImageRadioButton_4.setChecked(False)
self.uiNewImageRadioButton_4.setObjectName("uiNewImageRadioButton_4")
self.horizontalLayout_7.addWidget(self.uiNewImageRadioButton_4)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem1)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem2)
self.gridLayout_4.addLayout(self.horizontalLayout_7, 0, 0, 1, 1)
self.formLayout_2 = QtWidgets.QFormLayout()
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
@@ -214,6 +230,11 @@ class Ui_QemuVMWizard(object):
self.uiQemuListLabel.setText(_translate("QemuVMWizard", "Qemu binary:"))
self.uiRamLabel.setText(_translate("QemuVMWizard", "RAM:"))
self.uiRamSpinBox.setSuffix(_translate("QemuVMWizard", " MB"))
self.uiConsoleTypeWizardPage.setTitle(_translate("QemuVMWizard", "Console type"))
self.uiConsoleTypeWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose the console type. Telnet will connect to the serial console of the machine. VNC will connect to graphical output of the machine."))
self.uiQemuConsoleTypeComboBox.setItemText(0, _translate("QemuVMWizard", "telnet"))
self.uiQemuConsoleTypeComboBox.setItemText(1, _translate("QemuVMWizard", "vnc"))
self.label.setText(_translate("QemuVMWizard", "Note: You don\'t need to install anything on the VM itself."))
self.uiDiskWizardPage.setTitle(_translate("QemuVMWizard", "Disk image"))
self.uiDiskWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a base disk image for your virtual machine."))
self.uiHdaDiskExistingImageRadioButton.setText(_translate("QemuVMWizard", "Existing image"))
@@ -229,4 +250,3 @@ class Ui_QemuVMWizard(object):
self.uiKernelImageLabel.setText(_translate("QemuVMWizard", "Kernel image (vmlinuz):"))
self.uiKernelImageToolButton.setText(_translate("QemuVMWizard", "&Browse..."))
self.uiInitrdLabel.setText(_translate("QemuVMWizard", "Initial RAM disk (initrd):"))

View File

@@ -19,7 +19,7 @@
Configuration page for VirtualBox VMs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -24,6 +24,9 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>10</number>
</property>
<item row="4" column="1">
<widget class="QComboBox" name="uiVMListComboBox">
<property name="sizePolicy">
@@ -165,6 +168,9 @@
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="margin">
<number>10</number>
</property>
<item row="3" column="1">
<widget class="QSpinBox" name="uiPortSegmentSizeSpinBox">
<property name="maximum">

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.6
# Created: Thu Jan 5 14:43:49 2017
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -19,7 +20,7 @@ class Ui_virtualBoxVMConfigPageWidget(object):
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setContentsMargins(10, 10, 10, 10)
self.gridLayout.setObjectName("gridLayout")
self.uiVMListComboBox = QtWidgets.QComboBox(self.tab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
@@ -92,7 +93,7 @@ class Ui_virtualBoxVMConfigPageWidget(object):
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_2)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setContentsMargins(10, 10, 10, 10)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiPortSegmentSizeSpinBox = QtWidgets.QSpinBox(self.tab_2)
self.uiPortSegmentSizeSpinBox.setMaximum(128)

View File

@@ -102,11 +102,6 @@ class VirtualBoxVM(Node):
:param new_settings: settings (dict)
"""
if "name" in new_settings and new_settings["name"] != self.name():
if self._linked_clone:
# forces the update of the VM name in VirtualBox.
new_settings["vmname"] = new_settings["name"]
params = {}
for name, value in new_settings.items():
if name in self._settings and self._settings[name] != value:
@@ -139,7 +134,7 @@ class VirtualBoxVM(Node):
state=state,
vmname=self._settings["vmname"],
ram=self._settings["ram"],
host=self.compute().id(),
host=self.compute().name(),
console=self._settings["console"])
port_info = ""

View File

@@ -19,7 +19,7 @@
Configuration page for VMware VMs.
"""
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.dialogs.node_properties_dialog import ConfigurationError
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.node import Node

View File

@@ -24,6 +24,18 @@
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="3" column="1">
<widget class="QComboBox" name="uiCategoryComboBox"/>
</item>
@@ -125,6 +137,18 @@
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="2" column="2">
<widget class="QLineEdit" name="uiPortNameFormatLineEdit">
<property name="text">
@@ -208,8 +232,11 @@
</item>
<item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="uiUseAnyAdapterCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This option will allow GNS3 to replace all adapters of the VM. This allow you to use any interface of the VM in GNS3.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Allow GNS3 to use any configured VMware adapter</string>
<string>Allow GNS3 to override non custom VMware adapter</string>
</property>
</widget>
</item>

View File

@@ -2,13 +2,15 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/vmware/ui/vmware_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.6
# Created by: PyQt5 UI code generator 5.8
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_VMwareVMConfigPageWidget(object):
def setupUi(self, VMwareVMConfigPageWidget):
VMwareVMConfigPageWidget.setObjectName("VMwareVMConfigPageWidget")
VMwareVMConfigPageWidget.resize(546, 470)
@@ -19,7 +21,7 @@ class Ui_VMwareVMConfigPageWidget(object):
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setContentsMargins(10, 10, 10, 10)
self.gridLayout.setObjectName("gridLayout")
self.uiCategoryComboBox = QtWidgets.QComboBox(self.tab)
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
@@ -69,7 +71,7 @@ class Ui_VMwareVMConfigPageWidget(object):
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_2)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setContentsMargins(10, 10, 10, 10)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiPortNameFormatLineEdit = QtWidgets.QLineEdit(self.tab_2)
self.uiPortNameFormatLineEdit.setText("")
@@ -140,7 +142,7 @@ class Ui_VMwareVMConfigPageWidget(object):
self.uiPortSegmentSizeLabel.setText(_translate("VMwareVMConfigPageWidget", "Segment size:"))
self.uiAdaptersLabel.setText(_translate("VMwareVMConfigPageWidget", "Adapters:"))
self.label.setText(_translate("VMwareVMConfigPageWidget", "Type:"))
self.uiUseAnyAdapterCheckBox.setText(_translate("VMwareVMConfigPageWidget", "Allow GNS3 to use any configured VMware adapter"))
self.uiUseAnyAdapterCheckBox.setToolTip(_translate("VMwareVMConfigPageWidget", "<html><head/><body><p>This option will allow GNS3 to replace all adapters of the VM. This allow you to use any interface of the VM in GNS3.</p></body></html>"))
self.uiUseAnyAdapterCheckBox.setText(_translate("VMwareVMConfigPageWidget", "Allow GNS3 to override non custom VMware adapter"))
self.uiFirstPortNameLabel.setText(_translate("VMwareVMConfigPageWidget", "First port name:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("VMwareVMConfigPageWidget", "Network"))

View File

@@ -127,7 +127,7 @@ class VMwareVM(Node):
id=self.id(),
node_id=self._node_id,
state=state,
host=self.compute().id(),
host=self.compute().name(),
console=self._settings["console"])
port_info = ""

View File

@@ -20,6 +20,7 @@ VPCS module implementation.
"""
import os
import copy
import shutil
from gns3.qt import QtWidgets
@@ -83,11 +84,12 @@ class VPCS(Module):
# save the settings
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
if self._settings["vpcs_path"]:
server_settings = copy.copy(self._settings)
if server_settings["vpcs_path"]:
# save some settings to the server config file
server_settings = {"vpcs_path": os.path.normpath(self._settings["vpcs_path"])}
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
server_settings["vpcs_path"] = os.path.normpath(server_settings["vpcs_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
def _loadVPCSNodes(self):
"""
@@ -115,25 +117,6 @@ class VPCS(Module):
self._settings["nodes"] = list(self._vpcs_nodes.values())
self._saveSettings()
def vpcsNodes(self):
"""
Returns VPCS node settings.
:returns: VPCS node settings (dictionary)
"""
return self._vpcs_nodes
def setVPCSNodes(self, new_vpcs_nodes):
"""
Sets VPCS node settings.
:param new_vpcs_nodes: VPCS node settings (dictionary)
"""
self._vpcs_nodes = new_vpcs_nodes.copy()
self._saveVPCSNodes()
def addNode(self, node):
"""
Adds a node to this module.
@@ -278,7 +261,7 @@ class VPCS(Module):
"""
self._vpcs_nodes = new_vpcs_nodes.copy()
self._saveSettings()
self._saveVPCSNodes()
@staticmethod
def classes():

View File

@@ -20,7 +20,7 @@ Configuration page for VPCS nodes
"""
import os
from gns3.qt import QtGui, QtWidgets
from gns3.qt import QtWidgets
from gns3.local_server import LocalServer
from gns3.node import Node

View File

@@ -163,7 +163,7 @@ class VPCSNodePreferencesPage(QtWidgets.QWidget, Ui_VPCSNodePageWidget):
"""
vpcs_module = VPCS.instance()
self._vpcs_nodes = copy.deepcopy(vpcs_module.vpcsNodes())
self._vpcs_nodes = copy.deepcopy(vpcs_module.VMs())
self._items.clear()
for key, node in self._vpcs_nodes.items():
@@ -187,4 +187,4 @@ class VPCSNodePreferencesPage(QtWidgets.QWidget, Ui_VPCSNodePageWidget):
Saves the VPCS node preferences.
"""
VPCS.instance().setVPCSNodes(self._vpcs_nodes)
VPCS.instance().setVMs(self._vpcs_nodes)

View File

@@ -126,7 +126,7 @@ class VPCSNode(Node):
id=self.id(),
node_id=self._node_id,
state=state,
host=self.compute().id(),
host=self.compute().name(),
console=self._settings["console"])
port_info = ""
@@ -198,14 +198,12 @@ class VPCSNode(Node):
try:
contents = os.listdir(directory)
except OSError as e:
self.warning_signal.emit(self.id(), "Can't list file in {}: {}".format(directory, str(e)))
return
script_file = normalize_filename(self.name()) + "_startup.vpc"
new_settings = {}
if script_file in contents:
new_settings["script_file"] = os.path.join(directory, script_file)
else:
self.warning_signal.emit(self.id(), "no script file could be found, expected file name: {}".format(script_file))
return
self.update(new_settings)

View File

@@ -338,9 +338,7 @@ class Node(BaseNode):
if "properties" in result:
for name, value in result["properties"].items():
if name.startswith("slot") or name.startswith("wic"):
pass
elif name in self._settings and self._settings[name] != value:
if name in self._settings and self._settings[name] != value:
log.debug("{} setting up and updating {} from '{}' to '{}'".format(self.name(), name, self._settings[name], value))
self._settings[name] = value
@@ -412,6 +410,7 @@ class Node(BaseNode):
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
@@ -432,7 +431,7 @@ class Node(BaseNode):
return
log.debug("{} is starting".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, progressText="{} is starting".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/start".format(node_id=self._node_id), self._startCallback, timeout=None, progressText="{} is starting".format(self.name()))
def _startCallback(self, result, error=False, **kwargs):
"""
@@ -458,7 +457,7 @@ class Node(BaseNode):
return
log.debug("{} is stopping".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/stop".format(node_id=self._node_id), self._stopCallback, progressText="{} is stopping".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/stop".format(node_id=self._node_id), self._stopCallback, progressText="{} is stopping".format(self.name()), timeout=None)
def _stopCallback(self, result, error=False, **kwargs):
"""
@@ -487,7 +486,7 @@ class Node(BaseNode):
return
log.debug("{} is being suspended".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/suspend".format(node_id=self._node_id), self._suspendCallback)
self.controllerHttpPost("/nodes/{node_id}/suspend".format(node_id=self._node_id), self._suspendCallback, timeout=None)
def _suspendCallback(self, result, error=False, **kwargs):
"""
@@ -509,7 +508,7 @@ class Node(BaseNode):
"""
log.debug("{} is being reloaded".format(self.name()))
self.controllerHttpPost("/nodes/{node_id}/reload".format(node_id=self._node_id), self._reloadCallback)
self.controllerHttpPost("/nodes/{node_id}/reload".format(node_id=self._node_id), self._reloadCallback, timeout=None)
def _reloadCallback(self, result, error=False, **kwargs):
"""
@@ -602,12 +601,3 @@ class Node(BaseNode):
"""
return self._settings["name"]
def settings(self):
"""
Returns all the node settings.
:returns: settings dictionary
"""
return self._settings

View File

@@ -27,6 +27,7 @@ from .modules import MODULES
from .node import Node
from .controller import Controller
from .dialogs.configuration_dialog import ConfigurationDialog
from .local_config import LocalConfig
class NodesView(QtWidgets.QTreeWidget):
@@ -127,7 +128,6 @@ class NodesView(QtWidgets.QTreeWidget):
def _showContextualMenu(self):
item = self.currentItem()
node = item.data(0, QtCore.Qt.UserRole)
node_module = None
for module in MODULES:
node_class = module.getNodeClass(node["class"])
if node_class:
@@ -135,10 +135,12 @@ class NodesView(QtWidgets.QTreeWidget):
# We can not edit stuff like EthernetSwitch
# or without config template like VPCS
if not "builtin" in node and hasattr(module, "vmConfigurationPage"):
if "builtin" not in node and hasattr(module, "vmConfigurationPage"):
for vm_key, vm in module.instance().VMs().items():
if vm["name"] == node["name"]:
break
if vm is None:
return
menu = QtWidgets.QMenu()
configuration = QtWidgets.QAction("Configure Template", menu)
configuration.setIcon(QtGui.QIcon(":/icons/configuration.svg"))
@@ -158,6 +160,7 @@ class NodesView(QtWidgets.QTreeWidget):
dialog.show()
if dialog.exec_():
module.instance().setVMs(module.instance().VMs())
LocalConfig.instance().writeConfig()
self.refresh()
def _deleteSlot(self, vm_key, vm, module, source):
@@ -168,4 +171,5 @@ class NodesView(QtWidgets.QTreeWidget):
vms = module.instance().VMs()
vms.pop(vm_key)
module.instance().setVMs(vms)
LocalConfig.instance().writeConfig()
self.refresh()

View File

@@ -47,7 +47,10 @@ class PacketCapture:
Kill all running captures (for example when change project)
"""
for process in list(self._tail_process.values()):
process.kill()
try:
process.kill()
except OSError:
pass
self._tail_process = {}
self._capture_reader_process = {}
@@ -154,7 +157,10 @@ class PacketCapture:
pass
del self._tail_process[link]
if link in self._capture_reader_process and self._capture_reader_process[link].poll() is None:
self._capture_reader_process[link].kill()
try:
self._capture_reader_process[link].kill()
except (PermissionError, OSError):
pass
del self._capture_reader_process[link]
# PCAP capture file path
@@ -206,6 +212,9 @@ class PacketCapture:
# normal traffic capture
if not sys.platform.startswith("win"):
command = shlex.split(command)
if len(command) == 0:
QtWidgets.QMessageBox.critical(self.parent(), "Packet capture", "No packet capture program configured")
return
try:
self._capture_reader_process[link] = subprocess.Popen(command)
except OSError as e:

View File

@@ -181,7 +181,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Import configuration file", "Could not load configuration file {}: {}".format(os.path.basename(path), e))
return
except ValueError as e:
except (ValueError, TypeError) as e:
QtWidgets.QMessageBox.critical(self, "Import configuration file", "Invalid file: {}".format(e))
return
@@ -258,6 +258,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
self.uiCrashReportCheckBox.setChecked(local_server["report_errors"])
self.uiCheckForUpdateCheckBox.setChecked(settings["check_for_update"])
self.uiExperimentalFeaturesCheckBox.setChecked(settings["experimental_features"])
self.uiHdpiCheckBox.setChecked(settings["hdpi"])
self.uiTelnetConsoleCommandLineEdit.setText(settings["telnet_console_command"])
self.uiTelnetConsoleCommandLineEdit.setCursorPosition(0)
index = self.uiStyleComboBox.findText(settings["style"])
@@ -328,6 +329,7 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
new_general_settings = {
"style": self.uiStyleComboBox.currentText(),
"experimental_features": self.uiExperimentalFeaturesCheckBox.isChecked(),
"hdpi": self.uiHdpiCheckBox.isChecked(),
"check_for_update": self.uiCheckForUpdateCheckBox.isChecked(),
"telnet_console_command": self.uiTelnetConsoleCommandLineEdit.text(),
"vnc_console_command": self.uiVNCConsoleCommandLineEdit.text(),

View File

@@ -20,14 +20,13 @@ Configuration page for GNS3 VM
"""
import copy
from gns3.qt import QtWidgets, QtCore, qpartial, qslot
from gns3.controller import Controller
from ..ui.gns3_vm_preferences_page_ui import Ui_GNS3VMPreferencesPageWidget
import logging
log = logging.getLogger(__name__)
from gns3.qt import QtWidgets, QtCore, qpartial
from gns3.controller import Controller
from ..ui.gns3_vm_preferences_page_ui import Ui_GNS3VMPreferencesPageWidget
class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
@@ -92,22 +91,25 @@ class GNS3VMPreferencesPage(QtWidgets.QWidget, Ui_GNS3VMPreferencesPageWidget):
else:
self.uiWhenExitStopRadioButton.setChecked(True)
self.uiHeadlessCheckBox.setChecked(self._settings["headless"])
index = self.uiGNS3VMEngineComboBox.findData(self._settings["engine"])
self.uiGNS3VMEngineComboBox.setCurrentIndex(index)
Controller.instance().get("/gns3vm/engines", self._listEnginesCallback)
def _listEnginesCallback(self, result, error=False, **kwargs):
@qslot
def _listEnginesCallback(self, result, error=False, ignore_error=False, **kwargs):
if error:
if "message" in result:
log.error("Error while getting the list of GNS3 VM engines : {}".format(result["message"]))
return
self.uiGNS3VMEngineComboBox.clear()
self._engines = result
# We insert first the current engine to avoid triggering unexpected signals
for engine in self._engines:
self.uiGNS3VMEngineComboBox.addItem(engine["name"], engine["engine_id"])
index = self.uiGNS3VMEngineComboBox.findData(self._settings["engine"])
self.uiGNS3VMEngineComboBox.setCurrentIndex(index)
if self._settings["engine"] == engine["engine_id"]:
self.uiGNS3VMEngineComboBox.addItem(engine["name"], engine["engine_id"])
for engine in self._engines:
if self._settings["engine"] != engine["engine_id"]:
self.uiGNS3VMEngineComboBox.addItem(engine["name"], engine["engine_id"])
@qslot
def _refreshVMSlot(self, ignore_error=False):
engine_id = self.uiGNS3VMEngineComboBox.currentData()
if engine_id:

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