mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-06 02:32:04 +03:00
Compare commits
259 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e172fc7e3 | ||
|
|
8ed8a2c115 | ||
|
|
073665a75d | ||
|
|
4ccc67aa46 | ||
|
|
6e2632e91f | ||
|
|
38ddcde902 | ||
|
|
436563afcb | ||
|
|
7eaab3e38b | ||
|
|
0927a2a8c9 | ||
|
|
87e6159ff6 | ||
|
|
effdcf5e24 | ||
|
|
021cdd2e65 | ||
|
|
363c4a9966 | ||
|
|
7082c75511 | ||
|
|
16c4a837d7 | ||
|
|
fd42ac410c | ||
|
|
6efc177804 | ||
|
|
6f9e6c9b92 | ||
|
|
0411c68150 | ||
|
|
4246e731e5 | ||
|
|
50cca71279 | ||
|
|
c78ef8f348 | ||
|
|
c03a5a9e0a | ||
|
|
c93b7836d8 | ||
|
|
690b22cc24 | ||
|
|
3560251816 | ||
|
|
90e861289f | ||
|
|
6e144d6122 | ||
|
|
2f168193d1 | ||
|
|
0fe5559564 | ||
|
|
37f0744d7c | ||
|
|
000f4a4790 | ||
|
|
a09b7d6738 | ||
|
|
a1fa8f9ec2 | ||
|
|
cd6b0b793e | ||
|
|
6453932421 | ||
|
|
37a23d9682 | ||
|
|
e18e10c701 | ||
|
|
a9240e2e46 | ||
|
|
4df0c33013 | ||
|
|
4096316ceb | ||
|
|
e09292c647 | ||
|
|
31c37161fa | ||
|
|
a047cd7f4c | ||
|
|
87cde665a8 | ||
|
|
6361c94bbe | ||
|
|
8a0aeff0bb | ||
|
|
508f8b3ad5 | ||
|
|
bfe942c029 | ||
|
|
b3a86594ff | ||
|
|
debe88bd37 | ||
|
|
a948fd07b1 | ||
|
|
072f714e21 | ||
|
|
a2059d3e7c | ||
|
|
c4cc819d50 | ||
|
|
0796d9aae5 | ||
|
|
c8148c1877 | ||
|
|
d6021afa0f | ||
|
|
9018bdac07 | ||
|
|
a48b898d92 | ||
|
|
949c1bbe37 | ||
|
|
0089edecc0 | ||
|
|
91da1d492b | ||
|
|
0242f53c17 | ||
|
|
bab7a1016f | ||
|
|
2ec91e1ef7 | ||
|
|
38af30ec15 | ||
|
|
3f8a0cb527 | ||
|
|
fd1a86df03 | ||
|
|
c954a16ead | ||
|
|
9418b997eb | ||
|
|
9682cad4f4 | ||
|
|
60ee9e1374 | ||
|
|
dbd5b9366a | ||
|
|
f63314d4d0 | ||
|
|
96eaec0100 | ||
|
|
5d554976e2 | ||
|
|
d1d8390b73 | ||
|
|
5f3f6462c2 | ||
|
|
821809bf6f | ||
|
|
a712fab7a4 | ||
|
|
4ab21ea9f9 | ||
|
|
1a4902279b | ||
|
|
35412171b9 | ||
|
|
73939847b9 | ||
|
|
5acfc5bc56 | ||
|
|
d67d0d146f | ||
|
|
23da6a4c31 | ||
|
|
fd5f999756 | ||
|
|
c05304b86f | ||
|
|
6361980591 | ||
|
|
d5ea98ba2a | ||
|
|
3ca01384c9 | ||
|
|
f35bb6a281 | ||
|
|
0c4a7693e6 | ||
|
|
20506525c5 | ||
|
|
bb00a9f64c | ||
|
|
ef36792379 | ||
|
|
873fd409bd | ||
|
|
03221e8ab7 | ||
|
|
55f9836dc9 | ||
|
|
3ab5144fe2 | ||
|
|
c91a22c9f8 | ||
|
|
a8ba909568 | ||
|
|
d33e9ed833 | ||
|
|
aa31af1dca | ||
|
|
4d07a7391f | ||
|
|
417395718d | ||
|
|
212048c4d1 | ||
|
|
11c27063b4 | ||
|
|
b8696fa54e | ||
|
|
ba9785f83f | ||
|
|
4ae742529c | ||
|
|
92cc335708 | ||
|
|
2099a4ae9a | ||
|
|
5038a72610 | ||
|
|
b9e320996b | ||
|
|
9c86cd71c8 | ||
|
|
7fdf42b442 | ||
|
|
61d86e919e | ||
|
|
bc71985ee3 | ||
|
|
6bebf2d14d | ||
|
|
490021aa47 | ||
|
|
c8db5e7e49 | ||
|
|
8efaacbc3d | ||
|
|
c5da24b954 | ||
|
|
4456cfd68e | ||
|
|
3468909db1 | ||
|
|
212728eb94 | ||
|
|
cf4f73a7e1 | ||
|
|
5948e5c4cb | ||
|
|
791aa27158 | ||
|
|
0b8ab56ffc | ||
|
|
12dcfda756 | ||
|
|
ef44beac41 | ||
|
|
97a71904f7 | ||
|
|
94f4059d67 | ||
|
|
14ed2546bc | ||
|
|
908258c163 | ||
|
|
704191ba25 | ||
|
|
be7fc9abe2 | ||
|
|
e0daf1dbb1 | ||
|
|
a8877d4f8a | ||
|
|
2db850a3f3 | ||
|
|
ec7cdedb86 | ||
|
|
606fa15a55 | ||
|
|
3eca9b0e54 | ||
|
|
3abaf74580 | ||
|
|
9713748633 | ||
|
|
405e86aff4 | ||
|
|
4da824dc2b | ||
|
|
0ec177c644 | ||
|
|
9ef4f86050 | ||
|
|
96b830817e | ||
|
|
0db8b00fb9 | ||
|
|
005dde6c2b | ||
|
|
260f9b352e | ||
|
|
852b0bc498 | ||
|
|
3a503a5fc0 | ||
|
|
88b408695a | ||
|
|
776b45363b | ||
|
|
adb270b64c | ||
|
|
5329b8fd72 | ||
|
|
d6379e4bb9 | ||
|
|
6ca18d5b29 | ||
|
|
50c008ddec | ||
|
|
4f56f100fa | ||
|
|
0c16a5b0d1 | ||
|
|
8f33ad3c70 | ||
|
|
7dca1b404c | ||
|
|
99ff98ff47 | ||
|
|
083d6e1298 | ||
|
|
71716c451e | ||
|
|
3c4a244b75 | ||
|
|
85892a3bd6 | ||
|
|
1d3d721cf3 | ||
|
|
37a72af75f | ||
|
|
c93713b9e7 | ||
|
|
77b3118cbc | ||
|
|
bc0cbfd040 | ||
|
|
0cfc66b4d4 | ||
|
|
1b9b1cbe3c | ||
|
|
407187c826 | ||
|
|
a6eb1e65ac | ||
|
|
efbb19a862 | ||
|
|
2a94816b58 | ||
|
|
aab307a519 | ||
|
|
1279e16484 | ||
|
|
c2e20f9bd6 | ||
|
|
4acdaf6b5a | ||
|
|
25313cbcde | ||
|
|
0de0eb12eb | ||
|
|
7d0fe52600 | ||
|
|
ba1d3b2423 | ||
|
|
cae6afe85d | ||
|
|
5865ac267b | ||
|
|
162993839c | ||
|
|
4359b490cc | ||
|
|
e4a8e67229 | ||
|
|
6605270e64 | ||
|
|
09e2bfeed0 | ||
|
|
b24733466d | ||
|
|
c31be48f20 | ||
|
|
a9ed27f42c | ||
|
|
1440caa532 | ||
|
|
513eb21940 | ||
|
|
de348d39da | ||
|
|
0a3697962b | ||
|
|
e0edcf3d23 | ||
|
|
57dbff6a8e | ||
|
|
0149bc90f2 | ||
|
|
f7292deb0f | ||
|
|
be01448c36 | ||
|
|
484617ce25 | ||
|
|
8ec53a6004 | ||
|
|
cfd1bbd9d1 | ||
|
|
25ae214b6b | ||
|
|
456160beb1 | ||
|
|
9affe2d9f4 | ||
|
|
3ed2f89b3b | ||
|
|
9bb353fdbd | ||
|
|
c414ea28e4 | ||
|
|
cb0253f7cb | ||
|
|
f28663c626 | ||
|
|
3ef018f90e | ||
|
|
e5ae7f77fa | ||
|
|
b6bac0cd3b | ||
|
|
6a8e435210 | ||
|
|
442318af49 | ||
|
|
ad93b46c94 | ||
|
|
f7a9cc09ea | ||
|
|
0d5507cf3d | ||
|
|
f885e33cbd | ||
|
|
4813a8681f | ||
|
|
829f750c76 | ||
|
|
08b9f4a6d2 | ||
|
|
7fdf022b36 | ||
|
|
f12935076e | ||
|
|
7a472d1574 | ||
|
|
4a82dc8705 | ||
|
|
52acaadfce | ||
|
|
ef1967ff00 | ||
|
|
6584f3b2d4 | ||
|
|
8ad55290b1 | ||
|
|
8ff34d63c0 | ||
|
|
da88947028 | ||
|
|
854dc5db71 | ||
|
|
e8ac144011 | ||
|
|
8b20f4d568 | ||
|
|
cb88a4f6d9 | ||
|
|
270f12dc1e | ||
|
|
46ff586055 | ||
|
|
78b999be57 | ||
|
|
4f702d9339 | ||
|
|
6f210c0e91 | ||
|
|
a677cff0a2 | ||
|
|
5b1050e427 | ||
|
|
271e987972 | ||
|
|
1df34148b2 |
23
.travis.yml
23
.travis.yml
@@ -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
239
CHANGELOG
@@ -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
|
||||
|
||||
@@ -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
19
appveyor.yml
Normal 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"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
19
gns3/link.py
19
gns3/link.py
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
21
gns3/main.py
21
gns3/main.py
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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><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></string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="uiGeneralGroupBox">
|
||||
|
||||
@@ -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:"))
|
||||
|
||||
|
||||
@@ -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><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></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>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="uiAddAllEthernetPushButton">
|
||||
<property name="text">
|
||||
<string>&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>&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">
|
||||
|
||||
@@ -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 "real world" (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"))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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><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></string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="uiGeneralGroupBox">
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)] = ""
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,7 +25,6 @@ from gns3.node import Node
|
||||
|
||||
IOU_SETTINGS = {
|
||||
"iourc_content": "",
|
||||
"iouyap_path": "",
|
||||
"license_check": True,
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 & 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>&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 & 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>&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>&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>
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):"))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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><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></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>
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
22
gns3/node.py
22
gns3/node.py
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user