mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-06-07 03:02:08 +03:00
Compare commits
547 Commits
v1.4.0beta
...
v1.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a336cd67 | ||
|
|
1810956185 | ||
|
|
af7f0f49d1 | ||
|
|
d6ebf8ea04 | ||
|
|
13721c9811 | ||
|
|
02810e84a9 | ||
|
|
a12a686a68 | ||
|
|
16ec69bb8c | ||
|
|
82cb95aff7 | ||
|
|
1eb3693f0a | ||
|
|
bfd9c654aa | ||
|
|
b1477f5fb5 | ||
|
|
cda6398010 | ||
|
|
13b699a183 | ||
|
|
0cd1f314f6 | ||
|
|
0c894ee48a | ||
|
|
c85820e685 | ||
|
|
445f721ceb | ||
|
|
5fa24247c6 | ||
|
|
2acfe7f1bf | ||
|
|
b33f660f90 | ||
|
|
91509888af | ||
|
|
fa3657f736 | ||
|
|
09638633c1 | ||
|
|
98ba58f39b | ||
|
|
baa7436e43 | ||
|
|
4a9b649e9f | ||
|
|
42143f77c5 | ||
|
|
e2bdd216bb | ||
|
|
856fde79ec | ||
|
|
91987b2e06 | ||
|
|
36c6eeabc9 | ||
|
|
549e364da3 | ||
|
|
d0321ce0fa | ||
|
|
0d4cca3aa7 | ||
|
|
d77177a98f | ||
|
|
13bcac7e22 | ||
|
|
4475a93fc5 | ||
|
|
041b609f99 | ||
|
|
d84ce07038 | ||
|
|
c9fec9ad51 | ||
|
|
e953ea7212 | ||
|
|
d1d7cd8186 | ||
|
|
d3e4f17dab | ||
|
|
344fde0d6f | ||
|
|
e27c8955c5 | ||
|
|
27fa234888 | ||
|
|
59ae047359 | ||
|
|
8445637dd1 | ||
|
|
e54faa3079 | ||
|
|
4d2a4f3433 | ||
|
|
ddd6de24ce | ||
|
|
19f1b969ea | ||
|
|
48beb69103 | ||
|
|
a0be08d62a | ||
|
|
a750c7df2a | ||
|
|
950986c69e | ||
|
|
cc7bddefa1 | ||
|
|
1c4b735880 | ||
|
|
0862c135d0 | ||
|
|
01e3cf1b1c | ||
|
|
519bd389f6 | ||
|
|
ee6aac2614 | ||
|
|
fa319b6529 | ||
|
|
905be4130e | ||
|
|
814e973f9a | ||
|
|
016e279d43 | ||
|
|
d432047ac1 | ||
|
|
9938e60e05 | ||
|
|
6e751bbfaf | ||
|
|
8bdc07185b | ||
|
|
8872aceb0b | ||
|
|
f7b14c7da7 | ||
|
|
86cd732453 | ||
|
|
3178fb1a46 | ||
|
|
a917e80774 | ||
|
|
9bde5fcad6 | ||
|
|
e33f423733 | ||
|
|
cc35408607 | ||
|
|
10d91b50ec | ||
|
|
7dd06b5659 | ||
|
|
86087bf505 | ||
|
|
cb4923815a | ||
|
|
43258d64d2 | ||
|
|
6d3109be67 | ||
|
|
a25e0cc23f | ||
|
|
026a78b1b6 | ||
|
|
380a4a0395 | ||
|
|
f6c2a6387f | ||
|
|
27f65a92e9 | ||
|
|
53dbac1d5c | ||
|
|
a780866cd8 | ||
|
|
bf6f1af217 | ||
|
|
29c85f3c65 | ||
|
|
ba51d3ebf5 | ||
|
|
e4f3ea1da9 | ||
|
|
f037452188 | ||
|
|
3d35601d47 | ||
|
|
146e642097 | ||
|
|
e2c53443e3 | ||
|
|
965a98d5a7 | ||
|
|
41a68e7b0e | ||
|
|
20e62b824c | ||
|
|
36e9fcbddf | ||
|
|
bd3d33d67b | ||
|
|
caf77a24f1 | ||
|
|
729f9e103d | ||
|
|
7261debb79 | ||
|
|
b4d0deddfc | ||
|
|
d62a32c7d7 | ||
|
|
cb94fc4d1e | ||
|
|
554888b21d | ||
|
|
4ae01282d7 | ||
|
|
020e62ddf5 | ||
|
|
c1dce98595 | ||
|
|
676187061c | ||
|
|
14f4f26791 | ||
|
|
57cf10c251 | ||
|
|
70b4fae62c | ||
|
|
5c3b9e7fae | ||
|
|
9ea39a78db | ||
|
|
8f77697482 | ||
|
|
1b5e53ddc6 | ||
|
|
86ee9595f4 | ||
|
|
dd7937dba4 | ||
|
|
a4c646152e | ||
|
|
3d7db9a9c7 | ||
|
|
e9ceadfc34 | ||
|
|
267cdc365d | ||
|
|
6dfbaccee1 | ||
|
|
f97f48d428 | ||
|
|
70b3bc680e | ||
|
|
c125579a38 | ||
|
|
9bc2a7e7bc | ||
|
|
a5358dec14 | ||
|
|
c84f554f36 | ||
|
|
0dd9803f74 | ||
|
|
2baaa26a42 | ||
|
|
3fd62f906e | ||
|
|
41e8b44dde | ||
|
|
09cbd9b606 | ||
|
|
3ab8d79ff7 | ||
|
|
97eaed6d2c | ||
|
|
805aa23948 | ||
|
|
a25c45a502 | ||
|
|
6ebda209ac | ||
|
|
d226b31fab | ||
|
|
27a3009ce1 | ||
|
|
654ad61105 | ||
|
|
31a461195f | ||
|
|
331eef3164 | ||
|
|
cb0d576ade | ||
|
|
32978381b2 | ||
|
|
a7f7a688d3 | ||
|
|
5d87726397 | ||
|
|
bb66555896 | ||
|
|
37726630ce | ||
|
|
e26762fce3 | ||
|
|
32e59fcce4 | ||
|
|
9b43330e95 | ||
|
|
b3b94243b4 | ||
|
|
fba1032388 | ||
|
|
ccc0803c5b | ||
|
|
f0340bcc98 | ||
|
|
8d19b8fcbf | ||
|
|
dbf66d5a9a | ||
|
|
0fa8d61b19 | ||
|
|
02d326419b | ||
|
|
608e80a80b | ||
|
|
d90f11eb86 | ||
|
|
dc39187091 | ||
|
|
e4cd418533 | ||
|
|
8559469c73 | ||
|
|
e6ba7bdd98 | ||
|
|
784055689f | ||
|
|
c937811f45 | ||
|
|
79c8021faa | ||
|
|
a428730f59 | ||
|
|
3cad8ea046 | ||
|
|
ab1775e44b | ||
|
|
9c3facb07a | ||
|
|
e5ae7b2d25 | ||
|
|
60462ff986 | ||
|
|
39443cd676 | ||
|
|
25ab8249ae | ||
|
|
6e86c606cc | ||
|
|
8878b96e74 | ||
|
|
c3ef2edbab | ||
|
|
f7e398edc3 | ||
|
|
8d4fc9585e | ||
|
|
5fe65e26ce | ||
|
|
daaebe7f96 | ||
|
|
f9f6a52e8b | ||
|
|
601c0217b9 | ||
|
|
b0971b4ba3 | ||
|
|
5a4549c36c | ||
|
|
fe2cc362f8 | ||
|
|
dd0220fd59 | ||
|
|
26fdd9ef6f | ||
|
|
733ee259e5 | ||
|
|
762fecbcff | ||
|
|
5e85dfe5fd | ||
|
|
e01632d60a | ||
|
|
60916e8b80 | ||
|
|
4fe55634ae | ||
|
|
b1b861c99d | ||
|
|
07c0474386 | ||
|
|
755fb5c8f3 | ||
|
|
fdb382874d | ||
|
|
471b3e1009 | ||
|
|
f64a226336 | ||
|
|
dec257bb6b | ||
|
|
e736fbbb87 | ||
|
|
aeb42b3ffe | ||
|
|
1694a57ed9 | ||
|
|
26001463a0 | ||
|
|
36c1197f1f | ||
|
|
1fa16936fe | ||
|
|
fca65784ee | ||
|
|
8983e6c5a9 | ||
|
|
c6e06f3941 | ||
|
|
b7e32a60ce | ||
|
|
da100e494b | ||
|
|
9d7b5bccb8 | ||
|
|
112b05c3dd | ||
|
|
987e85cce8 | ||
|
|
8142d0baa7 | ||
|
|
cc32b2661c | ||
|
|
781857f598 | ||
|
|
69179dcb63 | ||
|
|
8017838d60 | ||
|
|
81946493ec | ||
|
|
a7f40c3d50 | ||
|
|
c28723287f | ||
|
|
c7a8588647 | ||
|
|
4a64261c5d | ||
|
|
5f4365542c | ||
|
|
2e2c951ffc | ||
|
|
2ba7dde326 | ||
|
|
a1579ca86b | ||
|
|
81c4ddb85f | ||
|
|
285a8d413a | ||
|
|
672c86b38d | ||
|
|
905611d2a8 | ||
|
|
339fb22217 | ||
|
|
a8f5fa5dd5 | ||
|
|
bd365dd6eb | ||
|
|
8e4a6169e0 | ||
|
|
b1790844f3 | ||
|
|
4b0050d26c | ||
|
|
19bf40dc89 | ||
|
|
23cda61d17 | ||
|
|
c74ffde65a | ||
|
|
0a9c10e748 | ||
|
|
6973eaaa02 | ||
|
|
9ce483398b | ||
|
|
55744ab129 | ||
|
|
0e1cb47aa1 | ||
|
|
3bf12753df | ||
|
|
8907659220 | ||
|
|
a4ccb0b620 | ||
|
|
7d845c0ef8 | ||
|
|
8282ec1da6 | ||
|
|
edfe2c7f47 | ||
|
|
ca69673439 | ||
|
|
8677707a9a | ||
|
|
f3abbe5d58 | ||
|
|
c0f36590be | ||
|
|
bdb097a173 | ||
|
|
510491e26d | ||
|
|
6f0015a678 | ||
|
|
690e1d5ea0 | ||
|
|
19734e0bd3 | ||
|
|
5172c0b19e | ||
|
|
d74898c8a3 | ||
|
|
eb10f10988 | ||
|
|
eb2e504acc | ||
|
|
6c502e1213 | ||
|
|
462c00b358 | ||
|
|
b83eb61a42 | ||
|
|
96c35c49ff | ||
|
|
36b48f24dc | ||
|
|
efb21665f0 | ||
|
|
9ab77cfe20 | ||
|
|
6031a349be | ||
|
|
7163daf480 | ||
|
|
f662c39df0 | ||
|
|
d7caba76c4 | ||
|
|
36ff527cc0 | ||
|
|
d96ab77a67 | ||
|
|
b4ade1982e | ||
|
|
4c9ef92c2d | ||
|
|
078fc0f5ed | ||
|
|
40cc73c4f1 | ||
|
|
07c1443a8d | ||
|
|
dc592e0d4f | ||
|
|
c873b6c603 | ||
|
|
c7d0a7a504 | ||
|
|
e7eddf0a7e | ||
|
|
579e382971 | ||
|
|
95ecb05434 | ||
|
|
a2353f32e4 | ||
|
|
793181d208 | ||
|
|
932ff53190 | ||
|
|
e92e23a8be | ||
|
|
a16adb2b46 | ||
|
|
f579ebf5a0 | ||
|
|
67593c97ab | ||
|
|
fe185283a0 | ||
|
|
d2f8214a52 | ||
|
|
a2293b21ce | ||
|
|
57872c38b3 | ||
|
|
51f998c177 | ||
|
|
7abbc630c3 | ||
|
|
a0dee6cb42 | ||
|
|
d38143ae1c | ||
|
|
1db36dfb3e | ||
|
|
a347de96ed | ||
|
|
31c76bf56d | ||
|
|
8abf321d9a | ||
|
|
3f7b6e7be4 | ||
|
|
7b1337cd83 | ||
|
|
7f63a221af | ||
|
|
cfeb5c9495 | ||
|
|
17655cd855 | ||
|
|
ab071cd989 | ||
|
|
c73dd10783 | ||
|
|
6a5584ae41 | ||
|
|
16e82592a6 | ||
|
|
842e771eef | ||
|
|
d31f9c49f5 | ||
|
|
8b61c7ddc3 | ||
|
|
af247e2dd6 | ||
|
|
990cb49854 | ||
|
|
c530924d8a | ||
|
|
35360ae196 | ||
|
|
f37117ed09 | ||
|
|
9d2d80db25 | ||
|
|
364d8db5b2 | ||
|
|
0ac5215d4b | ||
|
|
493d81c519 | ||
|
|
c5ab0c8d02 | ||
|
|
03323e5df8 | ||
|
|
a8c429b77e | ||
|
|
52095c8ed9 | ||
|
|
b13855a062 | ||
|
|
0be67907ba | ||
|
|
cf7285a179 | ||
|
|
ba564869a0 | ||
|
|
a6ddddea99 | ||
|
|
a686a18d56 | ||
|
|
a98bd132e1 | ||
|
|
a100ca33b9 | ||
|
|
d2f75262d8 | ||
|
|
c65fd1683d | ||
|
|
76a8735133 | ||
|
|
da3c1334da | ||
|
|
0c370ec45e | ||
|
|
ec27e418fc | ||
|
|
92500e96d4 | ||
|
|
c2ba6f5cb3 | ||
|
|
07610e7556 | ||
|
|
741749dffb | ||
|
|
593c777ff0 | ||
|
|
8a616fa0c5 | ||
|
|
a191ec7326 | ||
|
|
f842812745 | ||
|
|
3cc43aa11f | ||
|
|
1d9839ed23 | ||
|
|
32a64f1259 | ||
|
|
3c4fc4c1bd | ||
|
|
1013cf527d | ||
|
|
1684ee3074 | ||
|
|
f50e08207c | ||
|
|
d3973d4a7b | ||
|
|
7170e58551 | ||
|
|
02968f1ece | ||
|
|
eb94ba8b93 | ||
|
|
67c6e03f3b | ||
|
|
faa3b519b6 | ||
|
|
3b4dca7545 | ||
|
|
66495cbf83 | ||
|
|
95fea87b29 | ||
|
|
4b0cea36d0 | ||
|
|
ec27683f0e | ||
|
|
a4d7aaf0a1 | ||
|
|
e2bcd1fdd1 | ||
|
|
d7fd04de03 | ||
|
|
f447cd9be6 | ||
|
|
a080b65615 | ||
|
|
bd034237c0 | ||
|
|
66aff0d9a2 | ||
|
|
04a4142128 | ||
|
|
ec71da4062 | ||
|
|
0ae97aa933 | ||
|
|
580e19de0c | ||
|
|
e41e95b0e6 | ||
|
|
1adeee9ba1 | ||
|
|
b598339e55 | ||
|
|
c65e5d8795 | ||
|
|
b8597bc196 | ||
|
|
be75a8e2b8 | ||
|
|
2352840c3b | ||
|
|
98f9f59af3 | ||
|
|
00ba2f1046 | ||
|
|
0389245cb7 | ||
|
|
2f746ff791 | ||
|
|
8b881dfd20 | ||
|
|
cfece32185 | ||
|
|
d09ce859aa | ||
|
|
227a8a0c9e | ||
|
|
1b0da301e3 | ||
|
|
0f3efaa3fe | ||
|
|
55b5f49c79 | ||
|
|
3601acf66e | ||
|
|
383306c0ab | ||
|
|
5f87be16d7 | ||
|
|
63ab01d364 | ||
|
|
4252f19819 | ||
|
|
1095ed1663 | ||
|
|
c5557e45c4 | ||
|
|
f5159a93f3 | ||
|
|
b00d39e531 | ||
|
|
0c077b5647 | ||
|
|
98aed2a8b6 | ||
|
|
0523873e5e | ||
|
|
ed5ce93df6 | ||
|
|
ab4f85b862 | ||
|
|
67912a918f | ||
|
|
0af939bd37 | ||
|
|
b712b54786 | ||
|
|
19bf52661e | ||
|
|
c0c0261c68 | ||
|
|
1eea941b55 | ||
|
|
547a8fb6c4 | ||
|
|
a0271926e6 | ||
|
|
7e796fc3f1 | ||
|
|
ba17b9d789 | ||
|
|
a4c17562ad | ||
|
|
ca173fb491 | ||
|
|
9330689641 | ||
|
|
ca67553e66 | ||
|
|
dabcb6d4f4 | ||
|
|
753eb79a15 | ||
|
|
034c9e8ad5 | ||
|
|
ba04783e8f | ||
|
|
a8ba51cdee | ||
|
|
24997c39f5 | ||
|
|
919311e2ab | ||
|
|
61c3b0bfff | ||
|
|
8024738400 | ||
|
|
ac58bc677f | ||
|
|
3b887f7f1b | ||
|
|
8ea24a156e | ||
|
|
264fe8f0c8 | ||
|
|
3f67cd4a99 | ||
|
|
5d9e9dfc4a | ||
|
|
95bec70e27 | ||
|
|
c1c6c812c8 | ||
|
|
0a8d947375 | ||
|
|
c9a699fc30 | ||
|
|
8bf350a7dd | ||
|
|
bbc468db0f | ||
|
|
2353f074ab | ||
|
|
79b7174a83 | ||
|
|
82e1c088c7 | ||
|
|
1d13a3de3c | ||
|
|
0fb0bafc14 | ||
|
|
6cc5547509 | ||
|
|
a72df53b69 | ||
|
|
f0802038db | ||
|
|
631b4b7a61 | ||
|
|
d9436520af | ||
|
|
d805cfbd6a | ||
|
|
078d5023c4 | ||
|
|
bc053da538 | ||
|
|
5d79bbce39 | ||
|
|
98c8d56dc5 | ||
|
|
1acaa18c3c | ||
|
|
8325fe0cee | ||
|
|
eedd12207c | ||
|
|
64ed8c3de0 | ||
|
|
ab4f2f3922 | ||
|
|
fc5bf2dc4b | ||
|
|
bd0fabd1f6 | ||
|
|
a4a2963bc3 | ||
|
|
310f47b52a | ||
|
|
b4a187dd02 | ||
|
|
f4afdca576 | ||
|
|
28bebff7b0 | ||
|
|
c3a4daef87 | ||
|
|
18a5a28283 | ||
|
|
36d08d2dca | ||
|
|
91bf6e667d | ||
|
|
a88a47e223 | ||
|
|
8ede0f2089 | ||
|
|
576f6c81a1 | ||
|
|
1666fc4aa0 | ||
|
|
1216882e89 | ||
|
|
3f825a163b | ||
|
|
2fcfa10573 | ||
|
|
dd95b24d32 | ||
|
|
bb2777ed5b | ||
|
|
e0f03ec582 | ||
|
|
51e844559e | ||
|
|
dc4a984c41 | ||
|
|
54139845ac | ||
|
|
8f9672d9e2 | ||
|
|
b007aea2f5 | ||
|
|
3900645d1f | ||
|
|
5d644467fc | ||
|
|
29fcd07b1c | ||
|
|
22c2ee31c4 | ||
|
|
69f99742a8 | ||
|
|
e7dc901d5e | ||
|
|
4dba7fa7fc | ||
|
|
55d4201aaf | ||
|
|
74a4e464c8 | ||
|
|
329c196047 | ||
|
|
85e74b482e | ||
|
|
dc12fdd1c9 | ||
|
|
0ee64ecbb8 | ||
|
|
2946e931aa | ||
|
|
30871f0cd1 | ||
|
|
70aa165444 | ||
|
|
beb35c6108 | ||
|
|
90d1cb9a7f | ||
|
|
5754d66560 | ||
|
|
77ba02f0ae | ||
|
|
effdc862a2 | ||
|
|
59c4736f41 | ||
|
|
dff831eafe | ||
|
|
4765640dc7 | ||
|
|
b8799a91b4 | ||
|
|
17aaa90fb1 | ||
|
|
40b8969d44 | ||
|
|
73454d97e1 | ||
|
|
7cf39aac5a | ||
|
|
2880168566 | ||
|
|
615d9240ad | ||
|
|
76f8aa4f60 | ||
|
|
0172dd0b53 | ||
|
|
ccb6a5cf0f | ||
|
|
7dc37c9dca | ||
|
|
69f13621ca | ||
|
|
a10f8939e2 | ||
|
|
4b681a5e55 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -58,3 +58,4 @@ keys
|
||||
# Custom config
|
||||
/gns3_server.ini
|
||||
updates
|
||||
.cache
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -1,23 +1,7 @@
|
||||
language: python
|
||||
sudo: required
|
||||
|
||||
#New container architecture
|
||||
#http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
#sudo: false
|
||||
|
||||
python:
|
||||
- "3.4"
|
||||
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- build
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
|
||||
- sudo apt-get install qt5-default qttools5-dev-tools
|
||||
- sh scripts/prepare_travis.sh
|
||||
services:
|
||||
- docker
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
@@ -29,9 +13,7 @@ notifications:
|
||||
# on_success: change
|
||||
# on_failure: always
|
||||
|
||||
install:
|
||||
- "pip install -r dev-requirements.txt"
|
||||
|
||||
script:
|
||||
- "xvfb-run py.test -vv" # Run tests in a fake X server
|
||||
# - "pep8 --exclude=build,.git,ui"
|
||||
- docker build -t gns3-gui-test .
|
||||
- docker run gns3-gui-test
|
||||
|
||||
|
||||
403
CHANGELOG
403
CHANGELOG
@@ -1,5 +1,408 @@
|
||||
# Change Log
|
||||
|
||||
## 1.4.2 17/02/2016
|
||||
|
||||
* Allow gif image (not animated) since pattent expire in 2004
|
||||
* Countdown before starting the GNS3 VM
|
||||
* Prevent IOU GNS3A install on Windows
|
||||
* Set timeout from 1 to 3 seconds when waiting for GNS3 VM server. Ref #1034.
|
||||
* Redirect stderr to stdout when executing VBoxManage or vmrun. Ref #1027.
|
||||
* Update VMware banners
|
||||
* Prevent a crash in progress dialog
|
||||
* Update for 4K monitor
|
||||
* Allow to cancel the start of the GNS3 VM
|
||||
* Update the .net converter
|
||||
* Detect and fix duplicate port name in topology
|
||||
* Allow to open a custom console on any node
|
||||
* All node are now SVG items
|
||||
* Move Qt code to a module so we can add new code in differents files
|
||||
* Fix rare crash when updating node
|
||||
* Prevent duplicate server in server summary
|
||||
* Fix a regression in Host and Cloud
|
||||
* Check if GNS3 is not installed twice in doctor
|
||||
* Allow to add custom command to the list
|
||||
* Update Readme Python 3.4 is require
|
||||
* Allow to import unknow files via GNS3A
|
||||
* Fix a problem with gns3 running in background after exit
|
||||
* Move common code for ports dump to vm.py
|
||||
* Move common code _updatePortSettings to vm.py
|
||||
* Fix a crash when searching for alternative images
|
||||
* Fix a crash with corrupted topology from 1.0
|
||||
* Remove all the docker code from 1.4 gui to avoid confusion
|
||||
* Create a dialog for choosing the console command.
|
||||
* Catch error if dynamips is disabled for local and no remote available
|
||||
* Put a link for the GNS3 VM in the setup wizard
|
||||
* When importing appliance explain why options is gray
|
||||
* User configurable default name format for VMware and VirtualBox. Ref #748
|
||||
* Changes "base name prefix" to "default name format". Ref #748.
|
||||
* User configurable base name support for Dynamips, IOU and VPCS. Ref #748.
|
||||
* Refactor "Import config" router dialog. Fixes #752.
|
||||
* Fixes ValueError: cannot mmap an empty file. Fixes #723.
|
||||
* Saves the "show port names" state in topology files. Fixes #778.
|
||||
* Fix KeyError: 'midplane' when loading 7200 in some cases
|
||||
* Hide the server select box for builtin switch if dynamips local is off
|
||||
* Fix an issue where the Existing image button can disapear from wizard
|
||||
* Fix a race condition when you ask for image list but close the windows
|
||||
* Fix alignements of VMware and VirtualBox in VM choice type
|
||||
* Better explanation during server choice
|
||||
* Disabled remote button when we have no remote in server wizard
|
||||
* Improved lookup for VMware host type. Fixes #970.
|
||||
* Change some text in nodes view.
|
||||
* Allow to edit a node via a right click in the node
|
||||
* Show error if a problem occur when getting remote server KVM status
|
||||
* Display a clean error when an appliance has an invalid JSON
|
||||
* MobaXterm integration
|
||||
* Allow to show the command line used to start a VM
|
||||
* Add - GNS3 at the end of the windows name
|
||||
* Fix a crash with doctor on windows
|
||||
* Fix crash in doctor if ubridge path is empty
|
||||
|
||||
## 1.4.1 01/02/2016
|
||||
|
||||
* Improvement to detect VMware Player on Linux. Ref #970.
|
||||
* You can move Dock widgets everywhere
|
||||
* Link to download VIX api
|
||||
* Fix SSH present in the server preferences
|
||||
* Check dynamips and ubridge permission.
|
||||
* Show a dialog for checking some common issues
|
||||
* Show a summary with server usages
|
||||
* Close project on VM when closing the project on all servers
|
||||
* Allow to not rotate the text when changing all text colors
|
||||
* Raise an error when psutil is too old
|
||||
* Fix a race condition when closing the server
|
||||
* Fix a very very rare crash in topology summary view
|
||||
* Reset port label positions. Fixes #811.
|
||||
* Drop SSH support
|
||||
* Fix inversion of port label when loading a topology
|
||||
* Fix error when importing Windows XP OVA
|
||||
* Warn if configuration file contain invalid unicode characters
|
||||
* Fix a crash when importing some OVA
|
||||
* Reset the telnet command on Mac
|
||||
* Fix only one console work for OSX
|
||||
* Fix a rare crash when loading topology with missing image
|
||||
* Fix a rare race condition when inserting an image
|
||||
* Fix a crash when pid file is empty
|
||||
* Fix a rare crash in progress dialogs
|
||||
* Fix a crash if you don't have vms when importing a gns3a
|
||||
* Warn user during appliance install if server is not avaible
|
||||
* Fix error when you stop the GNS3 VM but break the config before
|
||||
* startup_config is not mandatory inside .gns3 file
|
||||
* Fix wrong host on SSH connection
|
||||
* Fix select of image broken if you need to select multiple images
|
||||
* Fix a crash when changing qemu cluster size to more than 512
|
||||
* Fix IOU support in gns3a
|
||||
* Add urxvt support
|
||||
* Add a step in the wizard checking KVM support
|
||||
|
||||
## 1.4.0 12/01/2016
|
||||
|
||||
* Fix rare crash when showing the progress dialog
|
||||
* Fix crash on Windows when a gui is already running
|
||||
* Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389.
|
||||
|
||||
## 1.4.0rc3 05/01/2016
|
||||
|
||||
* Add information about antivirus and firewall in case of connection fail
|
||||
* Change link to doc for missing router image
|
||||
* On windows and OSX experimental Qemu GNS3A support
|
||||
* Wait server in thread
|
||||
* Fix local server non avaible in appliance wizard when local server started by hand.
|
||||
* Improve pid checks
|
||||
* Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide").
|
||||
* Fix project non closing when you have only remote servers
|
||||
* Fix Windows layout not saved in some scenario
|
||||
* Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables.
|
||||
* Added missing Qemu adapters to the topology schema.
|
||||
* Fix Cannot save my topology getting an error message for temporary topology
|
||||
* Fix creation of ASA devices
|
||||
* Turn off Docker until 1.5
|
||||
* Fix display of server preferences on small screen
|
||||
* Fix If you turn off the local server and close the gui and reopen preferences you have an issue
|
||||
* Zoc 7 support
|
||||
* Fix warning when closing GUI
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
|
||||
## 1.3.13 11/12/2015
|
||||
|
||||
* Release server 1.3.13
|
||||
|
||||
## 1.3.12 11/12/2015
|
||||
|
||||
* Fix warning when closing GUI
|
||||
* Update links for new website.
|
||||
* Ask user to send bug reports to GNS3.com
|
||||
* Fix travis dependencies installation
|
||||
* Drop Webkit from 1.3.X
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
* Set Wireshark 2.0 as default OSX version
|
||||
* iTerm 2.9 support
|
||||
* Add informations about GNS3 VM
|
||||
* Drops securecrt.vbs
|
||||
* Fix analytics report on OSX
|
||||
* Analytics send windows
|
||||
* Fix the progress dialog freeze bug
|
||||
* Fix typo in analytics
|
||||
* Send stats to GNS3 team
|
||||
* Licenses compliance.
|
||||
* Fix error when importing dynamips config from non existent directory
|
||||
* Fix crash when url is invalid
|
||||
* Add a debug level 2 in the console
|
||||
|
||||
## 1.4.0rc2 10/12/2015
|
||||
|
||||
* Prevent user turning off the Local server when using the GNS3 VM
|
||||
* Force VM wizard to be modal
|
||||
* Fix unicode error when exporting debug informations
|
||||
* Fix Debug can't be deactivated for current session
|
||||
* Support relative path for configuration file.
|
||||
* Report bug to GNS3.com
|
||||
* Avoid crash when cancel connection to a server
|
||||
* Fix version number display twice when installing appliance
|
||||
* Show a warning when you try to save as a remote topology
|
||||
* Fix application restart after self upgrade
|
||||
* Cleanup server autostart
|
||||
* Have default console port start from 2000.
|
||||
* Fix crash when opening a new topologies after gns3 converter failure
|
||||
* Fix SSH support
|
||||
* Fix bus error when writting on console
|
||||
* Fix Ok & Cancel button in preferences are broken
|
||||
* Fix After a project load failure you can't open new project
|
||||
* More fix around closing the GUI
|
||||
* Fix crash in progress dialog on OSX
|
||||
* Kill already running zombie server
|
||||
* Store the pid of the server when started
|
||||
* Fix a crash in rare case after a PyQT garbage collect
|
||||
* Allow to use the VNC port range for console
|
||||
* Preferences dialog resize
|
||||
* Fix a race condition when opening telnet from apple script
|
||||
* Experimental support for tabbed terminal on OSX
|
||||
* Fix validation error when saving topology with an usage
|
||||
* Fix another case of not closing windows
|
||||
* Fix GUI doesn't close after connection error to remote server
|
||||
* Add usage text to device template and on hover
|
||||
* Fix a rare crash in progress dialog
|
||||
* Fix crash when displaying an error from the update
|
||||
* Url encode royal tx url
|
||||
* Use Royal TX URI scheme thanks to @lemonmojo
|
||||
* OSX support for Royal TSX
|
||||
* Merge branch 'master' into unstable
|
||||
* Set Wireshark 2.0 as default OSX version
|
||||
* iTerm 2.9 support
|
||||
* Update debug information dialog.
|
||||
* Change text for export debug information.
|
||||
* Add informations about GNS3 VM
|
||||
|
||||
## 1.4.0rc1 12/15/2015
|
||||
|
||||
* Rename an appliance if the default name is already taken
|
||||
* Existing image option should be hidden when none is available
|
||||
* Warn users about the need to uncompress the image
|
||||
* Fix crash when you have no qemu vms and use gns3a
|
||||
* Change sentry key
|
||||
* Fix format_exception() missing 2 required positional arguments: 'value' and 'tb' in topologyFile
|
||||
* Fix dialog box not returning their result
|
||||
* Log to console the Qt Message Boxes
|
||||
* Drops securecrt.vbs
|
||||
|
||||
## 1.4.0b5 02/11/2015
|
||||
|
||||
* Fix crash when loading invalid appliance file
|
||||
* Show a message is starting or is stopping in progress dialog
|
||||
* Drop duplicate code
|
||||
* Changes some references for "GNS3 VM" to "Local GNS3 VM". Ref #506.
|
||||
* Fixes progress dialog remains #741.
|
||||
* Support more boot order for Qemu
|
||||
* Add Royal TS terminal
|
||||
* Add a Qt compatible version of partial
|
||||
* Show Upload filename instead of waiting for
|
||||
* Fix error where ISO is detected as an OVA during gns3a import
|
||||
* Removes News Dock Widget.
|
||||
* Support cdrom image in missing images detections
|
||||
* Fix crash when appliance is missing
|
||||
* Support for capture description in Wireshark window title.
|
||||
* Set the name of the VM in OSX Terminal application
|
||||
* Fix crash of symbol selector if the first file is a non builtin symbol
|
||||
* Install appliance from wizard instead of web app
|
||||
* Fix crash when using an old version of 1.4 server
|
||||
* Ensure default settings are saved when starting the app
|
||||
|
||||
## 1.4.0b4 19/10/2015
|
||||
|
||||
* Mockup of appliances wizard
|
||||
* Fix tests
|
||||
* Fix Crash when opening an appliance #728
|
||||
* Mock up for appliance wizard.
|
||||
* Registry: add -nographic to Qemu options by default. Fixes #730.
|
||||
* Registry: support for initrd, cpu throttling and process priority.
|
||||
* Support for modifications to a base Qemu VM (not a linked clone).
|
||||
* Registry: adds support for switch category, first_port_name and port_segment_size.
|
||||
* Fix traceback when exiting the GUI
|
||||
* Show a download button
|
||||
* Corrects some typos.
|
||||
* Drops securecrt.vbs
|
||||
* Display an error message if QtWebkit is not installed.
|
||||
* Fix console port lost when applying settings
|
||||
* Remove unused code
|
||||
* Fix analytics report on OSX
|
||||
* Fix invalid path with frozen application
|
||||
* When raising an appliance not found error show full path
|
||||
* Remove unnecessary checks to know if the local server is running.
|
||||
* Analytics send windows
|
||||
* Fixes issue when loading a project using VMware vmnet interfaces. Fixes #319.
|
||||
* Fix the progress dialog freeze bug
|
||||
* Revert "Try to solve Scanning for Appliance images doesn't end"
|
||||
* Try to fix the progress dialog freeze bug
|
||||
* Drop dead code from getting started dialog
|
||||
* Fix Appliance installs image without adapting the filename
|
||||
* Raise error if reference in GNS3a is invalid
|
||||
* Fix typo in analytics
|
||||
* Add information on how to debug
|
||||
* Send stats to GNS3 team
|
||||
* Licenses compliance.
|
||||
* Handles warning notifications.
|
||||
* Try to solve Scanning for Appliance images doesn't end
|
||||
* Fix TypeError: 'NoneType' object is not iterable in isLocalServerRunning
|
||||
* Fix crash AttributeError: 'NoneType' object has no attribute getNewProjectSettings
|
||||
* Fix error when importing dynamips config from non existent directory
|
||||
* Fix crash when url is invalid
|
||||
* Add a debug level 2 in the console
|
||||
* Fix a crash when loading appliance
|
||||
* Merge branch 'master' into unstable
|
||||
* Support upload of multiple vmdk file
|
||||
* Support PNG in the custom symbol selection dialog
|
||||
* Add custom messages when computing Idle-PC values. Fixes #704.
|
||||
* Display the version of Qt in the console
|
||||
* Do not crash when parsing a Qt version with a snapshot notation
|
||||
* Force nc path to /usr/bin/nc on Apple
|
||||
* Revert "Drop netcat for unix socket it's not supported by OSX"
|
||||
* Catch errors when we have an infinite recursion when copying a folder
|
||||
* Fix crash in recent files when changing locale
|
||||
* Catch error when we can't extract egg
|
||||
* Fix securecrt command line (backported from master)
|
||||
* Updates SecureCRT command line.
|
||||
* When it's an ova explain to user he need to download the ova
|
||||
* OVA file support
|
||||
* Ignore .cache directory
|
||||
* Fix update manager crash on Windows
|
||||
* Support for image in local subdirectory
|
||||
* Fix duplicate code
|
||||
* Fixes issue when saving Idle-PC into template. Fixes #674.
|
||||
* Add link for downloading VMware
|
||||
* Cache md5sum in memory when loading a gns3a
|
||||
* Support symbol import from GNS3A
|
||||
* Allow user to select symbol from his library
|
||||
* Improve HTTP progress reliability
|
||||
* Support ubuntu default VNC client (Vinagre)
|
||||
* Adds the COPYING file.
|
||||
* Fix error when receiving an HTTP error during HTTP progress
|
||||
* Support port_name_format in GNS3 a files
|
||||
* options is not mandatory in a .gns3 file
|
||||
* Xshell 5 support
|
||||
* Add missing gns3-converter to requirements.txt
|
||||
* Fixes Qemu binaries not listed in the node configuration dialog. Fixes #683.
|
||||
* Fixes SecureCRT command line.
|
||||
* Speedup directory scan for images when loading a gns3a file
|
||||
* Show a progress bar during directory scan when searching for appliances
|
||||
* Search image by default also in the download directory
|
||||
* Fixes issue when Telnet doesn't let you to login to an appliance on Linux.
|
||||
|
||||
## 1.3.11 07/10/2015
|
||||
|
||||
* Display the version of Qt in the console
|
||||
* Catch errors when we have an infinite recursion when copying a folder
|
||||
* Fix crash in recent files when changing locale
|
||||
* Catch error when we can't extract egg
|
||||
* Updates SecureCRT command line.
|
||||
* Fixes issue when saving Idle-PC into template. Fixes #674.
|
||||
* Adds the COPYING file.
|
||||
* Xshell 5 support
|
||||
* Add missing gns3-converter to requirements.txt
|
||||
* Fixes issue when Telnet doesn't let you to login to an appliance on Linux.
|
||||
* Improve alignments. Fixes #215.
|
||||
* Spelling correction
|
||||
|
||||
## 1.4.0b3 22/09/15
|
||||
|
||||
* Add a warning if you don't select VMware or VBox in Setup Wizard
|
||||
* Fix Configuration not always saved in client
|
||||
* Fixes file path reference issue.
|
||||
* Fix Appliance doesn't work on local Server #669
|
||||
* Allows Qemu VM template editing if the server is not running. Fixes #671.
|
||||
* Fixes NIO_VMNET != NIO_VMnet.
|
||||
* Automatically add the -no-kvm option if -icount is detected to help with the migration of ASA VMs created before version 1.4
|
||||
* Fix the Runtime error
|
||||
* Improve alignments. Fixes #215.
|
||||
* Updates kernel command line of ASA.
|
||||
|
||||
## 1.4.0beta2 17/09/15
|
||||
|
||||
* Drop netcat for unix socket it's not supported by OSX
|
||||
* VMware fusion is supported
|
||||
* Fix race conditions in http_client
|
||||
* On OSX show a warning when using an old Qemu
|
||||
* Tab support for xfce4-terminal
|
||||
* Improve alignments. Fixes #215.
|
||||
* Fixes ethertype validation error.
|
||||
* Improve check for uBridge permissions.
|
||||
* Fix an uuid is display instead of the server url
|
||||
* Set root permission to ubridge on OSX
|
||||
* Show GNS3 version at startup
|
||||
* Allows to select a remote server to run a switch. Fixes #653.
|
||||
* Support for packet capture on VMware VM links.
|
||||
* Always use ubridge with VMware by default
|
||||
* Fix PermissionError: [Errno 1] Operation not permitted when kill process
|
||||
* Support drag & drop gns3a
|
||||
* Fix an error when loading IOU schema with private_config
|
||||
* Support open a file via double click on OSX
|
||||
* Initial version of an appliance file format
|
||||
* Adds docker symbols. Fixes #643.
|
||||
* Call the vmnet management script from the GUI (with admin rights). Implements #639.
|
||||
* Use self update only if experimental features are allowed
|
||||
* Add an option for enabling experimental features
|
||||
* Backport docker support from Google Summer Of Code (not enabled)
|
||||
* Merge branch 'qinq_ethertype' of https://github.com/Bevaz/gns3-gui into Bevaz-qinq_ethertype
|
||||
* Allows VMware VMs to use vmnet interfaces for connections without using uBridge.
|
||||
* Add a firewall symbol
|
||||
* Detect broken link in topologies
|
||||
* Fix project not closing
|
||||
* Fix autostart
|
||||
* Fix file not found exception in vpcs list dir
|
||||
* Add missing virtio-net-pci to the json schema
|
||||
* Fix the all devices views
|
||||
* Fix double click event on Note Item
|
||||
* Fix Accepting insecure https connections creates additional server entry
|
||||
* Allow developer to debug packet capture on Windows
|
||||
* Fix saveAs error unsupported operand type(s) for +=: 'NoneType' and 'str'
|
||||
* Catch error when antivirus corrupt our own JSON errors
|
||||
* Add a note about VIX API require for VMware player
|
||||
* Create image directory if not exists
|
||||
* Allow GUI to be start with python -m gns3
|
||||
* Avoid errors in qemu configuration if server has been deleted
|
||||
* Fix error when the IOS image directory is not writable
|
||||
* Do not crash if something intercept the call to the update server
|
||||
* Fix super(): no arguments in SSH client
|
||||
* Catch error when configuration file contain invalid UTF-8 chars
|
||||
* Fix JSON schema for dynamips power supply and sensors
|
||||
* Fix missing boot_priority in JSON schema
|
||||
* Complete the error message about corrupted topologies
|
||||
* Removes ASAv warning in Qemu Wizard.
|
||||
* EthernetSwitch: Allow to choose ethertype for QinQ outer tag.
|
||||
* Adds missing properties for rectangle and ellipse in schema validation.
|
||||
* Use Qemu 0.11.0 instead of version 0.13.0 on Windows.
|
||||
* Fixes bug when opening Node properties dialog via a double click.
|
||||
* SecureCRT (installed on personal profile) command line.
|
||||
|
||||
## 1.3.10 04/09/2015
|
||||
|
||||
* Updates kernel command line of ASA.
|
||||
* Fix file not found exception in vpcs list dir
|
||||
* Fix saveAs error unsupported operand type(s) for +=: 'NoneType' and 'str'
|
||||
* Catch error when antivirus corrupt our own JSON errors
|
||||
* Use Qemu 0.11.0 instead of version 0.13.0 on Windows.
|
||||
* Removes "resources_type" references. Fixes #493.
|
||||
* Fixes bug when opening Node properties dialog via a double click.
|
||||
* SecureCRT (installed on personal profile) command line.
|
||||
|
||||
## 1.4.0beta1 07/08/2015
|
||||
|
||||
* Show an error if you try to use a local server not started
|
||||
|
||||
50
CONTRIBUTING.md
Normal file
50
CONTRIBUTING.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Contributing to GNS3
|
||||
|
||||
We welcome contributions and bugs reports from everyone.
|
||||
We are friendly so don't be afraid to ask questions.
|
||||
|
||||
## Bug reports
|
||||
|
||||
Before reporting an issue:
|
||||
* check our website over at https://gns3.com
|
||||
* check if an issue already exists on https://github.com/GNS3/gns3-gui
|
||||
* check if an issue already exists on https://github.com/GNS3/gns3-server
|
||||
|
||||
Please post on our community website if you are unsure you found a bug,
|
||||
you will get faster support and be able to exchange with more users.
|
||||
|
||||
If you are unsure which project you should create an issue for, just do
|
||||
it on https://github.com/GNS3/gns3-gui we will take care of the triage.
|
||||
|
||||
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
|
||||
|
||||
## Asking for new features
|
||||
|
||||
The best is to start a discussion on the community website in order to get feedback
|
||||
from the whole community.
|
||||
|
||||
|
||||
## Contributing code
|
||||
|
||||
We welcome code contribution from everyone including beginners.
|
||||
Don't be afraid to submit a half finished or mediocre contribution and we will help you.
|
||||
|
||||
Don't hesitate to share your plans before starting working on a contribution, we can help
|
||||
you to find the best approach.
|
||||
|
||||
### Contributors License Agreements
|
||||
|
||||
We at GNS3 are eager to work with you. For small changes — little bugfixes, correcting typos, and the like — please just submit pull requests to any of our projects. For larger changes, though, we have to ask you to jump through a little hoop.
|
||||
|
||||
In particular, in order for us to accept any major patches from you, you will have to electronically sign a statement that indicates two things:
|
||||
|
||||
- You are willingly licensing your contributions under the terms of the open source license of the project that you’re contributing to.
|
||||
- You are legally able to license your contributions as stated.
|
||||
|
||||
The reason we do this is to ensure, to the extent possible, that we don’t “taint” the projects we manage with contributions that turn out to be improper. This protects everyone who wants to use the projects, including you!
|
||||
|
||||
More information there: https://github.com/GNS3/cla
|
||||
|
||||
### Pull requests
|
||||
|
||||
Creating a pull request is the easiest way to contribute code. Do not hesitate to create one early when contributing for new feature in order to get our feedback.
|
||||
522
COPYING
Normal file
522
COPYING
Normal file
@@ -0,0 +1,522 @@
|
||||
GNU Public License (GPL)
|
||||
------------------------
|
||||
|
||||
GNS3 is released under the GPLv3 (see LICENSE) with the additional
|
||||
exemption that compiling, linking, and/or using OpenSSL is allowed.
|
||||
|
||||
GNS3 trademark
|
||||
--------------
|
||||
|
||||
"GNS3" is a trademark of GNS3 Technologies, Inc.
|
||||
|
||||
Windows Driver Kit
|
||||
------------------
|
||||
|
||||
The Windows binary distribution includes devcon.exe, a Microsoft(R)
|
||||
Windows Driver Kit (WDK) sample in object code form which is
|
||||
redistributed under the terms of the WDK License terms.
|
||||
|
||||
With respect to binaries built using the Microsoft(R) Windows
|
||||
Driver Kit (WDK), GPLv3 does not extend to any WDK Distributable Code.
|
||||
All WDK Distributable Code is considered by the licensors of GNS3
|
||||
to constitute, or be equivalent to, "System Libraries" as defined in
|
||||
section 1 of GPLv3.
|
||||
|
||||
OpenSSL License
|
||||
---------------
|
||||
|
||||
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
|
||||
the OpenSSL License and the original SSLeay license apply to the toolkit.
|
||||
See below for the actual license texts. Actually both licenses are BSD-style
|
||||
Open Source licenses. In case of any license issues related to OpenSSL
|
||||
please contact openssl-core@openssl.org.
|
||||
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2003 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This product includes cryptographic software written by Eric Young
|
||||
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||
* Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
*/
|
||||
|
||||
Original SSLeay License
|
||||
-----------------------
|
||||
|
||||
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* This package is an SSL implementation written
|
||||
* by Eric Young (eay@cryptsoft.com).
|
||||
* The implementation was written so as to conform with Netscapes SSL.
|
||||
*
|
||||
* This library is free for commercial and non-commercial use as long as
|
||||
* the following conditions are aheared to. The following conditions
|
||||
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||
* included with this distribution is covered by the same copyright terms
|
||||
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||
* the code are not to be removed.
|
||||
* If this package is used in a product, Eric Young should be given attribution
|
||||
* as the author of the parts of the library used.
|
||||
* This can be in the form of a textual message at program startup or
|
||||
* in documentation (online or textual) provided with the package.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* "This product includes cryptographic software written by
|
||||
* Eric Young (eay@cryptsoft.com)"
|
||||
* The word 'cryptographic' can be left out if the rouines from the library
|
||||
* being used are not cryptographic related :-).
|
||||
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||
* the apps directory (application code) you must include an acknowledgement:
|
||||
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* The licence and distribution terms for any publically available version or
|
||||
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||
* copied and put under another distribution licence
|
||||
* [including the GNU Public Licence.]
|
||||
*/
|
||||
|
||||
=====================================================================================================
|
||||
Several fantastic pieces of free and open-source software have really get GNS3 to where it is today.
|
||||
A few require that we include their license agreements within our software.
|
||||
=====================================================================================================
|
||||
|
||||
License notice for Qt
|
||||
---------------------
|
||||
http://doc.qt.io/qt-4.8/gpl.html
|
||||
|
||||
License notice for PyQt
|
||||
-----------------------
|
||||
http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
License notice for jsonschema
|
||||
-----------------------------
|
||||
https://github.com/Julian/jsonschema/blob/master/COPYING
|
||||
|
||||
Copyright (c) 2013 Julian Berman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
License notice for aiohttp
|
||||
--------------------------
|
||||
https://github.com/KeepSafe/aiohttp/blob/master/LICENSE.txt
|
||||
|
||||
Copyright (c) 2013, 2014, 2015 Nikolay Kim and Andrew Svetlov
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
License notice for Jinja
|
||||
------------------------
|
||||
https://github.com/KeepSafe/aiohttp/blob/master/LICENSE.txt
|
||||
|
||||
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License notice for raven
|
||||
------------------------
|
||||
https://github.com/getsentry/raven-python/blob/master/LICENSE
|
||||
|
||||
Copyright (c) 2015 Functional Software, Inc and individual contributors.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License notice for Apache Libcloud
|
||||
----------------------------------
|
||||
https://github.com/apache/libcloud/blob/trunk/LICENSE
|
||||
|
||||
Copyright (c) 2010-2015 The Apache Software Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
License notice for requests
|
||||
---------------------------
|
||||
https://github.com/kennethreitz/requests/blob/master/LICENSE
|
||||
|
||||
Copyright (c) 2015 Kenneth Reitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
License notice for gns3-converter
|
||||
---------------------------------
|
||||
https://github.com/dlintott/gns3-converter/blob/master/COPYING
|
||||
|
||||
License notice for paramiko
|
||||
---------------------------
|
||||
https://github.com/paramiko/paramiko/blob/master/LICENSE
|
||||
|
||||
License notice for pywin32
|
||||
--------------------------
|
||||
https://github.com/SublimeText/Pywin32/blob/master/License.txt
|
||||
|
||||
Unless stated in the specfic source file, this work is
|
||||
Copyright (c) 1996-2008, Greg Stein and Mark Hammond.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither names of Greg Stein, Mark Hammond nor the name of contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License notice for Winpcap
|
||||
--------------------------
|
||||
https://www.winpcap.org/misc/copyright.htm
|
||||
|
||||
Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy).
|
||||
Copyright (c) 2005 - 2010 CACE Technologies, Davis (California).
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License notice for cpulimit
|
||||
---------------------------
|
||||
https://github.com/opsengine/cpulimit/blob/master/LICENSE
|
||||
|
||||
Copyright (C) 2005-2012, by: Angelo Marletta <angelo dot marletta at gmail dot com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
License notice for Cygwin
|
||||
-------------------------
|
||||
https://cygwin.com/licensing.html
|
||||
|
||||
License notice for SuperPutty
|
||||
-----------------------------
|
||||
https://github.com/jimradford/superputty/blob/master/License.txt
|
||||
|
||||
Copyright (c) 2009 Jim Radford http://www.jimradford.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
License notice for Putty
|
||||
------------------------
|
||||
http://www.chiark.greenend.org.uk/~sgtatham/putty/licence.html
|
||||
|
||||
PuTTY is copyright 1997-2015 Simon Tatham.
|
||||
|
||||
Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar,
|
||||
Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn,
|
||||
Colin Watson, Christopher Staite, and CORE SDI S.A.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
License notice for iouyap
|
||||
-------------------------
|
||||
https://github.com/GNS3/iouyap/blob/master/LICENSE
|
||||
|
||||
License notice for Dynamips
|
||||
---------------------------
|
||||
https://github.com/GNS3/dynamips/blob/master/COPYING
|
||||
|
||||
License notice for Qemu
|
||||
-----------------------
|
||||
http://wiki.qemu.org/License
|
||||
|
||||
License notice for VPCS
|
||||
-----------------------
|
||||
|
||||
Copyright (c) 2007-2013, Paul Meng (mirnshi@gmail.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License notice for Python
|
||||
-------------------------
|
||||
https://www.python.org/download/releases/3.4.2/license/
|
||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
# Run tests inside a container
|
||||
FROM ubuntu:vivid
|
||||
|
||||
MAINTAINER GNS3 Team
|
||||
|
||||
#ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --force-yes python3.4 python3-pyqt5 python3-pip python3-pyqt5.qtsvg python3.4-dev xvfb
|
||||
RUN apt-get clean
|
||||
|
||||
|
||||
ADD dev-requirements.txt /dev-requirements.txt
|
||||
ADD requirements.txt /requirements.txt
|
||||
RUN pip3 install -r /dev-requirements.txt
|
||||
|
||||
|
||||
ADD . /src
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
CMD xvfb-run python3.4 -m pytest -vv
|
||||
21
README.rst
21
README.rst
@@ -18,7 +18,7 @@ You must be connected to the Internet in order to install the dependencies.
|
||||
|
||||
Dependencies:
|
||||
|
||||
- Python 3.3 or above
|
||||
- Python 3.4 or above
|
||||
- Setuptools
|
||||
- PyQt 5 libraries
|
||||
- Apache Libcloud library
|
||||
@@ -50,7 +50,7 @@ Finally these commands will install the GUI as well as the rest of the dependenc
|
||||
Windows
|
||||
-------
|
||||
|
||||
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
|
||||
Please use our `all-in-one installer <https://gns3.com/software/download>`_ to install the stable build.
|
||||
|
||||
If you install via source you need to first install:
|
||||
|
||||
@@ -103,7 +103,7 @@ Finally, install both the GUI & server from the source.
|
||||
|
||||
Or follow this `HOWTO that uses MacPorts <http://binarynature.blogspot.ca/2014/05/install-gns3-early-release-on-mac-os-x.html>`_.
|
||||
|
||||
Developement
|
||||
Development
|
||||
-------------
|
||||
|
||||
If you want to update the interface, modify the .ui files using QT tools. And:
|
||||
@@ -113,6 +113,21 @@ If you want to update the interface, modify the .ui files using QT tools. And:
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
|
||||
Debug
|
||||
"""""
|
||||
|
||||
If you want to see the full logs in the internal shell you can type:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
debug 2
|
||||
|
||||
|
||||
Or start the app with --debug flag.
|
||||
|
||||
Due to the fact PyQT intercept you can use a web debugger for inspecting stuff:
|
||||
https://github.com/Kozea/wdb
|
||||
|
||||
|
||||
Test with PyQT4
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
20
gns3/__main__.py
Normal file
20
gns3/__main__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .main import main
|
||||
|
||||
main()
|
||||
50
gns3/application.py
Normal file
50
gns3/application.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from .qt import QtWidgets, QtGui, QtCore
|
||||
from .version import __version__
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Application(QtWidgets.QApplication):
|
||||
file_open_signal = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, argv):
|
||||
super().__init__(argv)
|
||||
# this info is necessary for QSettings
|
||||
self.setOrganizationName("GNS3")
|
||||
self.setOrganizationDomain("gns3.net")
|
||||
self.setApplicationName("GNS3")
|
||||
self.setApplicationVersion(__version__)
|
||||
|
||||
# File path if we have received the path to
|
||||
# a file on system via an OSX event
|
||||
self.open_file_at_startup = None
|
||||
|
||||
def event(self, event):
|
||||
# When you double click file you receive an event
|
||||
# and not the file as command line parameter
|
||||
if sys.platform.startswith("darwin"):
|
||||
if isinstance(event, QtGui.QFileOpenEvent):
|
||||
self.open_file_at_startup = str(event.file())
|
||||
self.file_open_signal.emit(str(event.file()))
|
||||
return super().event(event)
|
||||
@@ -200,7 +200,7 @@ class ConsoleCmd(cmd.Cmd):
|
||||
def do_debug(self, args):
|
||||
"""
|
||||
Activate or deactivate debugging messages
|
||||
debug [level] (0 or 1).
|
||||
debug [level] (0, 1 or 2).
|
||||
"""
|
||||
|
||||
if '?' in args or args.strip() == "":
|
||||
@@ -208,16 +208,22 @@ class ConsoleCmd(cmd.Cmd):
|
||||
return
|
||||
|
||||
root = logging.getLogger()
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
|
||||
if len(args) == 1:
|
||||
level = int(args[0])
|
||||
if level == 0:
|
||||
print("Deactivating debugging")
|
||||
root.removeHandler(ch)
|
||||
for handler in root.handlers:
|
||||
if isinstance(handler, logging.StreamHandler):
|
||||
root.removeHandler(handler)
|
||||
root.setLevel(logging.INFO)
|
||||
else:
|
||||
print("Activating debugging")
|
||||
root.addHandler(ch)
|
||||
root.addHandler(logging.StreamHandler(sys.stdout))
|
||||
if level == 1:
|
||||
print("Activating debugging")
|
||||
else:
|
||||
print("Activating full debugging")
|
||||
root.setLevel(logging.DEBUG)
|
||||
from .main_window import MainWindow
|
||||
MainWindow.instance().setSettings({"debug_level": level})
|
||||
else:
|
||||
|
||||
@@ -27,6 +27,7 @@ from .version import __version__
|
||||
from .console_cmd import ConsoleCmd
|
||||
from .pycutext import PyCutExt
|
||||
from .modules import MODULES
|
||||
from .local_config import LocalConfig
|
||||
|
||||
|
||||
class ConsoleView(PyCutExt, ConsoleCmd):
|
||||
@@ -40,8 +41,13 @@ class ConsoleView(PyCutExt, ConsoleCmd):
|
||||
# Set introduction message
|
||||
bitness = struct.calcsize("P") * 8
|
||||
current_year = datetime.date.today().year
|
||||
self.intro = "GNS3 management console. Running GNS3 version {} on {} ({}-bit) with Qt {}.\n" \
|
||||
"Copyright (c) 2006-{} GNS3 Technologies.".format(__version__, platform.system(), bitness, QtCore.QT_VERSION_STR, current_year)
|
||||
self.intro = "GNS3 management console.\nRunning GNS3 version {} on {} ({}-bit) with Python {} Qt {}.\n" \
|
||||
"Copyright (c) 2006-{} GNS3 Technologies.\n" \
|
||||
"Use Help -> GNS3 Doctor to detect common issues." \
|
||||
"".format(__version__, platform.system(), bitness, platform.python_version(), QtCore.QT_VERSION_STR, current_year)
|
||||
|
||||
if LocalConfig.instance().experimental():
|
||||
self.intro += "\nWARNING: Experimental features enable. You can use some unfinished features and lost data."
|
||||
|
||||
# Parent class initialization
|
||||
try:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import psutil
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
@@ -28,14 +29,14 @@ except ImportError:
|
||||
RAVEN_AVAILABLE = False
|
||||
|
||||
from .utils.get_resource import get_resource
|
||||
from .version import __version__
|
||||
from .version import __version__, __version_info__
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Dev build
|
||||
if __version__[4] != 0:
|
||||
if __version_info__[3] != 0:
|
||||
import faulthandler
|
||||
# Display a traceback in case of segfault crash. Usefull when frozen
|
||||
# Not enabled by default for security reason
|
||||
@@ -49,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "sync+https://a86f12c42f7746288a81af9a9c1145a4:db8b6973bd2c448ea0a98675119ff8ee@app.getsentry.com/38506"
|
||||
DSN = "sync+https://3d44e34021504514a5fb0539ae6f8f92:af41562761754b4c9beca492d1b9115d@app.getsentry.com/38506"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = get_resource("cacert.pem")
|
||||
if cacert is not None and os.path.isfile(cacert):
|
||||
@@ -94,7 +95,7 @@ class CrashReport:
|
||||
"python:encoding": sys.getdefaultencoding(),
|
||||
"python:frozen": "{}".format(hasattr(sys, "frozen"))
|
||||
}
|
||||
context = self._add_qt_informations(context)
|
||||
context = self._add_qt_information(context)
|
||||
client.tags_context(context)
|
||||
try:
|
||||
report = client.captureException((exception, value, tb))
|
||||
@@ -103,12 +104,13 @@ class CrashReport:
|
||||
return
|
||||
log.info("Crash report sent with event ID: {}".format(client.get_ident(report)))
|
||||
|
||||
def _add_qt_informations(self, context):
|
||||
def _add_qt_information(self, context):
|
||||
try:
|
||||
from .qt import QtCore
|
||||
import sip
|
||||
except ImportError:
|
||||
return context
|
||||
context["psutil:version"] = psutil.__version__
|
||||
context["pyqt:version"] = QtCore.PYQT_VERSION_STR
|
||||
context["qt:version"] = QtCore.QT_VERSION_STR
|
||||
context["sip:version"] = sip.SIP_VERSION_STR
|
||||
|
||||
511
gns3/dialogs/appliance_wizard.py
Normal file
511
gns3/dialogs/appliance_wizard.py
Normal file
@@ -0,0 +1,511 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ..qt import QtWidgets, QtCore, QtGui, qpartial
|
||||
from ..ui.appliance_wizard_ui import Ui_ApplianceWizard
|
||||
from ..image_manager import ImageManager
|
||||
from ..modules import Qemu
|
||||
from ..registry.appliance import Appliance
|
||||
from ..registry.registry import Registry
|
||||
from ..registry.config import Config, ConfigException
|
||||
from ..registry.image import Image
|
||||
from ..utils import human_filesize
|
||||
from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..servers import Servers
|
||||
from ..gns3_vm import GNS3VM
|
||||
from ..local_config import LocalConfig
|
||||
|
||||
|
||||
class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
|
||||
|
||||
def __init__(self, parent, path):
|
||||
super().__init__(parent)
|
||||
|
||||
self._path = path
|
||||
self.setupUi(self)
|
||||
images_directories = list()
|
||||
images_directories.append(os.path.dirname(self._path))
|
||||
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
|
||||
if download_directory != "" and download_directory != os.path.dirname(self._path):
|
||||
images_directories.append(download_directory)
|
||||
self._registry = Registry(images_directories)
|
||||
self._appliance = Appliance(self._registry, self._path)
|
||||
self._registry.appendImageDirectory(os.path.join(ImageManager.instance().getDirectory(), self._appliance.image_dir_name()))
|
||||
|
||||
self.uiApplianceVersionTreeWidget.currentItemChanged.connect(self._applianceVersionCurrentItemChangedSlot)
|
||||
self.uiRefreshPushButton.clicked.connect(self._refreshVersions)
|
||||
self.uiDownloadPushButton.clicked.connect(self._downloadPushButtonClickedSlot)
|
||||
self.uiImportPushButton.clicked.connect(self._importPushButtonClickedSlot)
|
||||
self.uiCreateVersionPushButton.clicked.connect(self._createVersionPushButtonClickedSlot)
|
||||
|
||||
self.uiRemoteRadioButton.toggled.connect(self._remoteServerToggledSlot)
|
||||
if hasattr(self, "uiVMRadioButton"):
|
||||
self.uiVMRadioButton.toggled.connect(self._vmToggledSlot)
|
||||
|
||||
self.uiLocalRadioButton.toggled.connect(self._localToggledSlot)
|
||||
if hasattr(self, "uiLoadBalanceCheckBox"):
|
||||
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
|
||||
|
||||
self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete
|
||||
|
||||
def initializePage(self, page_id):
|
||||
"""
|
||||
Initialize Wizard pages.
|
||||
|
||||
:param page_id: page identifier
|
||||
"""
|
||||
super().initializePage(page_id)
|
||||
|
||||
if self._appliance["category"] == "guest":
|
||||
symbol = ":/symbols/computer.svg"
|
||||
else:
|
||||
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
|
||||
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
|
||||
|
||||
if "qemu" in self._appliance:
|
||||
type = "qemu"
|
||||
elif "iou" in self._appliance:
|
||||
type = "iou"
|
||||
elif "dynamips" in self._appliance:
|
||||
type = "dynamips"
|
||||
|
||||
if self.page(page_id) == self.uiInfoWizardPage:
|
||||
self.uiInfoWizardPage.setTitle(self._appliance["product_name"])
|
||||
self.uiDescriptionLabel.setText(self._appliance["description"])
|
||||
|
||||
info = (
|
||||
("Category", "category"),
|
||||
("Product", "product_name"),
|
||||
("Vendor", "vendor_name"),
|
||||
("Status", "status"),
|
||||
("Maintainer", "maintainer"),
|
||||
("Architecture", "qemu/arch"),
|
||||
("KVM", "qemu/kvm")
|
||||
)
|
||||
|
||||
self.uiInfoTreeWidget.clear()
|
||||
for (name, key) in info:
|
||||
if "/" in key:
|
||||
key, subkey = key.split("/")
|
||||
value = self._appliance.get(key, {}).get(subkey, None)
|
||||
else:
|
||||
value = self._appliance.get(key, None)
|
||||
if value is None:
|
||||
continue
|
||||
item = QtWidgets.QTreeWidgetItem([name + ":", value])
|
||||
font = item.font(0)
|
||||
font.setBold(True)
|
||||
item.setFont(0, font)
|
||||
self.uiInfoTreeWidget.addTopLevelItem(item)
|
||||
|
||||
elif self.page(page_id) == self.uiServerWizardPage:
|
||||
self.uiRemoteServersComboBox.clear()
|
||||
for server in Servers.instance().remoteServers().values():
|
||||
self.uiRemoteServersComboBox.addItem(server.url(), server)
|
||||
|
||||
if not GNS3VM.instance().isRunning():
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
|
||||
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
|
||||
if type == "qemu":
|
||||
# Qemu has issues on OSX and Windows we disallow usage of the local server
|
||||
if not LocalConfig.instance().experimental():
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
elif type != "dynamips":
|
||||
self.uiLocalRadioButton.setEnabled(False)
|
||||
|
||||
if GNS3VM.instance().isRunning():
|
||||
self.uiVMRadioButton.setChecked(True)
|
||||
elif Servers.instance().localServer().isLocalServerRunning() and self.uiLocalRadioButton.isEnabled():
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
elif len(Servers.instance().remoteServers().values()) > 0:
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
else:
|
||||
self.uiRemoteRadioButton.setChecked(False)
|
||||
|
||||
elif self.page(page_id) == self.uiFilesWizardPage:
|
||||
self._refreshVersions()
|
||||
|
||||
elif self.page(page_id) == self.uiQemuWizardPage:
|
||||
Qemu.instance().getQemuBinariesFromServer(self._server, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
|
||||
|
||||
elif self.page(page_id) == self.uiSummaryWizardPage:
|
||||
self.uiSummaryTreeWidget.clear()
|
||||
|
||||
for key in self._appliance[type]:
|
||||
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance[type][key])])
|
||||
font = item.font(0)
|
||||
font.setBold(True)
|
||||
item.setFont(0, font)
|
||||
self.uiSummaryTreeWidget.addTopLevelItem(item)
|
||||
self.uiSummaryTreeWidget.resizeColumnToContents(0)
|
||||
|
||||
elif self.page(page_id) == self.uiUsageWizardPage:
|
||||
self.uiUsageTextEdit.setText("The appliance is available in the {} category. \n\n{}".format(
|
||||
self._appliance["category"].replace("_", " "),
|
||||
self._appliance.get("usage", ""))
|
||||
)
|
||||
|
||||
elif self.page(page_id) == self.uiCheckServerWizardPage:
|
||||
self.uiCheckServerLabel.setText("Please wait while checking server capacities...")
|
||||
if 'qemu' in self._appliance:
|
||||
if self._appliance['qemu'].get('kvm', 'require') == 'require':
|
||||
self._server_check = False # If the server as the capacities for running the appliance
|
||||
Qemu.instance().getQemuCapabilitiesFromServer(self._server, qpartial(self._qemuServerCapabilitiesCallback))
|
||||
return
|
||||
self.uiCheckServerLabel.setText("")
|
||||
self._server_check = True
|
||||
self.next()
|
||||
|
||||
def _qemuServerCapabilitiesCallback(self, result, error=None, *args, **kwargs):
|
||||
"""
|
||||
Check if server support KVM or not
|
||||
"""
|
||||
if error is None and "kvm" in result and self._appliance["qemu"]["arch"] in result["kvm"]:
|
||||
self._server_check = True
|
||||
self.uiCheckServerLabel.setText("GNS3 server requirements is OK you can continue the installation")
|
||||
else:
|
||||
if error:
|
||||
msg = result["message"]
|
||||
else:
|
||||
msg = "The remote server doesn't support KVM. You need a Linux server or the GNS3 VM with VMware and CPU virtualization instructions."
|
||||
self.uiCheckServerLabel.setText(msg)
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu", msg)
|
||||
self._server_check = False
|
||||
|
||||
def _uiServerWizardPage_isComplete(self):
|
||||
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
|
||||
|
||||
def _refreshVersions(self):
|
||||
"""
|
||||
Refresh the list of files for different version of the appliance
|
||||
"""
|
||||
|
||||
self.uiFilesWizardPage.setSubTitle("The following versions are available for " + self._appliance["product_name"] + ". Check the status of files required to install.")
|
||||
self.uiApplianceVersionTreeWidget.clear()
|
||||
|
||||
worker = WaitForLambdaWorker(lambda: self._resfreshDialogWorker())
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Scanning directories for files...", None, busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_():
|
||||
for version in self._appliance["versions"]:
|
||||
top = QtWidgets.QTreeWidgetItem(["{} {}".format(self._appliance["product_name"], version["name"])])
|
||||
|
||||
size = 0
|
||||
status = "Ready to install"
|
||||
for image in version["images"].values():
|
||||
if image["status"] == "Missing":
|
||||
status = "Missing files"
|
||||
|
||||
size += image.get("filesize", 0)
|
||||
image_widget = QtWidgets.QTreeWidgetItem(
|
||||
[
|
||||
"",
|
||||
image["filename"],
|
||||
human_filesize(image.get("filesize", 0)),
|
||||
image["status"],
|
||||
image["version"],
|
||||
image.get("md5sum", "")
|
||||
])
|
||||
|
||||
if image["status"] == "Missing":
|
||||
image_widget.setForeground(3, QtGui.QBrush(QtGui.QColor("red")))
|
||||
else:
|
||||
image_widget.setForeground(3, QtGui.QBrush(QtGui.QColor("green")))
|
||||
|
||||
# Associated data stored are col 0: version, col 1: image
|
||||
image_widget.setData(0, QtCore.Qt.UserRole, version)
|
||||
image_widget.setData(1, QtCore.Qt.UserRole, image)
|
||||
image_widget.setData(2, QtCore.Qt.UserRole, self._appliance)
|
||||
top.addChild(image_widget)
|
||||
|
||||
font = top.font(0)
|
||||
font.setBold(True)
|
||||
top.setFont(0, font)
|
||||
|
||||
expand = True
|
||||
if status == "Missing files":
|
||||
top.setForeground(3, QtGui.QBrush(QtGui.QColor("red")))
|
||||
else:
|
||||
expand = False
|
||||
top.setForeground(3, QtGui.QBrush(QtGui.QColor("green")))
|
||||
|
||||
top.setData(2, QtCore.Qt.DisplayRole, human_filesize(size))
|
||||
top.setData(3, QtCore.Qt.DisplayRole, status)
|
||||
top.setData(2, QtCore.Qt.UserRole, self._appliance)
|
||||
top.setData(0, QtCore.Qt.UserRole, version)
|
||||
self.uiApplianceVersionTreeWidget.addTopLevelItem(top)
|
||||
if expand:
|
||||
top.setExpanded(True)
|
||||
|
||||
self.uiApplianceVersionTreeWidget.resizeColumnToContents(0)
|
||||
self.uiApplianceVersionTreeWidget.resizeColumnToContents(1)
|
||||
self.uiApplianceVersionTreeWidget.setCurrentItem(self.uiApplianceVersionTreeWidget.topLevelItem(0))
|
||||
|
||||
def _resfreshDialogWorker(self):
|
||||
"""
|
||||
Scan local directory in order to found the images on disk
|
||||
"""
|
||||
for version in self._appliance["versions"]:
|
||||
for image in version["images"].values():
|
||||
img = self._registry.search_image_file(image["filename"], image.get("md5sum"), image.get("filesize"))
|
||||
if img:
|
||||
image["status"] = "Found"
|
||||
image["md5sum"] = img.md5sum
|
||||
image["filesize"] = img.filesize
|
||||
else:
|
||||
image["status"] = "Missing"
|
||||
|
||||
def _applianceVersionCurrentItemChangedSlot(self, current, previous):
|
||||
"""
|
||||
Called when user select a different item in the list of appliance files
|
||||
"""
|
||||
self.uiDownloadPushButton.hide()
|
||||
self.uiImportPushButton.hide()
|
||||
self.uiExplainDownloadLabel.hide()
|
||||
|
||||
if current is None:
|
||||
return
|
||||
|
||||
image = current.data(1, QtCore.Qt.UserRole)
|
||||
if image is not None:
|
||||
if "direct_download_url" in image or "download_url" in image:
|
||||
self.uiDownloadPushButton.show()
|
||||
self.uiImportPushButton.show()
|
||||
|
||||
def _downloadPushButtonClickedSlot(self):
|
||||
"""
|
||||
Called when user want to download an appliance images.
|
||||
He should have selected the file before.
|
||||
"""
|
||||
current = self.uiApplianceVersionTreeWidget.currentItem()
|
||||
data = current.data(1, QtCore.Qt.UserRole)
|
||||
if data is not None:
|
||||
if "direct_download_url" in data:
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["direct_download_url"]))
|
||||
if "compression" in data:
|
||||
QtWidgets.QMessageBox.warning(self, "Add appliance", "The file is compressed with {} you need to uncompress it before using it.".format(data["compression"]))
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "Add appliance", "Download will redirect you where the required file can be downloaded, you may have to be registered with the vendor in order to download the file.")
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["download_url"]))
|
||||
|
||||
def _createVersionPushButtonClickedSlot(self):
|
||||
"""
|
||||
Allow user to create a new version of an appliance
|
||||
"""
|
||||
|
||||
new_version, ok = QtWidgets.QInputDialog.getText(self, "Creating a new version", "Creating a new version allows to import unknown files to use with this appliance.\nPlease share your experience on the GNS3 community if this version works.\n\nVersion name:", QtWidgets.QLineEdit.Normal)
|
||||
if ok:
|
||||
self._appliance.create_new_version(new_version)
|
||||
self._refreshVersions()
|
||||
|
||||
def _importPushButtonClickedSlot(self):
|
||||
"""
|
||||
Called when user want to import an appliance images.
|
||||
He should have selected the file before.
|
||||
"""
|
||||
|
||||
current = self.uiApplianceVersionTreeWidget.currentItem()
|
||||
disk = current.data(1, QtCore.Qt.UserRole)
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName()
|
||||
if len(path) == 0:
|
||||
return
|
||||
|
||||
image = Image(path)
|
||||
if "md5sum" in disk and image.md5sum != disk["md5sum"]:
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct file. The MD5 sum is {} and should be {}. For OVA you need to import the OVA/OVF not the file inside the archive.".format(image.md5sum, disk["md5sum"]))
|
||||
return
|
||||
|
||||
config = Config()
|
||||
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, self._appliance.image_dir_name()), disk["filename"]), allowed_exceptions=[OSError, ValueError])
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Importing the appliance...", None, busy=True, parent=self)
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
self._refreshVersions()
|
||||
|
||||
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for getQemuBinariesFromServer.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
|
||||
else:
|
||||
self.uiQemuListComboBox.clear()
|
||||
for qemu in result:
|
||||
if qemu["version"]:
|
||||
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
|
||||
else:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
|
||||
if self.uiQemuListComboBox.count() == 1:
|
||||
self.next()
|
||||
|
||||
def _install(self, version):
|
||||
"""
|
||||
Install the appliance to GNS3
|
||||
|
||||
:params version: Version name
|
||||
"""
|
||||
|
||||
try:
|
||||
config = Config()
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "Add appliance", str(e))
|
||||
return False
|
||||
|
||||
appliance_configuration = self._appliance.search_images_for_version(version)
|
||||
|
||||
if self._server.isLocal():
|
||||
server_string = "local"
|
||||
elif self._server.isGNS3VM():
|
||||
server_string = "vm"
|
||||
else:
|
||||
server_string = self._server.url()
|
||||
|
||||
while len(appliance_configuration["name"]) == 0 or not config.is_name_available(appliance_configuration["name"]):
|
||||
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "The name \"{}\" is already used by another appliance".format(appliance_configuration["name"]))
|
||||
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add appliance", "New name:", QtWidgets.QLineEdit.Normal, appliance_configuration["name"])
|
||||
appliance_configuration["name"] = appliance_configuration["name"].strip()
|
||||
|
||||
if "qemu" in appliance_configuration:
|
||||
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
|
||||
|
||||
worker = WaitForLambdaWorker(lambda: config.add_appliance(appliance_configuration, server_string), allowed_exceptions=[ConfigException, OSError])
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return False
|
||||
|
||||
worker = WaitForLambdaWorker(lambda: config.save(), allowed_exceptions=[ConfigException, OSError])
|
||||
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_():
|
||||
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
|
||||
return True
|
||||
|
||||
def nextId(self):
|
||||
if self.currentPage() == self.uiServerWizardPage:
|
||||
if "qemu" not in self._appliance:
|
||||
return super().nextId() + 1
|
||||
elif self.currentPage() == self.uiFilesWizardPage:
|
||||
if "qemu" not in self._appliance:
|
||||
return super().nextId() + 1
|
||||
return super().nextId()
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Validates the settings.
|
||||
"""
|
||||
|
||||
if self.currentPage() == self.uiFilesWizardPage:
|
||||
current = self.uiApplianceVersionTreeWidget.currentItem()
|
||||
version = current.data(0, QtCore.Qt.UserRole)
|
||||
appliance = current.data(2, QtCore.Qt.UserRole)
|
||||
if not self._appliance.is_version_installable(version["name"]):
|
||||
QtWidgets.QMessageBox.warning(self, "Appliance", "Sorry, you cannot install {} with missing files".format(appliance["name"]))
|
||||
return False
|
||||
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Would you like to install {} version {}?".format(appliance["name"], version["name"]),
|
||||
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return False
|
||||
|
||||
elif self.currentPage() == self.uiUsageWizardPage:
|
||||
current = self.uiApplianceVersionTreeWidget.currentItem()
|
||||
version = current.data(0, QtCore.Qt.UserRole)
|
||||
return self._install(version["name"])
|
||||
|
||||
elif self.currentPage() == self.uiServerWizardPage:
|
||||
if self.uiRemoteRadioButton.isChecked():
|
||||
if not Servers.instance().remoteServers():
|
||||
QtWidgets.QMessageBox.critical(self, "Remote server", "There is no remote server registered in your preferences")
|
||||
return False
|
||||
self._server = self.uiRemoteServersComboBox.itemData(self.uiRemoteServersComboBox.currentIndex())
|
||||
elif hasattr(self, "uiVMRadioButton") and self.uiVMRadioButton.isChecked():
|
||||
gns3_vm_server = Servers.instance().vmServer()
|
||||
if gns3_vm_server is None:
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The GNS3 VM is not running")
|
||||
return False
|
||||
self._server = gns3_vm_server
|
||||
else:
|
||||
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
|
||||
if "qemu" in self._appliance:
|
||||
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and MacOSX is not supported by the GNS3 team. Are you sur to continue?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return False
|
||||
|
||||
self._server = Servers.instance().localServer()
|
||||
|
||||
elif self.currentPage() == self.uiQemuWizardPage:
|
||||
if self.uiQemuListComboBox.currentIndex() == -1:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
|
||||
return False
|
||||
|
||||
elif self.currentPage() == self.uiCheckServerWizardPage:
|
||||
return self._server_check
|
||||
|
||||
return True
|
||||
|
||||
def _vmToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the VM radio button is toggled.
|
||||
|
||||
:param checked: either the button is checked or not
|
||||
"""
|
||||
if checked:
|
||||
self.uiRemoteServersGroupBox.setEnabled(False)
|
||||
self.uiRemoteServersGroupBox.hide()
|
||||
|
||||
def _remoteServerToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the remote server radio button is toggled.
|
||||
|
||||
:param checked: either the button is checked or not
|
||||
"""
|
||||
|
||||
if checked:
|
||||
self.uiRemoteServersGroupBox.setEnabled(True)
|
||||
self.uiRemoteServersGroupBox.show()
|
||||
|
||||
def _localToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the local server radio button is toggled.
|
||||
|
||||
:param checked: either the button is checked or not
|
||||
"""
|
||||
if checked:
|
||||
self.uiRemoteServersGroupBox.setEnabled(False)
|
||||
self.uiRemoteServersGroupBox.hide()
|
||||
|
||||
def _loadBalanceToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the load balance checkbox is toggled.
|
||||
|
||||
:param checked: either the box is checked or not
|
||||
"""
|
||||
|
||||
if checked:
|
||||
self.uiRemoteServersComboBox.setEnabled(False)
|
||||
else:
|
||||
self.uiRemoteServersComboBox.setEnabled(True)
|
||||
135
gns3/dialogs/console_command_dialog.py
Normal file
135
gns3/dialogs/console_command_dialog.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import copy
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.ui.console_command_dialog_ui import Ui_uiConsoleCommandDialog
|
||||
from gns3.settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, \
|
||||
PRECONFIGURED_SERIAL_CONSOLE_COMMANDS, \
|
||||
PRECONFIGURED_VNC_CONSOLE_COMMANDS, \
|
||||
CUSTOM_CONSOLE_COMMANDS_SETTINGS
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConsoleCommandDialog(QtWidgets.QDialog, Ui_uiConsoleCommandDialog):
|
||||
"""
|
||||
This dialog allow user to select the command used to start a
|
||||
console.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, console_type="telnet", current=None):
|
||||
"""
|
||||
:params console_type: telnet, serial or vnc
|
||||
:params current: Current console command
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self._console_type = console_type
|
||||
self._current = current
|
||||
|
||||
self._settings = LocalConfig.instance().loadSectionSettings("CustomConsoleCommands", CUSTOM_CONSOLE_COMMANDS_SETTINGS)
|
||||
|
||||
self.uiCommandComboBox.currentIndexChanged.connect(self.commandComboBoxCurrentIndexChangedSlot)
|
||||
self.uiCommandPlainTextEdit.textChanged.connect(self.textChangedSlot)
|
||||
self.uiSavePushButton.clicked.connect(self.savePushButtonClickedSlot)
|
||||
self.uiRemovePushButton.clicked.connect(self.removePushButtonClickedSlot)
|
||||
|
||||
self._refreshList()
|
||||
|
||||
def _refreshList(self):
|
||||
if self._console_type == "telnet":
|
||||
self._consoles = copy.copy(PRECONFIGURED_TELNET_CONSOLE_COMMANDS)
|
||||
self._consoles.update(self._settings[self._console_type])
|
||||
elif self._console_type == "vnc":
|
||||
self._consoles = copy.copy(PRECONFIGURED_VNC_CONSOLE_COMMANDS)
|
||||
self._consoles.update(self._settings[self._console_type])
|
||||
else:
|
||||
self._consoles = copy.copy(PRECONFIGURED_SERIAL_CONSOLE_COMMANDS)
|
||||
self._consoles.update(self._settings[self._console_type])
|
||||
|
||||
self.uiCommandComboBox.clear()
|
||||
self.uiCommandComboBox.addItem("Custom", "")
|
||||
for name, cmd in sorted(self._consoles.items(), key=(lambda item: item[0].lower())):
|
||||
self.uiCommandComboBox.addItem(name, cmd)
|
||||
|
||||
if self._current:
|
||||
self.uiCommandPlainTextEdit.setPlainText(self._current)
|
||||
else:
|
||||
self.uiCommandComboBox.setCurrentIndex(1)
|
||||
|
||||
def removePushButtonClickedSlot(self):
|
||||
"""
|
||||
Remove the custom command from the custom list
|
||||
"""
|
||||
self._settings[self._console_type].pop(self.uiCommandComboBox.currentText())
|
||||
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
|
||||
self._current = None
|
||||
self._refreshList()
|
||||
|
||||
def savePushButtonClickedSlot(self):
|
||||
"""
|
||||
Save a custom command to the list
|
||||
"""
|
||||
name, ok = QtWidgets.QInputDialog.getText(self, "Add a command", "Command name:", QtWidgets.QLineEdit.Normal)
|
||||
command = self.uiCommandPlainTextEdit.toPlainText().strip()
|
||||
if ok and len(command) > 0:
|
||||
if command not in self._consoles.values():
|
||||
self._settings[self._console_type][name] = command
|
||||
self._current = command
|
||||
LocalConfig.instance().saveSectionSettings("CustomConsoleCommands", self._settings)
|
||||
self._refreshList()
|
||||
|
||||
def textChangedSlot(self):
|
||||
index = self.uiCommandComboBox.findData(self.uiCommandPlainTextEdit.toPlainText())
|
||||
if index == -1:
|
||||
index = 0
|
||||
self.uiCommandComboBox.setCurrentIndex(index)
|
||||
|
||||
def commandComboBoxCurrentIndexChangedSlot(self, index):
|
||||
self.uiRemovePushButton.hide()
|
||||
# Ignore custom command
|
||||
if index != 0:
|
||||
self.uiCommandPlainTextEdit.setPlainText(self.uiCommandComboBox.currentData())
|
||||
self.uiSavePushButton.hide()
|
||||
if self.uiCommandComboBox.currentText() in self._settings[self._console_type].keys():
|
||||
self.uiRemovePushButton.show()
|
||||
else:
|
||||
self.uiSavePushButton.show()
|
||||
|
||||
@staticmethod
|
||||
def getCommand(parent, console_type="telnet", current=None):
|
||||
dialog = ConsoleCommandDialog(parent, console_type=console_type, current=current)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
return (True, dialog.uiCommandPlainTextEdit.toPlainText().replace("\n", " "))
|
||||
return (False, None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
main = QtWidgets.QMainWindow()
|
||||
(ok, command) = ConsoleCommandDialog.getCommand(main, console_type="telnet", current=list(PRECONFIGURED_TELNET_CONSOLE_COMMANDS.items())[0][1])
|
||||
print(ok)
|
||||
print(command)
|
||||
|
||||
190
gns3/dialogs/doctor_dialog.py
Normal file
190
gns3/dialogs/doctor_dialog.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import psutil
|
||||
import platform
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.ui.doctor_dialog_ui import Ui_DoctorDialog
|
||||
from gns3.servers import Servers
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3 import version
|
||||
from gns3.modules.vmware import VMware
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DoctorDialog(QtWidgets.QDialog, Ui_DoctorDialog):
|
||||
"""
|
||||
This dialog allow user to detect error in his GNS3 installation.
|
||||
|
||||
If you want to add a test add a method starting by check. The
|
||||
check return a tuple result and a message in case of failure.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, console=False):
|
||||
|
||||
super().__init__(parent)
|
||||
self._console = console
|
||||
self.setupUi(self)
|
||||
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
|
||||
for method in sorted(dir(self)):
|
||||
if method.startswith('check'):
|
||||
self.write(getattr(self, method).__doc__ + "...")
|
||||
(res, msg) = getattr(self, method)()
|
||||
if res == 0:
|
||||
self.write('<span style="color: green"><strong>OK</strong></span>')
|
||||
elif res == 1:
|
||||
self.write('<span style="color: orange"><strong>WARNING</strong> {}</span>'.format(msg))
|
||||
elif res == 2:
|
||||
self.write('<span style="color: red"><strong>ERROR</strong> {}</span>'.format(msg))
|
||||
self.write("<br/>")
|
||||
|
||||
def write(self, text):
|
||||
"""
|
||||
Add text to the text windows
|
||||
"""
|
||||
if self._console:
|
||||
print(text)
|
||||
self.uiDoctorResultTextEdit.setHtml(self.uiDoctorResultTextEdit.toHtml() + text)
|
||||
|
||||
def _okButtonClickedSlot(self):
|
||||
self.accept()
|
||||
|
||||
def checkLocalServerEnabled(self):
|
||||
"""Checking if the local server is enabled"""
|
||||
if Servers.instance().shouldLocalServerAutoStart() is False:
|
||||
return (2, "The local server is disabled. Go to Preferences -> Server -> Local Server and enable the local server.")
|
||||
return (0, None)
|
||||
|
||||
def checkDevVersionOfGNS3(self):
|
||||
"""Checking for stable GNS3 version"""
|
||||
if version.__version_info__[3] != 0:
|
||||
return (1, "You are using a unstable version of GNS3.")
|
||||
return (0, None)
|
||||
|
||||
def checkExperimentalFeaturesEnabled(self):
|
||||
"""Checking if experimental features are not enabled"""
|
||||
if LocalConfig.instance().experimental():
|
||||
return (1, "Experimental features are enabled. Turn them off by going to Preferences -> General -> Miscellaneous.")
|
||||
return (0, None)
|
||||
|
||||
def checkAVGInstalled(self):
|
||||
"""Checking if AVG software is not installed"""
|
||||
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
psinfo = proc.as_dict(["exe"])
|
||||
if psinfo["exe"] and "AVG\\" in psinfo["exe"]:
|
||||
return (2, "AVG has known issues with GNS3, even after you disable it. You must whitelist dynamips.exe in the AVG preferences.")
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
def checkFreeRam(self):
|
||||
"""Checking for amount of free virtual memory"""
|
||||
|
||||
if int(psutil.virtual_memory().available / (1024 * 1024)) < 600:
|
||||
return (2, "You have less than 600MB of available virtual memory, this could prevent nodes to start")
|
||||
return (0, None)
|
||||
|
||||
def checkVmrun(self):
|
||||
"""Checking if vmrun is installed"""
|
||||
vmrun = VMware.instance().findVmrun()
|
||||
if len(vmrun) == 0:
|
||||
return (1, "The vmrun executable could not be found, VMware VMs cannot be used")
|
||||
return (0, None)
|
||||
|
||||
def check64Bit(self):
|
||||
"""Check if processor is 64 bit"""
|
||||
if platform.architecture()[0] != "64bit":
|
||||
return (2, "The architecture {} is not supported.".format(platform.architecture()[0]))
|
||||
return (0, None)
|
||||
|
||||
def checkUbridgePermission(self):
|
||||
"""Check if ubridge has the correct permission"""
|
||||
if not sys.platform.startswith("win") and os.geteuid() == 0:
|
||||
# we are root, so we should have privileged access.
|
||||
return (0, None)
|
||||
|
||||
path = Servers.instance().localServerSettings().get("ubridge_path")
|
||||
if path is None:
|
||||
return (0, None)
|
||||
if not os.path.exists(path):
|
||||
return (2, "Ubridge path {path} doesn't exists".format(path=path))
|
||||
|
||||
request_setuid = False
|
||||
if sys.platform.startswith("linux"):
|
||||
if "security.capability" in os.listxattr(path):
|
||||
caps = os.getxattr(path, "security.capability")
|
||||
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
|
||||
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
|
||||
return(2, "Ubridge require CAP_NET_RAW. Run sudo setcap cap_net_admin,cap_net_raw=ep {path}".format(path=path))
|
||||
else:
|
||||
# capabilities not supported
|
||||
request_setuid = True
|
||||
|
||||
if sys.platform.startswith("darwin") or request_setuid:
|
||||
if os.stat(path).st_uid != 0 or not os.stat(path).st_mode & stat.S_ISUID:
|
||||
return (2, "Ubridge should be setuid. Run sudo chown root {path} and sudo chmod 4755 {path}".format(path=path))
|
||||
return (0, None)
|
||||
|
||||
def checkDynamipsPermission(self):
|
||||
"""Check if dynamips has the correct permission"""
|
||||
if not sys.platform.startswith("win") and os.geteuid() == 0:
|
||||
# we are root, so we should have privileged access.
|
||||
return (0, None)
|
||||
|
||||
path = Servers.instance().localServerSettings().get("dynamips_path")
|
||||
if path is None:
|
||||
return (0, None)
|
||||
if not os.path.exists(path):
|
||||
return (2, "Dynamips path {path} doesn't exists".format(path=path))
|
||||
|
||||
if sys.platform.startswith("linux") and "security.capability" in os.listxattr(path):
|
||||
caps = os.getxattr(path, "security.capability")
|
||||
# test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set
|
||||
if not struct.unpack("<IIIII", caps)[1] & 1 << 13:
|
||||
return(2, "Dynamips require CAP_NET_RAW. Run sudo setcap cap_net_raw,cap_net_admin+eip {path}".format(path=path))
|
||||
return (0, None)
|
||||
|
||||
def checkGNS3InstalledTwice(self):
|
||||
"""Check if gns3 is not installed twice"""
|
||||
|
||||
if not sys.platform.startswith("win"):
|
||||
return (0, None)
|
||||
|
||||
try:
|
||||
if os.path.exists("/usr/local/bin/gns3server") and os.path.exists("/usr/bin/gns3server"):
|
||||
return(2, "GNS3 is installed twice please remove it from /usr/local/bin")
|
||||
except OSError:
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
main = QtWidgets.QMainWindow()
|
||||
dialog = DoctorDialog(main, console=True)
|
||||
#dialog.show()
|
||||
#exit_code = app.exec_()
|
||||
120
gns3/dialogs/export_debug_dialog.py
Normal file
120
gns3/dialogs/export_debug_dialog.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zipfile import ZipFile
|
||||
import platform
|
||||
import psutil
|
||||
import os
|
||||
|
||||
from gns3.version import __version__
|
||||
from gns3.qt import QtWidgets, QtCore
|
||||
from gns3.ui.export_debug_dialog_ui import Ui_ExportDebugDialog
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
|
||||
"""
|
||||
This dialog allow user to export useful information
|
||||
for remote debugging by a GNS3 developers.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, project):
|
||||
|
||||
super().__init__(parent)
|
||||
self._project = project
|
||||
self.setupUi(self)
|
||||
self.uiOkButton.clicked.connect(self._okButtonClickedSlot)
|
||||
|
||||
def _okButtonClickedSlot(self):
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export debug file", None, "Zip file (*.zip)", "Zip file (*.zip)")
|
||||
|
||||
if len(path) == 0:
|
||||
self.reject()
|
||||
return
|
||||
|
||||
log.info("Export debug information to %s", path)
|
||||
|
||||
try:
|
||||
with ZipFile(path, 'w') as zip:
|
||||
zip.writestr("debug.txt", self._getDebugData())
|
||||
dir = LocalConfig.configDirectory()
|
||||
for filename in os.listdir(dir):
|
||||
path = os.path.join(dir, filename)
|
||||
if os.path.isfile(path):
|
||||
zip.write(path, filename)
|
||||
|
||||
dir = self._project.filesDir()
|
||||
if dir:
|
||||
for filename in os.listdir(dir):
|
||||
path = os.path.join(dir, filename)
|
||||
if os.path.isfile(path):
|
||||
zip.write(path, filename)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug information: {}".format(str(e)))
|
||||
self.accept()
|
||||
|
||||
def _getDebugData(self):
|
||||
try:
|
||||
connections = psutil.net_connections()
|
||||
# You need to be root for OSX
|
||||
except psutil.AccessDenied:
|
||||
connections = None
|
||||
|
||||
try:
|
||||
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
|
||||
except UnicodeDecodeError:
|
||||
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
|
||||
|
||||
data = """Version: {version}
|
||||
OS: {os}
|
||||
Python: {python}
|
||||
Qt: {qt}
|
||||
CPU: {cpu}
|
||||
Memory: {memory}
|
||||
|
||||
Networks:
|
||||
{addrs}
|
||||
|
||||
Open connections:
|
||||
{connections}
|
||||
|
||||
Processus:
|
||||
""".format(
|
||||
version=__version__,
|
||||
qt=QtCore.BINDING_VERSION_STR,
|
||||
os=platform.platform(),
|
||||
python=platform.python_version(),
|
||||
memory=psutil.virtual_memory(),
|
||||
cpu=psutil.cpu_times(),
|
||||
connections=connections,
|
||||
addrs="\n".join(addrs)
|
||||
)
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
psinfo = proc.as_dict(attrs=["name", "exe"])
|
||||
data += "* {} {}\n".format(psinfo["name"], psinfo["exe"])
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
return data
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
print(ExportDebugDialog(None)._getDebugData())
|
||||
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ..qt import QtCore, QtGui, QtWebKitWidgets, QtWidgets
|
||||
from ..ui.getting_started_dialog_ui import Ui_GettingStartedDialog
|
||||
from ..utils.get_resource import get_resource
|
||||
from ..local_config import LocalConfig
|
||||
|
||||
|
||||
class GettingStartedDialog(QtWidgets.QDialog, Ui_GettingStartedDialog):
|
||||
|
||||
"""
|
||||
GettingStarted dialog.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.uiWebView.page().mainFrame().setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.adjustSize()
|
||||
self.uiWebView.page().setLinkDelegationPolicy(QtWebKitWidgets.QWebPage.DelegateAllLinks)
|
||||
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
|
||||
self._local_config = LocalConfig.instance()
|
||||
settings = parent.settings()
|
||||
self.uiCheckBox.setChecked(settings["hide_getting_started_dialog"])
|
||||
getting_started = get_resource(os.path.join("static", "getting_started.html"))
|
||||
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
|
||||
# do not show the page on Windows 32-bit (crash when no Internet connection)
|
||||
self.uiWebView.load(QtCore.QUrl.fromLocalFile(getting_started))
|
||||
else:
|
||||
self.uiCheckBox.setChecked(True)
|
||||
self.accept()
|
||||
|
||||
def showit(self):
|
||||
"""
|
||||
Either this dialog should be automatically showed at startup.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return not self.uiCheckBox.isChecked()
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
This dialog is closed.
|
||||
|
||||
:param result: ignored
|
||||
"""
|
||||
|
||||
settings = self.parentWidget().settings()
|
||||
settings["hide_getting_started_dialog"] = self.uiCheckBox.isChecked()
|
||||
self.parentWidget().setSettings(settings)
|
||||
super().done(result)
|
||||
|
||||
def _urlClickedSlot(self, url):
|
||||
"""
|
||||
Opens a clicked URL using user's default browser.
|
||||
|
||||
:param url: URL to open
|
||||
"""
|
||||
|
||||
if QtGui.QDesktopServices.openUrl(url) is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))
|
||||
@@ -19,9 +19,6 @@
|
||||
Dialog to configure and update node settings using widget pages.
|
||||
"""
|
||||
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.progress import Progress
|
||||
|
||||
from ..qt import QtCore, QtGui, QtWidgets
|
||||
from ..ui.node_properties_dialog_ui import Ui_NodePropertiesDialog
|
||||
|
||||
@@ -67,6 +64,10 @@ class NodePropertiesDialog(QtWidgets.QDialog, Ui_NodePropertiesDialog):
|
||||
for node_item in self._node_items:
|
||||
if not node_item.node().initialized():
|
||||
continue
|
||||
|
||||
# If something of one of the displayed nodes we reload everything
|
||||
node_item.node().updated_signal.connect(self.resetSettings)
|
||||
|
||||
group_name = " {} group".format(str(node_item.node()))
|
||||
parent = group_name
|
||||
if parent not in self._parent_items:
|
||||
|
||||
@@ -38,8 +38,21 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
def __init__(self, parent):
|
||||
|
||||
super().__init__(parent)
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
# We adapt the max size to the screen resolution
|
||||
# We need to manually do that otherwise on small screen the windows
|
||||
# could be bigger than the screen instead of displaying scrollbars
|
||||
height = QtWidgets.QDesktopWidget().screenGeometry().height() - 100
|
||||
width = QtWidgets.QDesktopWidget().screenGeometry().width() - 100
|
||||
|
||||
self.setMaximumSize(QtCore.QSize(width, height))
|
||||
if width > 900 and self.width() < 900:
|
||||
self.resize(900, self.height())
|
||||
if height > 768 and self.height() < 768:
|
||||
self.resize(self.width(), 768)
|
||||
|
||||
self.uiTreeWidget.currentItemChanged.connect(self._showPreferencesPageSlot)
|
||||
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
|
||||
self._applyButton.clicked.connect(self._applyPreferences)
|
||||
@@ -54,6 +67,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
# Something has change?
|
||||
self._modified = False
|
||||
|
||||
|
||||
|
||||
def _loadPreferencePages(self):
|
||||
"""
|
||||
Loads all preference pages (built-ins and from modules).
|
||||
@@ -170,15 +185,14 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
|
||||
|
||||
if self._modified:
|
||||
reply = QtWidgets.QMessageBox.warning(self,
|
||||
"Preferences",
|
||||
"You have unsaved preferences.\n\nContinue without saving?",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No)
|
||||
"Preferences",
|
||||
"You have unsaved preferences.\n\nContinue without saving?",
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.No:
|
||||
return
|
||||
QtWidgets.QDialog.reject(self)
|
||||
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Saves the preferences and closes this dialog.
|
||||
|
||||
@@ -19,7 +19,7 @@ import sys
|
||||
import os
|
||||
import psutil
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets, QtGui
|
||||
from gns3.servers import Servers
|
||||
from ..gns3_vm import GNS3VM
|
||||
from ..dialogs.preferences_dialog import PreferencesDialog
|
||||
@@ -27,6 +27,7 @@ from ..ui.setup_wizard_ui import Ui_SetupWizard
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..utils.wait_for_vm_worker import WaitForVMWorker
|
||||
from ..utils.wait_for_connection_worker import WaitForConnectionWorker
|
||||
from ..version import __version__
|
||||
|
||||
|
||||
class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
@@ -46,9 +47,11 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
|
||||
|
||||
self._server = Servers.instance().localServer()
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('')
|
||||
self.uiRefreshPushButton.clicked.connect(self._refreshVMListSlot)
|
||||
self.uiVmwareRadioButton.clicked.connect(self._listVMwareVMsSlot)
|
||||
self.uiVirtualBoxRadioButton.clicked.connect(self._listVirtualBoxVMsSlot)
|
||||
self.uiVMwareBannerButton.clicked.connect(self._VMwareBannerButtonClickedSlot)
|
||||
settings = parent.settings()
|
||||
self.uiShowCheckBox.setChecked(settings["hide_setup_wizard"])
|
||||
|
||||
@@ -58,16 +61,30 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
self.uiVmwareRadioButton.setChecked(False)
|
||||
self.uiVirtualBoxRadioButton.setChecked(False)
|
||||
|
||||
if sys.platform.startswith("darwin"):
|
||||
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_fusion_banner.jpg"))
|
||||
else:
|
||||
self.uiVMwareBannerButton.setIcon(QtGui.QIcon(":/images/vmware_workstation_banner.jpg"))
|
||||
|
||||
def _VMwareBannerButtonClickedSlot(self):
|
||||
if sys.platform.startswith("darwin"):
|
||||
url = "http://send.onenetworkdirect.net/z/616461/CD225091/"
|
||||
else:
|
||||
url = "http://send.onenetworkdirect.net/z/616460/CD225091/"
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
|
||||
|
||||
def _listVMwareVMsSlot(self):
|
||||
"""
|
||||
Slot to refresh the VMware VMs list.
|
||||
"""
|
||||
|
||||
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__)
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
|
||||
self.uiVirtualBoxRadioButton.setChecked(False)
|
||||
from gns3.modules import VMware
|
||||
settings = VMware.instance().settings()
|
||||
if not os.path.exists(settings["vmrun_path"]):
|
||||
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API is probably not installed")
|
||||
QtWidgets.QMessageBox.critical(self, "VMware", "VMware vmrun tool could not be found, VMware or the VIX API (required for VMware player) is probably not installed. You can download it from https://www.vmware.com/support/developer/vix-api/")
|
||||
return
|
||||
self._refreshVMListSlot()
|
||||
|
||||
@@ -76,6 +93,8 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
Slot to refresh the VirtualBox VMs list.
|
||||
"""
|
||||
|
||||
download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__)
|
||||
self.uiGNS3VMDownloadLinkUrlLabel.setText('If you don\'t have the GNS3 Virtual Machine you can <a href="{download_url}">download it here</a>.<br>And import the VM in the virtualization software and hit refresh.'.format(download_url=download_url))
|
||||
self.uiVmwareRadioButton.setChecked(False)
|
||||
from gns3.modules import VirtualBox
|
||||
settings = VirtualBox.instance().settings()
|
||||
@@ -160,7 +179,7 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
# start the GNS3 VM
|
||||
servers.initVMServer()
|
||||
worker = WaitForVMWorker()
|
||||
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self)
|
||||
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self, delay=5)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_():
|
||||
previous_local_server_ip = servers.localServer().host()
|
||||
@@ -175,6 +194,10 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
else:
|
||||
if not self.uiVmwareRadioButton.isChecked() and not self.uiVirtualBoxRadioButton.isChecked():
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select VMware or VirtualBox")
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "GNS3 VM", "Please select a VM. If no VM is listed, check if the GNS3 VM is correctly imported and press refresh.")
|
||||
return False
|
||||
elif self.currentPage() == self.uiAddVMsWizardPage:
|
||||
|
||||
@@ -216,9 +239,6 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
|
||||
Refresh the list of VM available in VMware or VirtualBox.
|
||||
"""
|
||||
|
||||
if not Servers.instance().localServerIsRunning():
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "{}".format("Local server is not running"))
|
||||
return
|
||||
server = Servers.instance().localServer()
|
||||
if self.uiVmwareRadioButton.isChecked():
|
||||
server.get("/vmware/vms", self._getVMsFromServerCallback)
|
||||
|
||||
@@ -21,10 +21,10 @@ Dialog to change node symbols.
|
||||
|
||||
import os
|
||||
|
||||
from ..qt import QtSvg, QtCore, QtGui, QtWidgets
|
||||
from ..items.svg_node_item import SvgNodeItem
|
||||
from ..items.pixmap_node_item import PixmapNodeItem
|
||||
from ..qt import QtCore, QtGui, QtWidgets
|
||||
from ..qt.qimage_svg_renderer import QImageSvgRenderer
|
||||
from ..ui.symbol_selection_dialog_ui import Ui_SymbolSelectionDialog
|
||||
from ..servers import Servers
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -49,56 +49,71 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
self.uiSymbolToolButton.clicked.connect(self._symbolBrowserSlot)
|
||||
self.uiCustomSymbolRadioButton.toggled.connect(self._customSymbolToggledSlot)
|
||||
self.uiBuiltInSymbolRadioButton.toggled.connect(self._builtInSymbolToggledSlot)
|
||||
self.uiSearchLineEdit.textChanged.connect(self._searchTextChangedSlot)
|
||||
self.uiBuiltinSymbolOnlyCheckBox.toggled.connect(self._builtinSymbolOnlyToggledSlot)
|
||||
self._symbols_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation)
|
||||
self._symbols_path = Servers.instance().localServerSettings()["symbols_path"]
|
||||
|
||||
selected_symbol = symbol
|
||||
if not self._items:
|
||||
self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply).hide()
|
||||
else:
|
||||
first_item = items[0]
|
||||
if isinstance(first_item, SvgNodeItem):
|
||||
custom_symbol = first_item.renderer().objectName()
|
||||
if not custom_symbol:
|
||||
symbol_name = first_item.node().defaultSymbol()
|
||||
else:
|
||||
symbol_name = custom_symbol
|
||||
selected_symbol = symbol_name
|
||||
elif isinstance(first_item, PixmapNodeItem):
|
||||
selected_symbol = first_item.pixmapSymbolPath()
|
||||
|
||||
custom_symbol = True
|
||||
self.uiBuiltInSymbolRadioButton.setChecked(True)
|
||||
self.uiSymbolListWidget.setFocus()
|
||||
self.uiSymbolListWidget.setIconSize(QtCore.QSize(64, 64))
|
||||
symbol_resources = QtCore.QResource(":/symbols")
|
||||
for symbol in symbol_resources.children():
|
||||
if symbol.endswith(".svg"):
|
||||
self._symbol_items = []
|
||||
symbols = symbol_resources.children()
|
||||
|
||||
try:
|
||||
for file in os.listdir(self._symbols_path):
|
||||
symbols.append(file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
symbols.sort()
|
||||
for symbol in symbols:
|
||||
if symbol.endswith(".svg") or symbol.endswith(".png"):
|
||||
name = os.path.splitext(symbol)[0]
|
||||
item = QtWidgets.QListWidgetItem(self.uiSymbolListWidget)
|
||||
self._symbol_items.append(item)
|
||||
item.setText(name)
|
||||
resource_path = ":/symbols/" + symbol
|
||||
svg_renderer = QtSvg.QSvgRenderer(resource_path)
|
||||
if resource_path == selected_symbol:
|
||||
# this is a built-in symbol
|
||||
custom_symbol = False
|
||||
self.uiSymbolListWidget.setCurrentItem(item)
|
||||
|
||||
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
|
||||
# Set the ARGB to 0 to prevent rendering artifacts
|
||||
image.fill(0x00000000)
|
||||
|
||||
if os.path.exists(os.path.join(self._symbols_path, symbol)):
|
||||
svg_renderer = QImageSvgRenderer(os.path.join(self._symbols_path, symbol))
|
||||
else:
|
||||
resource_path = ":/symbols/" + symbol
|
||||
svg_renderer = QImageSvgRenderer(resource_path)
|
||||
svg_renderer.render(QtGui.QPainter(image))
|
||||
|
||||
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
|
||||
item.setIcon(icon)
|
||||
|
||||
if custom_symbol:
|
||||
# this is a custom symbol
|
||||
self.uiCustomSymbolRadioButton.setChecked(True)
|
||||
self.uiSymbolLineEdit.setText(selected_symbol)
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(selected_symbol))
|
||||
self.uiBuiltInGroupBox.setEnabled(False)
|
||||
self.uiBuiltInGroupBox.hide()
|
||||
|
||||
self.adjustSize()
|
||||
|
||||
def _builtinSymbolOnlyToggledSlot(self, checked):
|
||||
self._filter()
|
||||
|
||||
def _searchTextChangedSlot(self, text):
|
||||
self._filter()
|
||||
|
||||
def _filter(self):
|
||||
"""
|
||||
Hide element not matching the search
|
||||
"""
|
||||
text = self.uiSearchLineEdit.text()
|
||||
for item in self._symbol_items:
|
||||
if self.uiBuiltinSymbolOnlyCheckBox.isChecked() and not QtCore.QResource(":/symbols/{}.svg".format(item.text())).isValid():
|
||||
item.setHidden(True)
|
||||
else:
|
||||
if len(text.strip()) == 0 or text.strip().lower() in item.text().lower():
|
||||
item.setHidden(False)
|
||||
else:
|
||||
item.setHidden(True)
|
||||
|
||||
def _customSymbolToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the custom symbol radio button is toggled.
|
||||
@@ -132,56 +147,42 @@ class SymbolSelectionDialog(QtWidgets.QDialog, Ui_SymbolSelectionDialog):
|
||||
Applies the selected symbol to the items.
|
||||
"""
|
||||
|
||||
if self.uiSymbolListWidget.isEnabled():
|
||||
current = self.uiSymbolListWidget.currentItem()
|
||||
if current:
|
||||
name = current.text()
|
||||
path = ":/symbols/{}.svg".format(name)
|
||||
renderer = QtSvg.QSvgRenderer(path)
|
||||
renderer.setObjectName(path)
|
||||
for item in self._items:
|
||||
if isinstance(item, SvgNodeItem):
|
||||
item.setSharedRenderer(renderer)
|
||||
else:
|
||||
pixmap = QtGui.QPixmap(path)
|
||||
if not pixmap.isNull():
|
||||
item.setPixmap(pixmap)
|
||||
item.setPixmapSymbolPath(path)
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Built-in SVG symbol", "Built-in SVG symbol cannot be applied on Pixmap node item")
|
||||
return False
|
||||
else:
|
||||
symbol_path = self.uiSymbolLineEdit.text()
|
||||
pixmap = QtGui.QPixmap(symbol_path)
|
||||
if not pixmap.isNull():
|
||||
for item in self._items:
|
||||
if isinstance(item, PixmapNodeItem):
|
||||
item.setPixmap(pixmap)
|
||||
item.setPixmapSymbolPath(symbol_path)
|
||||
else:
|
||||
renderer = QtSvg.QSvgRenderer(symbol_path)
|
||||
renderer.setObjectName(symbol_path)
|
||||
if renderer.isValid():
|
||||
item.setSharedRenderer(renderer)
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Custom pixmap symbol", "Custom pixmap symbol which is not SVG format cannot be applied on SVG node item")
|
||||
return False
|
||||
symbol_path = self.getSymbol()
|
||||
|
||||
pixmap = QtGui.QPixmap(symbol_path)
|
||||
if not pixmap.isNull():
|
||||
for item in self._items:
|
||||
renderer = QImageSvgRenderer(symbol_path)
|
||||
renderer.setObjectName(symbol_path)
|
||||
if renderer.isValid():
|
||||
item.setSharedRenderer(renderer)
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Custom pixmap symbol", "Invalid image")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getSymbol(self):
|
||||
|
||||
if self.uiSymbolListWidget.isEnabled():
|
||||
current = self.uiSymbolListWidget.currentItem()
|
||||
name = current.text()
|
||||
normal_symbol = ":/symbols/{}.svg".format(name)
|
||||
if current:
|
||||
name = current.text()
|
||||
if QtCore.QResource(":/symbols/{}.svg".format(name)).isValid():
|
||||
return ":/symbols/{}.svg".format(name)
|
||||
else:
|
||||
symbol_path = os.path.join(self._symbols_path, "{}.svg".format(name))
|
||||
if not os.path.exists(symbol_path):
|
||||
symbol_path = os.path.join(self._symbols_path, "{}.png".format(name))
|
||||
return symbol_path
|
||||
else:
|
||||
normal_symbol = self.uiSymbolLineEdit.text()
|
||||
return normal_symbol
|
||||
return self.uiSymbolLineEdit.text()
|
||||
return None
|
||||
|
||||
def _symbolBrowserSlot(self):
|
||||
|
||||
# supported image file formats
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm *.gif);;All files (*.*)"
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._symbols_dir, file_formats)
|
||||
if not path:
|
||||
return
|
||||
|
||||
@@ -52,6 +52,10 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
|
||||
self.uiPlainTextEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
|
||||
|
||||
if len(self._items) == 1:
|
||||
self.uiApplyColorToAllItemsCheckBox.setChecked(True)
|
||||
self.uiApplyColorToAllItemsCheckBox.hide()
|
||||
self.uiApplyRotationToAllItemsCheckBox.setChecked(True)
|
||||
self.uiApplyRotationToAllItemsCheckBox.hide()
|
||||
self.uiApplyTextToAllItemsCheckBox.setChecked(True)
|
||||
self.uiApplyTextToAllItemsCheckBox.hide()
|
||||
|
||||
@@ -90,9 +94,11 @@ class TextEditorDialog(QtWidgets.QDialog, Ui_TextEditorDialog):
|
||||
"""
|
||||
|
||||
for item in self._items:
|
||||
item.setDefaultTextColor(self._color)
|
||||
item.setFont(self.uiPlainTextEdit.font())
|
||||
item.setRotation(self.uiRotationSpinBox.value())
|
||||
if self.uiApplyColorToAllItemsCheckBox.isChecked():
|
||||
item.setDefaultTextColor(self._color)
|
||||
if self.uiApplyRotationToAllItemsCheckBox.isChecked():
|
||||
item.setRotation(self.uiRotationSpinBox.value())
|
||||
if item.editable() and self.uiApplyTextToAllItemsCheckBox.isChecked():
|
||||
item.setPlainText(self.uiPlainTextEdit.toPlainText())
|
||||
|
||||
|
||||
@@ -112,8 +112,8 @@ class VMWithImagesWizard(VMWizard):
|
||||
User select a different image in the combo box
|
||||
"""
|
||||
item = combo_box.itemData(index)
|
||||
if item and item["filename"]:
|
||||
line_edit.setText(item["filename"])
|
||||
if item and item["path"]:
|
||||
line_edit.setText(item["path"])
|
||||
else:
|
||||
line_edit.setText("")
|
||||
|
||||
@@ -130,7 +130,7 @@ class VMWithImagesWizard(VMWizard):
|
||||
browser.hide()
|
||||
line_edit.hide()
|
||||
if combo_box.count() > 0:
|
||||
line_edit.setText(combo_box.itemData(combo_box.currentIndex())["filename"])
|
||||
line_edit.setText(combo_box.itemData(combo_box.currentIndex())["path"])
|
||||
else:
|
||||
combo_box.hide()
|
||||
line_edit.setText("")
|
||||
@@ -160,8 +160,35 @@ class VMWithImagesWizard(VMWizard):
|
||||
QtWidgets.QMessageBox.critical(self, "Images", "Error while getting the VMs: {}".format(result["message"]))
|
||||
return
|
||||
|
||||
for combo_box in self._images_combo_boxes:
|
||||
combo_box.clear()
|
||||
for vm in result:
|
||||
combo_box.addItem(vm["filename"], vm)
|
||||
# Wizard is closed
|
||||
if self.currentPage() is None:
|
||||
return
|
||||
|
||||
if len(result) == 0:
|
||||
for radio_button in self._radio_existing_images_buttons:
|
||||
if radio_button.isChecked() and self._widgetOnCurrentPage(radio_button):
|
||||
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
|
||||
if button != radio_button:
|
||||
button.setChecked(True)
|
||||
button.hide()
|
||||
else:
|
||||
for radio_button in self._radio_existing_images_buttons:
|
||||
if self._widgetOnCurrentPage(radio_button):
|
||||
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
|
||||
if button == radio_button:
|
||||
button.setChecked(True)
|
||||
button.show()
|
||||
|
||||
for combo_box in self._images_combo_boxes:
|
||||
if self._widgetOnCurrentPage(combo_box):
|
||||
combo_box.clear()
|
||||
for vm in result:
|
||||
combo_box.addItem(vm["path"], vm)
|
||||
|
||||
|
||||
def _widgetOnCurrentPage(self, widget):
|
||||
"""
|
||||
:returns Boolean True if widget is current active Wizard page
|
||||
"""
|
||||
return self.currentPage().findChild(widget.__class__, widget.objectName()) is not None
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class VMWizard(QtWidgets.QWizard):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.setModal(True)
|
||||
|
||||
self._devices = devices
|
||||
self._use_local_server = use_local_server
|
||||
|
||||
@@ -51,7 +53,6 @@ class VMWizard(QtWidgets.QWizard):
|
||||
if hasattr(self, "uiLoadBalanceCheckBox"):
|
||||
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
|
||||
|
||||
|
||||
# By default we use the local server
|
||||
self._server = Servers.instance().localServer()
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
@@ -61,7 +62,6 @@ class VMWizard(QtWidgets.QWizard):
|
||||
# skip the server page if we use the local server
|
||||
self.setStartId(1)
|
||||
|
||||
|
||||
def _vmToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the VM radio button is toggled.
|
||||
@@ -107,22 +107,21 @@ class VMWizard(QtWidgets.QWizard):
|
||||
|
||||
if self.page(page_id) == self.uiServerWizardPage:
|
||||
self.uiRemoteServersComboBox.clear()
|
||||
for server in Servers.instance().remoteServers().values():
|
||||
self.uiRemoteServersComboBox.addItem(server.url(), server)
|
||||
|
||||
if len(Servers.instance().remoteServers().values()) == 0:
|
||||
self.uiRemoteRadioButton.setEnabled(False)
|
||||
else:
|
||||
for server in Servers.instance().remoteServers().values():
|
||||
self.uiRemoteServersComboBox.addItem(server.url(), server)
|
||||
|
||||
if hasattr(self, "uiVMRadioButton") and not GNS3VM.instance().isRunning():
|
||||
self.uiVMRadioButton.setEnabled(False)
|
||||
if hasattr(self, "uiVMRadioButton") and GNS3VM.instance().isRunning():
|
||||
|
||||
self.uiVMRadioButton.setChecked(True)
|
||||
elif self._use_local_server and self.uiLocalRadioButton.isChecked():
|
||||
self.uiLocalRadioButton.setChecked(True)
|
||||
else:
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
else:
|
||||
if self.uiLocalRadioButton.isChecked():
|
||||
servers = Servers.instance()
|
||||
if servers.localServerAutoStart() and not servers.localServerIsRunning():
|
||||
QtWidgets.QMessageBox.critical(self, "Wizard", "Local server is not running")
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
|
||||
@@ -41,6 +41,8 @@ class GNS3VM:
|
||||
def __init__(self):
|
||||
|
||||
self._is_running = False
|
||||
# The current running vboxmanage and vmrun process
|
||||
self._running_process = None
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
@@ -60,8 +62,38 @@ class GNS3VM:
|
||||
|
||||
Servers.instance().setVMsettings(settings)
|
||||
|
||||
@staticmethod
|
||||
def execute_vmrun(subcommand, args, timeout=60):
|
||||
def killRunningProcess(self):
|
||||
"""
|
||||
Kill the VBoxManage or vmrun process if running
|
||||
"""
|
||||
if self._running_process is not None:
|
||||
self._running_process.kill()
|
||||
self._running_process.wait()
|
||||
self._running_process = None
|
||||
|
||||
def _process_check_output(self, command, timeout=None):
|
||||
# Original code from Python's subprocess.check_output
|
||||
# https://github.com/python/cpython/blob/3.4/Lib/subprocess.py
|
||||
with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
|
||||
self._running_process = process
|
||||
try:
|
||||
output, unused_err = process.communicate(None, timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
output, unused_err = process.communicate()
|
||||
self._running_process = None
|
||||
raise subprocess.TimeoutExpired(process.args, timeout, output=output)
|
||||
except:
|
||||
self.killRunningProcess()
|
||||
raise
|
||||
retcode = process.poll()
|
||||
if retcode:
|
||||
self._running_process = None
|
||||
raise subprocess.CalledProcessError(retcode, process.args, output=output)
|
||||
self._running_process = None
|
||||
return output.decode("utf-8", errors="ignore").strip()
|
||||
|
||||
def execute_vmrun(self, subcommand, args, timeout=60):
|
||||
|
||||
from gns3.modules.vmware import VMware
|
||||
vmware_settings = VMware.instance().settings()
|
||||
@@ -73,11 +105,10 @@ class GNS3VM:
|
||||
command = [vmrun_path, "-T", host_type, subcommand]
|
||||
command.extend(args)
|
||||
log.debug("Executing vmrun with command: {}".format(command))
|
||||
output = subprocess.check_output(command, timeout=timeout)
|
||||
return output.decode("utf-8", errors="ignore").strip()
|
||||
|
||||
@staticmethod
|
||||
def execute_vboxmanage(subcommand, args, timeout=60):
|
||||
return self._process_check_output(command, timeout=timeout)
|
||||
|
||||
def execute_vboxmanage(self, subcommand, args, timeout=60):
|
||||
|
||||
from gns3.modules.virtualbox import VirtualBox
|
||||
virtualbox_settings = VirtualBox.instance().settings()
|
||||
@@ -85,8 +116,7 @@ class GNS3VM:
|
||||
command = [vboxmanage_path, "--nologo", subcommand]
|
||||
command.extend(args)
|
||||
log.debug("Executing VBoxManage with command: {}".format(command))
|
||||
output = subprocess.check_output(command, timeout=timeout)
|
||||
return output.decode("utf-8", errors="ignore").strip()
|
||||
return self._process_check_output(command, timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
def parse_vmx_file(path):
|
||||
@@ -254,6 +284,9 @@ class GNS3VM:
|
||||
if self._is_running and (vm_settings["auto_stop"] or force):
|
||||
try:
|
||||
if vm_settings["virtualization"] == "VMware":
|
||||
if vm_settings["vmx_path"] is None:
|
||||
log.error("No vm path configured, can't stop the VM")
|
||||
return
|
||||
self.execute_vmrun("stop", [vm_settings["vmx_path"], "soft"])
|
||||
elif vm_settings["virtualization"] == "VirtualBox":
|
||||
self.execute_vboxmanage("controlvm", [vm_settings["vmname"], "acpipowerbutton"], timeout=3)
|
||||
|
||||
@@ -22,13 +22,11 @@ Graphical view on the scene where items are drawn.
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import functools
|
||||
|
||||
from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets
|
||||
from .qt import QtCore, QtGui, QtSvg, QtNetwork, QtWidgets, qpartial
|
||||
from .servers import Servers
|
||||
from .items.node_item import NodeItem
|
||||
from .items.svg_node_item import SvgNodeItem
|
||||
from .items.pixmap_node_item import PixmapNodeItem
|
||||
from .dialogs.node_properties_dialog import NodePropertiesDialog
|
||||
from .link import Link
|
||||
from .node import Node
|
||||
@@ -42,8 +40,11 @@ from .dialogs.style_editor_dialog import StyleEditorDialog
|
||||
from .dialogs.text_editor_dialog import TextEditorDialog
|
||||
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from .dialogs.idlepc_dialog import IdlePCDialog
|
||||
from .dialogs.console_command_dialog import ConsoleCommandDialog
|
||||
from .local_config import LocalConfig
|
||||
from .progress import Progress
|
||||
from .utils.server_select import server_select
|
||||
from .utils.normalize_filename import normalize_filename
|
||||
|
||||
# link items
|
||||
from .items.link_item import LinkItem
|
||||
@@ -293,36 +294,36 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self.deleteLinkSlot(link_id)
|
||||
return
|
||||
|
||||
# ugly multi-link management
|
||||
# FIXME: taken from old GNS3 and has a bug!
|
||||
multi = 0
|
||||
d1 = 0
|
||||
d2 = 1
|
||||
link_items = source_item.links()
|
||||
for link_item in link_items:
|
||||
if link_item.destinationItem().node().id() == destination_item.node().id():
|
||||
d1 += 1
|
||||
if link_item.sourceItem().node().id() == destination_item.node().id():
|
||||
d2 += 1
|
||||
|
||||
if len(link_items) > 0:
|
||||
if d2 - d1 == 2:
|
||||
source_port, destination_port = destination_port, source_port
|
||||
source_item, destination_item = destination_item, source_item
|
||||
multi = d1 + 1
|
||||
elif d1 >= d2:
|
||||
source_port, destination_port = destination_port, source_port
|
||||
source_item, destination_item = destination_item, source_item
|
||||
multi = d2
|
||||
else:
|
||||
multi = d1
|
||||
|
||||
# MAX 7 links on the scene between 2 nodes
|
||||
if multi > 3:
|
||||
multi = 0
|
||||
# Multi-link management
|
||||
#
|
||||
# multi is the offset of the link
|
||||
# +------+ multi = -1 Link 2 +-------+
|
||||
# | +-----------------------------+ |
|
||||
# | R1 | | R2 |
|
||||
# | | multi = 0 Link 1 | |
|
||||
# | +-----------------------------+ |
|
||||
# | | multi = 1 Link 3 | |
|
||||
# +------+-----------------------------+-------+
|
||||
|
||||
if source_item == destination_item:
|
||||
multi = 0
|
||||
else:
|
||||
multi = 0
|
||||
link_items = source_item.links()
|
||||
for link_item in link_items:
|
||||
if link_item.destinationItem().node().id() == destination_item.node().id():
|
||||
multi += 1
|
||||
if link_item.sourceItem().node().id() == destination_item.node().id():
|
||||
multi += 1
|
||||
|
||||
# MAX 7 links on the scene between 2 nodes
|
||||
if multi > 7:
|
||||
multi = 0
|
||||
# Pair item represent the bottom links
|
||||
elif multi % 2 == 0:
|
||||
multi = multi // 2
|
||||
else:
|
||||
multi = -multi // 2
|
||||
|
||||
if link.sourcePort().linkType() == "Serial" or (source_port.isStub() and link.destinationPort().linkType() == "Serial"):
|
||||
link_item = SerialLinkItem(source_item, source_port, destination_item, destination_port, link, multilink=multi)
|
||||
@@ -396,6 +397,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
elif source_port.linkType() != destination_port.linkType():
|
||||
QtWidgets.QMessageBox.critical(self, "Connection", "Cannot connect this port!")
|
||||
return
|
||||
elif source_port.defaultNio() != destination_port.defaultNio():
|
||||
QtWidgets.QMessageBox.critical(self, "Connection", "These nodes cannot be connected together ({} != {})".format(source_port.defaultNio().__name__,
|
||||
destination_port.defaultNio().__name__))
|
||||
return
|
||||
|
||||
if source_item.node().server().protocol() != destination_item.node().server().protocol():
|
||||
QtWidgets.QMessageBox.critical(self, "Connection", "Sorry, you cannot connect a device running on an insecure server to a device running on a secure server.")
|
||||
@@ -548,7 +553,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
"""
|
||||
|
||||
factor = self.transform().scale(scale_factor, scale_factor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
|
||||
if (factor < 0.10 or factor > 10):
|
||||
if factor < 0.10 or factor > 10:
|
||||
return
|
||||
self.scale(scale_factor, scale_factor)
|
||||
|
||||
@@ -626,8 +631,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
item.parentItem().setSelected(True)
|
||||
self.changeHostnameActionSlot()
|
||||
return
|
||||
else:
|
||||
super().mouseDoubleClickEvent(event)
|
||||
super().mouseDoubleClickEvent(event)
|
||||
|
||||
def configureSlot(self, items=None):
|
||||
"""
|
||||
@@ -635,14 +639,15 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
"""
|
||||
|
||||
if not items:
|
||||
items = self.scene().selectedItems()
|
||||
items = []
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and item.node().initialized():
|
||||
items.append(item)
|
||||
with Progress.instance().context(min_duration=0):
|
||||
node_properties = NodePropertiesDialog(items, self._main_window)
|
||||
node_properties.setModal(True)
|
||||
node_properties.show()
|
||||
node_properties.exec_()
|
||||
for item in items:
|
||||
item.setSelected(False)
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
"""
|
||||
@@ -696,7 +701,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
return
|
||||
path = event.mimeData().urls()[0].toLocalFile()
|
||||
if os.path.isfile(path) and self._main_window.checkForUnsavedChanges():
|
||||
self._main_window.loadProject(path)
|
||||
self._main_window.loadPath(path)
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
@@ -755,6 +760,12 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
console_action.triggered.connect(self.consoleActionSlot)
|
||||
menu.addAction(console_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "console"), items)):
|
||||
console_edit_action = QtWidgets.QAction("Custom console", menu)
|
||||
console_edit_action.setIcon(QtGui.QIcon(':/icons/console_edit.svg'))
|
||||
console_edit_action.triggered.connect(self.customConsoleActionSlot)
|
||||
menu.addAction(console_edit_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
|
||||
aux_console_action = QtWidgets.QAction("Auxiliary console", menu)
|
||||
aux_console_action.setIcon(QtGui.QIcon(':/icons/aux-console.svg'))
|
||||
@@ -839,6 +850,13 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
style_action.triggered.connect(self.styleActionSlot)
|
||||
menu.addAction(style_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "commandLine"), items)):
|
||||
# Action: Get command line
|
||||
show_in_file_manager_action = QtWidgets.QAction("Command line", menu)
|
||||
show_in_file_manager_action.setIcon(QtGui.QIcon(':/icons/console.svg'))
|
||||
show_in_file_manager_action.triggered.connect(self.getCommandLineSlot)
|
||||
menu.addAction(show_in_file_manager_action)
|
||||
|
||||
# item must have no parent
|
||||
if True in list(map(lambda item: item.parentItem() is None, items)):
|
||||
|
||||
@@ -968,7 +986,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
break
|
||||
|
||||
if os.path.exists(vm_dir):
|
||||
log.debug("Open %s in file manage" )
|
||||
log.debug("Open %s in file manage")
|
||||
if QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(vm_dir)) is False:
|
||||
QtWidgets.QMessageBox.critical(self, "Show in file manager", "Failed to open {}".format(vm_dir))
|
||||
break
|
||||
@@ -994,38 +1012,11 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
# returns True to ignore this node.
|
||||
return True
|
||||
|
||||
if hasattr(node, "serialConsole") and node.serialConsole():
|
||||
try:
|
||||
from .serial_console import serialConsole
|
||||
serialConsole(node.name(), node.serialPipe())
|
||||
except (OSError, ValueError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
|
||||
return False
|
||||
else:
|
||||
name = node.name()
|
||||
if aux:
|
||||
console_port = node.auxConsole()
|
||||
if console_port is None:
|
||||
QtWidgets.QMessageBox.critical(self, "Console", "AUX console port not allocated for {}".format(name))
|
||||
return False
|
||||
else:
|
||||
console_port = node.console()
|
||||
|
||||
console_type = "telnet"
|
||||
if "console_type" in node.settings():
|
||||
console_type = node.settings()["console_type"]
|
||||
|
||||
try:
|
||||
from .telnet_console import nodeTelnetConsole
|
||||
from .vnc_console import vncConsole
|
||||
|
||||
if console_type == "telnet":
|
||||
nodeTelnetConsole(name, node.server(), console_port)
|
||||
elif console_type == "vnc":
|
||||
vncConsole(node.server().host(), console_port)
|
||||
except (OSError, ValueError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
|
||||
return False
|
||||
try:
|
||||
node.openConsole(aux=aux)
|
||||
except (OSError, ValueError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Console", "Cannot start console application: {}".format(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def consoleFromItems(self, items):
|
||||
@@ -1045,7 +1036,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
node = nodes[name]
|
||||
callback = functools.partial(self.consoleToNode, node)
|
||||
callback = qpartial(self.consoleToNode, node)
|
||||
self._main_window.run_later(counter, callback)
|
||||
counter += delay
|
||||
|
||||
@@ -1057,6 +1048,25 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
self.consoleFromItems(self.scene().selectedItems())
|
||||
|
||||
def customConsoleActionSlot(self):
|
||||
"""
|
||||
Allow user to use a custom console for this VM
|
||||
"""
|
||||
|
||||
current_cmd = None
|
||||
console_type = "telnet"
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
|
||||
current_cmd = item.node().consoleCommand()
|
||||
console_type = item.node().consoleType()
|
||||
|
||||
(ok, cmd) = ConsoleCommandDialog.getCommand(self, console_type=console_type, current=current_cmd)
|
||||
if ok:
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
|
||||
node = item.node()
|
||||
node.openConsole(command=cmd)
|
||||
|
||||
def auxConsoleFromItems(self, items):
|
||||
"""
|
||||
Aux console from scene items.
|
||||
@@ -1074,7 +1084,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
node = nodes[name]
|
||||
callback = functools.partial(self.consoleToNode, node, aux=True)
|
||||
callback = qpartial(self.consoleToNode, node, aux=True)
|
||||
self._main_window.run_later(counter, callback)
|
||||
counter += delay
|
||||
|
||||
@@ -1111,23 +1121,50 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
self._import_config_dir = self._main_window.project().filesDir()
|
||||
|
||||
item = items[0]
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Import config",
|
||||
self._import_config_dir,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
|
||||
if path:
|
||||
self._import_config_dir = os.path.dirname(path)
|
||||
item.node().importConfig(path)
|
||||
if hasattr(item.node(), "importPrivateConfig"):
|
||||
# this node can have one startup-config and one private-config
|
||||
default_startup_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + "_startup-config.cfg"
|
||||
if os.path.exists(default_startup_config_path):
|
||||
default_import_path = default_startup_config_path
|
||||
else:
|
||||
default_import_path = self._import_config_dir
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Import startup-config",
|
||||
default_import_path,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
|
||||
if path:
|
||||
self._import_config_dir = os.path.dirname(path)
|
||||
item.node().importConfig(path)
|
||||
|
||||
default_private_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + "_private-config.cfg"
|
||||
if os.path.exists(default_private_config_path):
|
||||
default_import_path = default_private_config_path
|
||||
else:
|
||||
default_import_path = self._import_config_dir
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Import private-config",
|
||||
self._import_config_dir,
|
||||
default_import_path,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
if path:
|
||||
item.node().importPrivateConfig(path)
|
||||
else:
|
||||
# this node has just one config
|
||||
default_config_path = os.path.join(self._import_config_dir, normalize_filename(item.node().name())) + ".cfg"
|
||||
if os.path.exists(default_config_path):
|
||||
default_import_path = default_config_path
|
||||
else:
|
||||
default_import_path = self._import_config_dir
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Import config",
|
||||
default_import_path,
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
|
||||
if path:
|
||||
self._import_config_dir = os.path.dirname(path)
|
||||
item.node().importConfig(path)
|
||||
|
||||
def exportConfigActionSlot(self):
|
||||
"""
|
||||
@@ -1155,12 +1192,17 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
item = items[0]
|
||||
if hasattr(item.node(), "importPrivateConfig"):
|
||||
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export startup-config", self._export_config_dir)
|
||||
# this node can have one startup-config and one private-config
|
||||
default_startup_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + "_startup-config.cfg"
|
||||
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export startup-config", default_startup_config_path)
|
||||
self._export_config_dir = os.path.dirname(config_path)
|
||||
private_config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export private-config", self._export_config_dir)
|
||||
default_private_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + "_private-config.cfg"
|
||||
private_config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export private-config", default_private_config_path)
|
||||
item.node().exportConfig(config_path, private_config_path)
|
||||
else:
|
||||
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export config", self._export_config_dir)
|
||||
# this node has just one config
|
||||
default_config_path = os.path.join(self._export_config_dir, normalize_filename(item.node().name())) + ".cfg"
|
||||
config_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Export config", default_config_path)
|
||||
self._export_config_dir = os.path.dirname(config_path)
|
||||
item.node().exportConfig(config_path)
|
||||
|
||||
@@ -1201,6 +1243,31 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, "Capture", "No port available for packet capture on {}".format(node.name()))
|
||||
|
||||
def getCommandLineSlot(self):
|
||||
"""
|
||||
Slot to receive events from the get command line action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
items = self.scene().selectedItems()
|
||||
if len(items) != 1:
|
||||
QtWidgets.QMessageBox.critical(self, "Command line", "Please select only one router")
|
||||
return
|
||||
item = items[0]
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "commandLine"):
|
||||
router = item.node()
|
||||
if router.commandLine() is None:
|
||||
QtWidgets.QMessageBox.warning(self, "Command line", "Get command line is not supported for this type of node.")
|
||||
elif router.commandLine() == '':
|
||||
QtWidgets.QMessageBox.warning(self, "Command line", "Please start the node in order to get the command line.")
|
||||
else:
|
||||
dialog = QtWidgets.QInputDialog(self)
|
||||
dialog.setOptions(QtWidgets.QInputDialog.NoButtons)
|
||||
dialog.setLabelText("Command used to start the VM:")
|
||||
dialog.setTextValue(router.commandLine())
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def idlepcActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the idlepc action in the
|
||||
@@ -1336,8 +1403,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
if horizontal_pos is None:
|
||||
horizontal_pos = item.y()
|
||||
item.setPos(item.x(), horizontal_pos)
|
||||
horizontal_pos = item.y() + item.boundingRect().height() / 2
|
||||
item.setPos(item.x(), horizontal_pos - item.boundingRect().height() / 2)
|
||||
|
||||
def verticalAlignmentSlot(self):
|
||||
"""
|
||||
@@ -1349,8 +1416,8 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
if vertical_position is None:
|
||||
vertical_position = item.x()
|
||||
item.setPos(vertical_position, item.y())
|
||||
vertical_position = item.x() + item.boundingRect().width() / 2
|
||||
item.setPos(vertical_position - item.boundingRect().width() / 2, item.y())
|
||||
|
||||
def raiseLayerActionSlot(self):
|
||||
"""
|
||||
@@ -1405,6 +1472,25 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
elif item.parentItem() is None:
|
||||
item.delete()
|
||||
|
||||
@staticmethod
|
||||
def allocateServer(node_data, module_instance):
|
||||
"""
|
||||
Allocates a server.
|
||||
|
||||
:returns: allocated server (HTTPClient instance)
|
||||
"""
|
||||
|
||||
from .main_window import MainWindow
|
||||
mainwindow = MainWindow.instance()
|
||||
|
||||
allow_local_server = True
|
||||
if "builtin" in node_data:
|
||||
allow_local_server = module_instance.settings()["use_local_server"]
|
||||
server = server_select(mainwindow, allow_local_server=allow_local_server)
|
||||
if server is None:
|
||||
raise ModuleError("Please select a server")
|
||||
return server
|
||||
|
||||
def createNode(self, node_data, pos):
|
||||
"""
|
||||
Creates a new node on the scene.
|
||||
@@ -1428,7 +1514,7 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
raise ModuleError("Could not find any module for {}".format(node_class))
|
||||
|
||||
if "server" not in node_data:
|
||||
server = node_module.allocateServer(node_class)
|
||||
server = self.allocateServer(node_data, instance)
|
||||
elif node_data["server"] == "local":
|
||||
server = Servers.instance().localServer()
|
||||
elif node_data["server"] == "vm":
|
||||
@@ -1452,12 +1538,10 @@ class GraphicsView(QtWidgets.QGraphicsView):
|
||||
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
|
||||
node.warning_signal.connect(self._main_window.uiConsoleTextEdit.writeWarning)
|
||||
node.server_error_signal.connect(self._main_window.uiConsoleTextEdit.writeServerError)
|
||||
if QtSvg.QSvgRenderer(node_data["symbol"]).isValid():
|
||||
node_item = SvgNodeItem(node, node_data["symbol"])
|
||||
else:
|
||||
node_item = PixmapNodeItem(node, node_data["symbol"])
|
||||
node_item = SvgNodeItem(node, node_data["symbol"])
|
||||
node_module.setupNode(node, node_data["name"])
|
||||
except ModuleError as e:
|
||||
# If no server is available a ValueError is raised
|
||||
except (ModuleError, ValueError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Node creation", "{}".format(e))
|
||||
return
|
||||
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
|
||||
import json
|
||||
import http
|
||||
import copy
|
||||
import ipaddress
|
||||
import uuid
|
||||
import urllib.request
|
||||
import pathlib
|
||||
import base64
|
||||
from functools import partial
|
||||
|
||||
from .version import __version__, __version_info__
|
||||
from .qt import QtCore, QtNetwork
|
||||
from .qt import QtCore, QtNetwork, qpartial
|
||||
from .network_client import getNetworkUrl
|
||||
from .utils import parse_version
|
||||
|
||||
@@ -54,7 +54,9 @@ class HTTPClient(QtCore.QObject):
|
||||
# Callback class used for displaying progress
|
||||
_progress_callback = None
|
||||
|
||||
connected_signal = QtCore.Signal()
|
||||
connection_connected_signal = QtCore.Signal()
|
||||
connection_closed_signal = QtCore.Signal()
|
||||
system_usage_updated_signal = QtCore.Signal()
|
||||
connection_error_signal = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, settings, network_manager):
|
||||
@@ -64,7 +66,10 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
self._scheme = settings.get("protocol", "http")
|
||||
self._host = settings["host"]
|
||||
self._http_host = settings["host"]
|
||||
if "http_host" in settings:
|
||||
self._http_host = settings["http_host"]
|
||||
else:
|
||||
self._http_host = settings["host"]
|
||||
self._port = int(settings["port"])
|
||||
self._http_port = int(settings["port"])
|
||||
self._user = settings.get("user", None)
|
||||
@@ -76,6 +81,7 @@ class HTTPClient(QtCore.QObject):
|
||||
self._ram_limit = settings.get("ram_limit", 0)
|
||||
self._allocated_ram = 0
|
||||
self._accept_insecure_certificate = settings.get("accept_insecure_certificate", None)
|
||||
self._usage = None
|
||||
|
||||
self._network_manager = network_manager
|
||||
|
||||
@@ -86,25 +92,6 @@ class HTTPClient(QtCore.QObject):
|
||||
self._id = HTTPClient._instance_count
|
||||
HTTPClient._instance_count += 1
|
||||
|
||||
def getTunnel(self, port):
|
||||
"""
|
||||
Get a tunnel to the remote port.
|
||||
For HTTP standard client it's the same port. For SSH it will create a new tunnel.
|
||||
|
||||
:param port: Remote port
|
||||
:returns: Tuple host, port to connect
|
||||
"""
|
||||
return self._host, port
|
||||
|
||||
def releaseTunnel(self, port):
|
||||
"""
|
||||
Release a tunnel to the remote port.
|
||||
For HTTP standard client it's do nothing
|
||||
|
||||
:param port: Allocated remote port
|
||||
"""
|
||||
pass
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Return a dictionnary with server settings
|
||||
@@ -283,6 +270,7 @@ class HTTPClient(QtCore.QObject):
|
||||
"""
|
||||
log.info("Connection to %s closed", self.url())
|
||||
self._connected = False
|
||||
self.connection_closed_signal.emit()
|
||||
|
||||
def isLocalServerRunning(self):
|
||||
"""
|
||||
@@ -295,13 +283,9 @@ class HTTPClient(QtCore.QObject):
|
||||
if json_data is None or status != 200:
|
||||
return False
|
||||
else:
|
||||
version = json_data.get("version")
|
||||
local_server = json_data.get("local", False)
|
||||
if version != __version__:
|
||||
log.debug("Client version {} differs with server version {}".format(__version__, version))
|
||||
return False
|
||||
if not local_server:
|
||||
log.debug("Running server is not a GNS3 local server (not started with --local)")
|
||||
version = json_data.get("version", None)
|
||||
if version is None:
|
||||
log.debug("Server is not a GNS3 server")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -333,12 +317,18 @@ class HTTPClient(QtCore.QObject):
|
||||
return response.status, json_data
|
||||
else:
|
||||
return response.status, None
|
||||
except http.client.InvalidURL as e:
|
||||
log.warn("Invalid local server url: {}".format(e))
|
||||
return 0, None
|
||||
except urllib.error.URLError:
|
||||
# Connection refused. It's a normal behavior if server is not started
|
||||
return 0, None
|
||||
except urllib.error.HTTPError as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
|
||||
return e.code, None
|
||||
except (OSError, http.client.BadStatusLine, ValueError) as e:
|
||||
log.debug("Error during get on {}:{}: {}".format(self.host(), self.port(), e))
|
||||
return 0, None
|
||||
return 0, None
|
||||
|
||||
def get(self, path, callback, **kwargs):
|
||||
"""
|
||||
@@ -399,13 +389,11 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
return QtNetwork.QNetworkRequest(url)
|
||||
|
||||
# FIXME: connect is a method in parent class (QObject)
|
||||
def connect(self, query, callback):
|
||||
def _connect(self, query):
|
||||
"""
|
||||
Initialize the connection
|
||||
|
||||
:param query: The query to execute when all network stack is ready
|
||||
:param callback: User callback when connection is finish
|
||||
"""
|
||||
self.executeHTTPQuery("GET", "/version", query, {})
|
||||
|
||||
@@ -426,11 +414,11 @@ class HTTPClient(QtCore.QObject):
|
||||
"""
|
||||
|
||||
if self._connected:
|
||||
return self.executeHTTPQuery(method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
|
||||
return self.executeHTTPQuery(method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
|
||||
else:
|
||||
log.info("Connection to {}".format(self.url()))
|
||||
query = partial(self._callbackConnect, method, path, callback, body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
|
||||
self.connect(query, callback)
|
||||
query = qpartial(self._callbackConnect, method, path, qpartial(callback), body, context, downloadProgressCallback=downloadProgressCallback, showProgress=showProgress, ignoreErrors=ignoreErrors, progressText=progressText)
|
||||
self._connect(query)
|
||||
|
||||
def _connectionError(self, callback, msg=""):
|
||||
"""
|
||||
@@ -440,10 +428,17 @@ class HTTPClient(QtCore.QObject):
|
||||
:param msg: An optional additional message for the callback
|
||||
"""
|
||||
|
||||
if len(msg) > 0:
|
||||
msg = "Can't connect to server {}: {}".format(self.url(), msg)
|
||||
if self.isLocal():
|
||||
server = "local server {}".format(self.url())
|
||||
else:
|
||||
msg = "Can't connect to server {}".format(self.url())
|
||||
server = "remote server {}".format(self.url())
|
||||
if len(msg) > 0:
|
||||
msg = "Cannot connect to {}: {}".format(server, msg)
|
||||
else:
|
||||
if self.isLocal():
|
||||
msg = "Cannot connect to {}. Please check if GNS3 is allowed in your antivirus and firewall.".format(server)
|
||||
else:
|
||||
msg = "Cannot connect to {}".format(server)
|
||||
log.error(msg)
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
@@ -497,6 +492,7 @@ class HTTPClient(QtCore.QObject):
|
||||
return
|
||||
|
||||
self._connected = True
|
||||
self.connection_connected_signal.emit()
|
||||
kwargs["context"] = original_context
|
||||
self.executeHTTPQuery(method, path, callback, body, **kwargs)
|
||||
self._version = params["version"]
|
||||
@@ -568,7 +564,10 @@ class HTTPClient(QtCore.QObject):
|
||||
host = self._http_host
|
||||
|
||||
log.debug("{method} {protocol}://{host}:{port}/v1{path} {body}".format(method=method, protocol=self._scheme, host=host, port=self._http_port, path=path, body=body))
|
||||
url = QtCore.QUrl("{protocol}://{host}:{port}/v1{path}".format(protocol=self._scheme, host=host, port=self._http_port, path=path))
|
||||
if self._user:
|
||||
url = QtCore.QUrl("{protocol}://{user}@{host}:{port}/v1{path}".format(protocol=self._scheme, user=self._user, host=host, port=self._http_port, path=path))
|
||||
else:
|
||||
url = QtCore.QUrl("{protocol}://{host}:{port}/v1{path}".format(protocol=self._scheme, host=host, port=self._http_port, path=path))
|
||||
request = self._request(url)
|
||||
|
||||
request = self.addAuth(request)
|
||||
@@ -580,53 +579,58 @@ class HTTPClient(QtCore.QObject):
|
||||
|
||||
response = self._network_manager.sendCustomRequest(request, method.encode(), body)
|
||||
|
||||
import copy
|
||||
context = copy.copy(context)
|
||||
query_id = str(uuid.uuid4())
|
||||
context["query_id"] = query_id
|
||||
if showProgress:
|
||||
self.notify_progress_start_query(context["query_id"], progressText, response)
|
||||
response.uploadProgress.connect(partial(self.notify_progress_upload, query_id))
|
||||
response.downloadProgress.connect(partial(self.notify_progress_download, query_id))
|
||||
context["query_id"] = str(uuid.uuid4())
|
||||
|
||||
response.finished.connect(qpartial(self._processResponse, response, callback, context, body, ignoreErrors))
|
||||
|
||||
response.finished.connect(partial(self._processResponse, response, callback, context, body, ignoreErrors))
|
||||
if downloadProgressCallback is not None:
|
||||
response.downloadProgress.connect(partial(self._processDownloadProgress, response, downloadProgressCallback, context))
|
||||
response.downloadProgress.connect(qpartial(self._processDownloadProgress, response, downloadProgressCallback, context))
|
||||
|
||||
if showProgress:
|
||||
response.uploadProgress.connect(qpartial(self.notify_progress_upload, context["query_id"]))
|
||||
response.downloadProgress.connect(qpartial(self.notify_progress_download, context["query_id"]))
|
||||
# Should be the last operation otherwise we have race condition in Qt
|
||||
# where query start before finishing connect to everything
|
||||
self.notify_progress_start_query(context["query_id"], progressText, response)
|
||||
|
||||
return response
|
||||
|
||||
def _processDownloadProgress(self, response, callback, context):
|
||||
def _processDownloadProgress(self, response, callback, context, bytesReceived, bytesTotal):
|
||||
"""
|
||||
Process a packet receive on the notification feed.
|
||||
The feed can contains partial JSON. If we found a
|
||||
The feed can contains qpartial JSON. If we found a
|
||||
part of a JSON we keep it for the next packet
|
||||
"""
|
||||
|
||||
if response.error() == QtNetwork.QNetworkReply.NoError:
|
||||
error_code = response.error()
|
||||
if response.error() != QtNetwork.QNetworkReply.NoError:
|
||||
return
|
||||
|
||||
if error_code >= 300:
|
||||
return
|
||||
# HTTP error
|
||||
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if status >= 300:
|
||||
return
|
||||
|
||||
content = bytes(response.readAll())
|
||||
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
|
||||
if content_type == "application/json":
|
||||
content = content.decode("utf-8")
|
||||
if context["query_id"] in self._buffer:
|
||||
content = self._buffer[context["query_id"]] + content
|
||||
try:
|
||||
while True:
|
||||
content = content.lstrip(" \r\n\t")
|
||||
answer, index = json.JSONDecoder().raw_decode(content)
|
||||
callback(answer, server=self, context=context)
|
||||
content = content[index:]
|
||||
except ValueError: # Partial JSON
|
||||
self._buffer[context["query_id"]] = content
|
||||
else:
|
||||
callback(content, server=self, context=context)
|
||||
content = bytes(response.readAll())
|
||||
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
|
||||
if content_type == "application/json":
|
||||
content = content.decode("utf-8")
|
||||
if context["query_id"] in self._buffer:
|
||||
content = self._buffer[context["query_id"]] + content
|
||||
try:
|
||||
while True:
|
||||
content = content.lstrip(" \r\n\t")
|
||||
answer, index = json.JSONDecoder().raw_decode(content)
|
||||
callback(answer, server=self, context=context)
|
||||
content = content[index:]
|
||||
except ValueError: # Partial JSON
|
||||
self._buffer[context["query_id"]] = content
|
||||
else:
|
||||
callback(content, server=self, context=context)
|
||||
|
||||
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
|
||||
request_canceled = partial(self._requestCanceled, response, context)
|
||||
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
|
||||
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
|
||||
request_canceled = qpartial(self._requestCanceled, response, context)
|
||||
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
|
||||
|
||||
def _requestCanceled(self, response, context):
|
||||
|
||||
@@ -675,7 +679,11 @@ class HTTPClient(QtCore.QObject):
|
||||
callback({"message": error_message}, error=True, server=self, context=context)
|
||||
else:
|
||||
log.debug(body)
|
||||
callback(json.loads(body), error=True, server=self, context=context)
|
||||
try:
|
||||
callback(json.loads(body), error=True, server=self, context=context)
|
||||
except ValueError:
|
||||
# It happens when an antivirus catch the communication and send is error page without changing the Content Type
|
||||
callback({"message": error_message}, error=True, server=self, context=context)
|
||||
else:
|
||||
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
log.debug("Decoding response from {} response {}".format(response.url().toString(), status))
|
||||
@@ -765,5 +773,14 @@ class HTTPClient(QtCore.QObject):
|
||||
server["accept_insecure_certificate"] = self._accept_insecure_certificate
|
||||
return server
|
||||
|
||||
def isCloud(self):
|
||||
return False
|
||||
def systemUsage(self):
|
||||
"""
|
||||
Get information about current system usage
|
||||
|
||||
:returns: None or dict
|
||||
"""
|
||||
return self._usage
|
||||
|
||||
def setSystemUsage(self, usage):
|
||||
self._usage = usage
|
||||
self.system_usage_updated_signal.emit()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import glob
|
||||
|
||||
from gns3.servers import Servers
|
||||
from gns3.qt import QtWidgets
|
||||
@@ -55,6 +56,11 @@ class ImageManager:
|
||||
QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.QMessageBox.Yes:
|
||||
destination_path = os.path.join(destination_directory, os.path.basename(path))
|
||||
try:
|
||||
os.makedirs(destination_directory, exist_ok=True)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(parent, 'Image', 'Could not create destination directory {}: {}'.format(destination_directory, str(e)))
|
||||
return path
|
||||
worker = FileCopyWorker(path, destination_path)
|
||||
progress_dialog = ProgressDialog(worker, 'Image', 'Copying {}'.format(os.path.basename(path)), 'Cancel', busy=True, parent=parent)
|
||||
progress_dialog.show()
|
||||
@@ -62,6 +68,7 @@ class ImageManager:
|
||||
errors = progress_dialog.errors()
|
||||
if errors:
|
||||
QtWidgets.QMessageBox.critical(parent, 'Image', '{}'.format(''.join(errors)))
|
||||
return path
|
||||
else:
|
||||
path = destination_path
|
||||
return path
|
||||
@@ -85,8 +92,8 @@ class ImageManager:
|
||||
else:
|
||||
raise Exception('Invalid image vm_type')
|
||||
|
||||
filename = os.path.basename(path)
|
||||
server.post('{}/{}'.format(upload_endpoint, filename), None, body=pathlib.Path(path))
|
||||
filename = self._getRelativeImagePath(path, vm_type).replace("\\", "/")
|
||||
server.post('{}/{}'.format(upload_endpoint, filename), None, body=pathlib.Path(path), progressText="Uploading {}".format(filename))
|
||||
return filename
|
||||
|
||||
def addMissingImage(self, filename, server, vm_type):
|
||||
@@ -106,6 +113,13 @@ class ImageManager:
|
||||
path = os.path.join(self.getDirectoryForType(vm_type), filename)
|
||||
if os.path.exists(path):
|
||||
if self._askForUploadMissingImage(filename, server):
|
||||
|
||||
if filename.endswith(".vmdk"):
|
||||
# A vmdk file could be split in multiple vmdk file
|
||||
search = glob.escape(path).replace(".vmdk", "-*.vmdk")
|
||||
for file in glob.glob(search):
|
||||
self._uploadImageToRemoteServer(file, server, vm_type)
|
||||
|
||||
self._uploadImageToRemoteServer(path, server, vm_type)
|
||||
del self._asked_for_this_image[server.id()][filename]
|
||||
|
||||
@@ -121,6 +135,25 @@ class ImageManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _getRelativeImagePath(self, path, vm_type):
|
||||
"""
|
||||
Get a path relative to images directory path
|
||||
or just filename if the path is not located inside
|
||||
image directory
|
||||
|
||||
:param path: file path
|
||||
:param vm_type: Type of vm
|
||||
:return: file path
|
||||
"""
|
||||
|
||||
if not path:
|
||||
return ""
|
||||
img_directory = self.getDirectoryForType(vm_type)
|
||||
path = os.path.abspath(path)
|
||||
if os.path.commonprefix([img_directory, path]) == img_directory:
|
||||
return os.path.relpath(path, img_directory)
|
||||
return os.path.basename(path)
|
||||
|
||||
def getDirectory(self):
|
||||
"""
|
||||
Returns the images directory path.
|
||||
@@ -134,6 +167,8 @@ class ImageManager:
|
||||
"""
|
||||
Return the path of local directory of the images
|
||||
of a specific vm_type
|
||||
|
||||
:param vm_type: Type of vm
|
||||
"""
|
||||
if vm_type == 'DYNAMIPS':
|
||||
return os.path.join(self.getDirectory(), 'IOS')
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
|
||||
import sys
|
||||
import shutil
|
||||
import signal
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
try:
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets, DEFAULT_BINDING
|
||||
from gns3.qt import QtGui, QtWidgets
|
||||
except ImportError:
|
||||
raise SystemExit("Can't import Qt modules: Qt and/or PyQt is probably not installed correctly...")
|
||||
|
||||
@@ -37,7 +36,9 @@ from gns3.version import __version__
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.ui.iouvm_converter_wizard_ui import Ui_IOUVMConverterWizard
|
||||
|
||||
|
||||
class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
@@ -47,7 +48,7 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
|
||||
self.setOptions(QtWidgets.QWizard.NoDefaultButton)
|
||||
|
||||
# set the window icon
|
||||
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico"))# this info is necessary for QSettings
|
||||
self.setWindowIcon(QtGui.QIcon(":/images/gns3.ico")) # this info is necessary for QSettings
|
||||
|
||||
config = self._loadConfig()
|
||||
self.uiPushButtonBrowse.clicked.connect(self._browseTopologiesSlot)
|
||||
@@ -126,7 +127,7 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
|
||||
topo = json.load(f)
|
||||
if "topology" in topo and "servers" in topo["topology"]:
|
||||
for server in topo["topology"]["servers"]:
|
||||
if server["local"] == False:
|
||||
if server["local"] is False:
|
||||
server["vm"] = True
|
||||
with open(path, 'w+') as f:
|
||||
topo = json.dump(topo, f)
|
||||
@@ -148,7 +149,8 @@ class IOUVMConverterWizard(QtWidgets.QWizard, Ui_IOUVMConverterWizard):
|
||||
else:
|
||||
filename = "gns3_gui.conf"
|
||||
directory = LocalConfig.configDirectory()
|
||||
return os.path.join(directory, filename)
|
||||
return os.path.join(directory, filename)
|
||||
|
||||
|
||||
def main():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
@@ -172,6 +172,20 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
|
||||
cls._draw_port_labels = state
|
||||
|
||||
def resetPortLabels(self):
|
||||
"""
|
||||
Resets the port label positions.
|
||||
"""
|
||||
|
||||
source_port_label = self._source_port.label()
|
||||
destination_port_label = self._destination_port.label()
|
||||
if source_port_label is not None:
|
||||
source_port_label.delete()
|
||||
self._source_port.setLabel(None)
|
||||
if destination_port_label is not None:
|
||||
destination_port_label.delete()
|
||||
self._destination_port.setLabel(None)
|
||||
|
||||
def populateLinkContextualMenu(self, menu):
|
||||
"""
|
||||
Adds device actions to the link contextual menu.
|
||||
@@ -318,13 +332,13 @@ class LinkItem(QtWidgets.QGraphicsPathItem):
|
||||
selection, ok = QtWidgets.QInputDialog.getItem(self._main_window, "Packet capture", "Please select a port:", ports, 0, False)
|
||||
if ok:
|
||||
if selection.endswith(self._source_port.name()):
|
||||
self._source_port.startPacketCaptureReader()
|
||||
self._source_port.startPacketCaptureReader(self._source_item.node().name())
|
||||
else:
|
||||
self._destination_port.startPacketCaptureReader()
|
||||
self._destination_port.startPacketCaptureReader(self._destination_item.node().name())
|
||||
elif self._source_port.capturing():
|
||||
self._source_port.startPacketCaptureReader()
|
||||
self._source_port.startPacketCaptureReader(self._source_item.node().name())
|
||||
elif self._destination_port.capturing():
|
||||
self._destination_port.startPacketCaptureReader()
|
||||
self._destination_port.startPacketCaptureReader(self._destination_item.node().name())
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self._main_window, "Packet capture", "Cannot start Wireshark: {}".format(e))
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class NodeItem():
|
||||
effect.setColor(QtGui.QColor("black"))
|
||||
effect.setStrength(0.8)
|
||||
#effect = QtWidgets.QGraphicsDropShadowEffect()
|
||||
#effect.setColor(QtGui.QColor("darkGray"))
|
||||
#effect.setBlurRadius(0)
|
||||
# effect.setColor(QtGui.QColor("darkGray"))
|
||||
# effect.setBlurRadius(0)
|
||||
#effect.setOffset(3, 3)
|
||||
self.setGraphicsEffect(effect)
|
||||
self.graphicsEffect().setEnabled(False)
|
||||
@@ -186,6 +186,8 @@ class NodeItem():
|
||||
when a the node has been updated.
|
||||
"""
|
||||
|
||||
if self is None:
|
||||
return
|
||||
if self._node_label:
|
||||
if self._node_label.toPlainText() != self._node.name():
|
||||
self._node_label.setPlainText(self._node.name())
|
||||
@@ -212,6 +214,8 @@ class NodeItem():
|
||||
when the node has been deleted.
|
||||
"""
|
||||
|
||||
if self is None:
|
||||
return
|
||||
self._node.removeAllocatedName()
|
||||
if self in self.scene().items():
|
||||
self.scene().removeItem(self)
|
||||
@@ -226,7 +230,8 @@ class NodeItem():
|
||||
:param message: error message
|
||||
"""
|
||||
|
||||
self._last_error = "{message}".format(message=message)
|
||||
if self:
|
||||
self._last_error = "{message}".format(message=message)
|
||||
|
||||
def errorSlot(self, node_id, message):
|
||||
"""
|
||||
@@ -237,7 +242,8 @@ class NodeItem():
|
||||
:param message: error message
|
||||
"""
|
||||
|
||||
self._last_error = "{message}".format(message=message)
|
||||
if self:
|
||||
self._last_error = "{message}".format(message=message)
|
||||
|
||||
def setCustomToolTip(self):
|
||||
"""
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Graphical representation of a pixmap node on the QGraphicsScene.
|
||||
"""
|
||||
|
||||
from ..qt import QtGui, QtWidgets
|
||||
from .node_item import NodeItem
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PixmapNodeItem(NodeItem, QtWidgets.QGraphicsPixmapItem):
|
||||
|
||||
"""
|
||||
Pixmap node for the scene.
|
||||
|
||||
:param node: Node instance
|
||||
:param pixmap_symbol: symbol for the node representation on the scene
|
||||
"""
|
||||
|
||||
def __init__(self, node, pixmap_symbol_path):
|
||||
|
||||
QtWidgets.QGraphicsPixmapItem.__init__(self)
|
||||
NodeItem.__init__(self, node)
|
||||
|
||||
self._pixmap_symbol_path = pixmap_symbol_path
|
||||
pixmap = QtGui.QPixmap(pixmap_symbol_path)
|
||||
self.setPixmap(pixmap)
|
||||
|
||||
def setPixmapSymbolPath(self, path):
|
||||
"""
|
||||
Sets the pixmap path
|
||||
|
||||
:param path: path to the Pixmap file.
|
||||
"""
|
||||
|
||||
self._pixmap_symbol_path = path
|
||||
|
||||
def pixmapSymbolPath(self):
|
||||
"""
|
||||
Returns the pixmap path
|
||||
|
||||
:returns: path to the Pixmap file.
|
||||
"""
|
||||
|
||||
return self._pixmap_symbol_path
|
||||
@@ -20,6 +20,7 @@ Graphical representation of a SVG node on the QGraphicsScene.
|
||||
"""
|
||||
|
||||
from ..qt import QtSvg
|
||||
from ..qt.qimage_svg_renderer import QImageSvgRenderer
|
||||
from .node_item import NodeItem
|
||||
|
||||
import logging
|
||||
@@ -42,9 +43,9 @@ class SvgNodeItem(NodeItem, QtSvg.QGraphicsSvgItem):
|
||||
|
||||
# create renderer using symbols path/resource
|
||||
if symbol:
|
||||
renderer = QtSvg.QSvgRenderer(symbol)
|
||||
renderer = QImageSvgRenderer(symbol)
|
||||
if symbol != node.defaultSymbol():
|
||||
renderer.setObjectName(symbol)
|
||||
else:
|
||||
renderer = QtSvg.QSvgRenderer(node.defaultSymbol())
|
||||
renderer = QImageSvgRenderer(node.defaultSymbol())
|
||||
self.setSharedRenderer(renderer)
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import rsa
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
|
||||
PUB_KEY = b"""-----BEGIN RSA PUBLIC KEY-----
|
||||
MIICCgKCAgEAiE4Zgzge3Cg6EUfct7vnzcmXkIvsy6g/QkfEeKSz3Cd+L7kxVZGE
|
||||
weOXySrSSrRBoF1i2JhL2KkqZTY31972deviL+fv+TgE5RueyERFey3fw7+oN/RW
|
||||
i8UIUvRqHjwocCuJq5yUiOv+AdGKG3TNeYXvx4Xvnrr4AJnJRThDfqd0nr8QAXRn
|
||||
/Ifx4MKivL8RDyqHoVlHvHeyJmtaZIzsYthsK3FU2XED6d6xwbga3t2cb4+DfJa3
|
||||
rBtWnoIXHiRdZZUtl34dGiiyxKL2yco+Dpd5pUvw6F7+n77SnSwN+F0ZzrrgUMHA
|
||||
vBHBnF4WB6mjRFxbO+B/H1OxnXcjwxgYWLCbkrhQogqyfdkmacppWLOH9OyzGUkY
|
||||
r7qITLCWSAHuIqXmQF4VAqCPYwEK7o6ndebFk1jaAAPGIw52AA1YOSXJ6jpKiO7f
|
||||
5gXT3xRfv4kW1Fp6le0hp0Laz6VGbOv44vauxk516v5MI+CUL3u5TOmGWM53u1OG
|
||||
qq6SfL+5Cu0/4L+SUaJ7nzN+PgWx6BEd0LRzEVQcmRPA4zHbhJ7ebBbYOul9RFyW
|
||||
8D7yy7mUQZwVQDcuaB6l2pu0BfZppb+Uf81h0nRQIrHt7BRBiyaGojQIHsw8CrqP
|
||||
3fsnHUvqtNLipC26FSTW4wlPIEktsWU8TABgjbuS45+zFTI141/J77ECAwEAAQ==
|
||||
-----END RSA PUBLIC KEY-----"""
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def checkLicence():
|
||||
"""
|
||||
Return true if the user as correctly installed the licence file
|
||||
"""
|
||||
appname = "GNS3"
|
||||
|
||||
filename = "licence.txt"
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
# On windows, the user specific configuration file location is %APPDATA%/GNS3/gns3_gui.conf
|
||||
appdata = os.path.expandvars("%APPDATA%")
|
||||
licence_file = os.path.join(appdata, appname, filename)
|
||||
|
||||
else:
|
||||
# On UNIX-like platforms, the user specific configuration file location is /etc/xdg/GNS3/gns3_gui.conf
|
||||
home = os.path.expanduser("~")
|
||||
licence_file = os.path.join(home, ".config", appname, filename)
|
||||
|
||||
return check_licence_file(licence_file)
|
||||
|
||||
|
||||
def check_licence_file(licence_file):
|
||||
if os.path.exists(licence_file):
|
||||
with open(licence_file) as f:
|
||||
email = f.readline().strip()
|
||||
key = f.readline().strip()
|
||||
pubkey = rsa.PublicKey.load_pkcs1(PUB_KEY)
|
||||
try:
|
||||
rsa.verify(email.encode("utf-8"), base64.b64decode(key), pubkey)
|
||||
log.info("Found a valid licence file. Thanks for your support")
|
||||
return True
|
||||
except rsa.pkcs1.VerificationError:
|
||||
log.error("Invalid licence file.")
|
||||
return False
|
||||
return False
|
||||
63
gns3/link.py
63
gns3/link.py
@@ -22,6 +22,7 @@ Manages and stores everything needed for a connection between 2 devices.
|
||||
|
||||
from .qt import QtCore
|
||||
from .nios.nio_udp import NIOUDP
|
||||
from .nios.nio_vmnet import NIOVMNET
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -72,30 +73,33 @@ class Link(QtCore.QObject):
|
||||
self._stub = True
|
||||
else:
|
||||
self._stub = False
|
||||
|
||||
# we must request UDP information if the NIO is a NIO UDP and before
|
||||
# it can be created.
|
||||
if not self._stub:
|
||||
|
||||
# connect signals used when a NIO has been created by a node
|
||||
# and this NIO need to be attached to a port connected to this link
|
||||
source_node.nio_signal.connect(self.newNIOSlot)
|
||||
destination_node.nio_signal.connect(self.newNIOSlot)
|
||||
|
||||
# currently, we support only NIO_UDP for normal connections (non-stub).
|
||||
if not source_port.defaultNio() == NIOUDP:
|
||||
# currently, we support only NIO_UDP and NIO_VMNET for normal connections (non-stub).
|
||||
if source_port.defaultNio() == NIOUDP:
|
||||
assert destination_port.defaultNio() == NIOUDP
|
||||
self._source_udp = None
|
||||
self._destination_udp = None
|
||||
|
||||
# connect signals used to receive a UDP port and host allocated by a node
|
||||
source_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
|
||||
destination_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
|
||||
|
||||
# request the UDP info for each node
|
||||
source_node.allocateUDPPort(self._source_port.id())
|
||||
destination_node.allocateUDPPort(self._destination_port.id())
|
||||
elif source_port.defaultNio() == NIOVMNET:
|
||||
assert destination_port.defaultNio() == NIOVMNET
|
||||
source_node.allocate_vmnet_nio_signal.connect(self.VMnetInterfaceAllocatedSlot)
|
||||
source_node.allocateVMnetInterface(self._source_port.id())
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
self._source_udp = None
|
||||
self._destination_udp = None
|
||||
|
||||
# connect signals used to receive a UDP port and host allocated by a node
|
||||
source_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
|
||||
destination_node.allocate_udp_nio_signal.connect(self.UDPPortAllocatedSlot)
|
||||
|
||||
# request the UDP info for each node
|
||||
source_node.allocateUDPPort(self._source_port.id())
|
||||
destination_node.allocateUDPPort(self._destination_port.id())
|
||||
else:
|
||||
# handle stub connections (to a cloud for instance).
|
||||
if not source_port.isStub() and destination_port.isStub():
|
||||
@@ -203,7 +207,6 @@ class Link(QtCore.QObject):
|
||||
:param port_id: port identifier
|
||||
:param lport: local UDP port
|
||||
"""
|
||||
|
||||
# check that the node is connected to this link as a source
|
||||
if node_id == self._source_node.id() and port_id == self._source_port.id():
|
||||
laddr = self._source_node.server().host()
|
||||
@@ -227,7 +230,6 @@ class Link(QtCore.QObject):
|
||||
laddr))
|
||||
|
||||
if self._source_udp and self._destination_udp:
|
||||
|
||||
# we got UDP info from both source and destination nodes
|
||||
# meaning we can proceed with the creation of UDP NIOs
|
||||
lport, laddr = self._source_udp
|
||||
@@ -247,6 +249,30 @@ class Link(QtCore.QObject):
|
||||
self._destination_node.nio_cancel_signal.connect(self.cancelNIOSlot)
|
||||
self._destination_node.addNIO(self._destination_port, self._destination_nio)
|
||||
|
||||
def VMnetInterfaceAllocatedSlot(self, node_id, port_id, vmnet):
|
||||
"""
|
||||
Slot to receive events from Node instances
|
||||
when a VMnet interface has been allocated in order to create a NIO VMNET.
|
||||
|
||||
:param node_id: node identifier
|
||||
:param port_id: port identifier
|
||||
:param vmnet: vmnet interface name
|
||||
"""
|
||||
|
||||
# check that the node is connected to this link as a source
|
||||
# only the source is used to request the server for a vmnet interface
|
||||
# and then allocate a NIO VMNET to both the source and destination
|
||||
if node_id == self._source_node.id() and port_id == self._source_port.id():
|
||||
self._source_node.allocate_vmnet_nio_signal.disconnect(self.VMnetInterfaceAllocatedSlot)
|
||||
self._source_nio = NIOVMNET(vmnet)
|
||||
self._destination_nio = NIOVMNET(vmnet)
|
||||
|
||||
# add the VMnet NIOs to the nodes
|
||||
self._source_node.nio_cancel_signal.connect(self.cancelNIOSlot)
|
||||
self._source_node.addNIO(self._source_port, self._source_nio)
|
||||
self._destination_node.nio_cancel_signal.connect(self.cancelNIOSlot)
|
||||
self._destination_node.addNIO(self._destination_port, self._destination_nio)
|
||||
|
||||
def newNIOSlot(self, node_id, port_id):
|
||||
"""
|
||||
Slot to receive events from Node instances
|
||||
@@ -382,5 +408,4 @@ class Link(QtCore.QObject):
|
||||
"source_node_id": self._source_node.id(),
|
||||
"source_port_id": self._source_port.id(),
|
||||
"destination_node_id": self._destination_node.id(),
|
||||
"destination_port_id": self._destination_port.id(),
|
||||
}
|
||||
"destination_port_id": self._destination_port.id()}
|
||||
|
||||
@@ -21,35 +21,16 @@ import json
|
||||
import shutil
|
||||
import copy
|
||||
|
||||
import psutil
|
||||
|
||||
from .qt import QtCore
|
||||
from .version import __version__
|
||||
from .utils import parse_version
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PeriodicCheckConfig(QtCore.QThread):
|
||||
|
||||
"""
|
||||
Timer for checking if the configuration file change
|
||||
on disk.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
|
||||
def run(self):
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.timeout.connect(self._parent._checkConfigChanged)
|
||||
self._timer.setInterval(1000) # milliseconds
|
||||
self._timer.start()
|
||||
self.exec_()
|
||||
|
||||
|
||||
class LocalConfig(QtCore.QObject):
|
||||
|
||||
"""
|
||||
@@ -110,9 +91,6 @@ class LocalConfig(QtCore.QObject):
|
||||
self._migrateOldConfig()
|
||||
self._writeConfig()
|
||||
|
||||
self._check_thread = PeriodicCheckConfig(self)
|
||||
self._check_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def configDirectory():
|
||||
"""
|
||||
@@ -168,6 +146,15 @@ class LocalConfig(QtCore.QObject):
|
||||
main_window["hide_getting_started_dialog"] = self._settings["GUI"].get("hide_getting_started_dialog", False)
|
||||
self._settings["MainWindow"] = main_window
|
||||
|
||||
if "version" not in self._settings or parse_version(self._settings["version"]) < parse_version("1.4.1dev2"):
|
||||
if sys.platform.startswith("darwin"):
|
||||
from .settings import PRECONFIGURED_TELNET_CONSOLE_COMMANDS, DEFAULT_TELNET_CONSOLE_COMMAND
|
||||
|
||||
if "MainWindow" in self._settings:
|
||||
if self._settings["MainWindow"]["telnet_console_command"] not in PRECONFIGURED_TELNET_CONSOLE_COMMANDS.values():
|
||||
self._settings["MainWindow"]["telnet_console_command"] = DEFAULT_TELNET_CONSOLE_COMMAND
|
||||
|
||||
|
||||
def _readConfig(self, config_path):
|
||||
"""
|
||||
Read the configuration file.
|
||||
@@ -205,7 +192,8 @@ class LocalConfig(QtCore.QObject):
|
||||
except (ValueError, OSError) as e:
|
||||
log.error("Could not write the config file {}: {}".format(self._config_file, e))
|
||||
|
||||
def _checkConfigChanged(self):
|
||||
def checkConfigChanged(self):
|
||||
|
||||
try:
|
||||
if self._last_config_changed and self._last_config_changed < os.stat(self._config_file).st_mtime:
|
||||
log.info("Client config has changed, reloading it...")
|
||||
@@ -263,25 +251,31 @@ class LocalConfig(QtCore.QObject):
|
||||
"""
|
||||
|
||||
settings = self.settings().get(section, dict())
|
||||
changed = False
|
||||
|
||||
def _copySettings(local, default):
|
||||
"""
|
||||
Copy only existing settings, ignore the other.
|
||||
Add default values if require.
|
||||
"""
|
||||
nonlocal changed
|
||||
|
||||
# use default values for missing settings
|
||||
for name, value in default.items():
|
||||
if name not in local:
|
||||
local[name] = value
|
||||
changed = True
|
||||
elif isinstance(value, dict):
|
||||
local[name] = _copySettings(local[name], default[name])
|
||||
return local
|
||||
|
||||
settings = _copySettings(settings, default_settings)
|
||||
|
||||
self._settings[section] = settings
|
||||
|
||||
if changed:
|
||||
log.info("Section %s has missing default values. Adding keys %s Saving configuration", section, ','.join(set(default_settings.keys()) - set(settings.keys())))
|
||||
self._writeConfig()
|
||||
|
||||
return copy.deepcopy(settings)
|
||||
|
||||
def saveSectionSettings(self, section, settings):
|
||||
@@ -296,12 +290,20 @@ class LocalConfig(QtCore.QObject):
|
||||
self._settings[section] = {}
|
||||
|
||||
if self._settings[section] != settings:
|
||||
self._settings[section].update(settings)
|
||||
self._settings[section].update(copy.deepcopy(settings))
|
||||
log.info("Section %s has changed. Saving configuration", section)
|
||||
self._writeConfig()
|
||||
else:
|
||||
log.debug("Section %s has not changed. Skip saving configuration", section)
|
||||
|
||||
def experimental(self):
|
||||
"""
|
||||
:returns: Boolean. True if experimental features allowed
|
||||
"""
|
||||
|
||||
from gns3.settings import GENERAL_SETTINGS
|
||||
return self.loadSectionSettings("MainWindow", GENERAL_SETTINGS)["experimental_features"]
|
||||
|
||||
@staticmethod
|
||||
def instance(config_file=None):
|
||||
"""
|
||||
@@ -313,3 +315,41 @@ class LocalConfig(QtCore.QObject):
|
||||
if not hasattr(LocalConfig, "_instance") or LocalConfig._instance is None:
|
||||
LocalConfig._instance = LocalConfig(config_file=config_file)
|
||||
return LocalConfig._instance
|
||||
|
||||
@staticmethod
|
||||
def isMainGui():
|
||||
"""
|
||||
:returns: Return true if we are the main gui (first gui to start)
|
||||
"""
|
||||
|
||||
my_pid = os.getpid()
|
||||
pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_gui.pid")
|
||||
|
||||
if os.path.exists(pid_path):
|
||||
try:
|
||||
with open(pid_path) as f:
|
||||
pid = int(f.read())
|
||||
if pid != my_pid:
|
||||
try:
|
||||
process = psutil.Process(pid=pid)
|
||||
ps_name = process.name()
|
||||
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
else:
|
||||
if "gns3" in ps_name or "python" in ps_name:
|
||||
# Process run under the same user id
|
||||
if sys.platform.startswith("win") or process.uids()[0] == os.getuid():
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except (OSError, ValueError) as e:
|
||||
log.critical("Can't read pid file %s: %s", pid_path, str(e))
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(pid_path, 'w+') as f:
|
||||
f.write(str(my_pid))
|
||||
except OSError as e:
|
||||
log.critical("Can't write pid file %s: %s", pid_path, str(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import os
|
||||
import sys
|
||||
import configparser
|
||||
from gns3.qt import QtCore
|
||||
|
||||
|
||||
import logging
|
||||
@@ -62,7 +61,7 @@ class LocalServerConfig:
|
||||
|
||||
try:
|
||||
self._config.read(self._config_file, encoding="utf-8")
|
||||
except (OSError, configparser.Error) as e:
|
||||
except (OSError, configparser.Error, UnicodeEncodeError, UnicodeDecodeError) as e:
|
||||
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
|
||||
|
||||
def writeConfig(self):
|
||||
|
||||
38
gns3/main.py
38
gns3/main.py
@@ -25,7 +25,7 @@ try:
|
||||
if gns3.update_manager.UpdateManager().installDownloadedUpdates():
|
||||
print("Update installed restart the application")
|
||||
python = sys.executable
|
||||
os.execl(python, python, * sys.argv)
|
||||
os.execl(python, *sys.argv)
|
||||
except Exception as e:
|
||||
print("Fail update installation: {}".format(str(e)))
|
||||
|
||||
@@ -45,6 +45,7 @@ import time
|
||||
import locale
|
||||
import argparse
|
||||
import signal
|
||||
import re
|
||||
|
||||
try:
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets, DEFAULT_BINDING
|
||||
@@ -55,11 +56,12 @@ from gns3.main_window import MainWindow
|
||||
from gns3.logger import init_logger
|
||||
from gns3.crash_report import CrashReport
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.application import Application
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
from gns3.version import __version__
|
||||
|
||||
|
||||
@@ -144,6 +146,7 @@ def main():
|
||||
os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")
|
||||
|
||||
if options.project:
|
||||
options.project = os.path.abspath(options.project)
|
||||
os.chdir(frozen_dir)
|
||||
|
||||
def exceptionHook(exception, value, tb):
|
||||
@@ -153,7 +156,7 @@ def main():
|
||||
|
||||
lines = traceback.format_exception(exception, value, tb)
|
||||
print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
|
||||
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
|
||||
print("\nPLEASE REPORT ON https://www.gns3.com\n")
|
||||
print("".join(lines))
|
||||
try:
|
||||
curdate = time.strftime("%d %b %Y %H:%M:%S")
|
||||
@@ -186,10 +189,7 @@ def main():
|
||||
raise SystemExit("Python 3.4 or higher is required")
|
||||
|
||||
def version(version_string):
|
||||
return [int(i) for i in version_string.split('.')]
|
||||
|
||||
if version(QtCore.QT_VERSION_STR) < version("4.6"):
|
||||
raise SystemExit("Requirement is Qt version 4.6 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
return [int(i) for i in re.split(r'[^0-9]', version_string)]
|
||||
|
||||
# 4.8.3 because of QSettings (http://pyqt.sourceforge.net/Docs/PyQt4/pyqt_qsettings.html)
|
||||
if DEFAULT_BINDING == "PyQt4" and version(QtCore.BINDING_VERSION_STR) < version("4.8.3"):
|
||||
@@ -198,6 +198,10 @@ def main():
|
||||
if DEFAULT_BINDING == "PyQt5" and version(QtCore.BINDING_VERSION_STR) < version("5.0.0"):
|
||||
raise SystemExit("Requirement is PyQt5 version 5.0.0 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
|
||||
|
||||
import psutil
|
||||
if version(psutil.__version__) < version("2.2.1"):
|
||||
raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))
|
||||
|
||||
# check for the correct locale
|
||||
# (UNIX/Linux only)
|
||||
locale_check()
|
||||
@@ -228,13 +232,8 @@ def main():
|
||||
except win32console.error as e:
|
||||
print("warning: could not allocate console: {}".format(e))
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
# this info is necessary for QSettings
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
app.setApplicationVersion(__version__)
|
||||
global app
|
||||
app = Application(sys.argv)
|
||||
|
||||
# save client logging info to a file
|
||||
logfile = os.path.join(LocalConfig.configDirectory(), "gns3_gui.log")
|
||||
@@ -248,7 +247,14 @@ def main():
|
||||
# update the exception file path to have it in the same directory as the settings file.
|
||||
exception_file_path = os.path.join(LocalConfig.configDirectory(), exception_file_path)
|
||||
|
||||
mainwindow = MainWindow(options.project)
|
||||
global mainwindow
|
||||
mainwindow = MainWindow()
|
||||
|
||||
# On OSX we can receive the file to open from a system event
|
||||
# loadPath is smart and will load only if a path is present
|
||||
mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(app.open_file_at_startup))
|
||||
mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(options.project))
|
||||
app.file_open_signal.connect(lambda path: mainwindow.loadPath(path))
|
||||
|
||||
# Manage Ctrl + C or kill command
|
||||
def sigint_handler(*args):
|
||||
@@ -259,9 +265,9 @@ def main():
|
||||
signal.signal(signal.SIGTERM, sigint_handler)
|
||||
|
||||
mainwindow.show()
|
||||
|
||||
exit_code = app.exec_()
|
||||
delattr(MainWindow, "_instance")
|
||||
app.deleteLater()
|
||||
|
||||
# We force a full garbage collect before exit
|
||||
# for unknow reason otherwise Qt Segfault on OSX in some
|
||||
|
||||
@@ -42,6 +42,8 @@ from .dialogs.about_dialog import AboutDialog
|
||||
from .dialogs.new_project_dialog import NewProjectDialog
|
||||
from .dialogs.preferences_dialog import PreferencesDialog
|
||||
from .dialogs.snapshots_dialog import SnapshotsDialog
|
||||
from .dialogs.export_debug_dialog import ExportDebugDialog
|
||||
from .dialogs.doctor_dialog import DoctorDialog
|
||||
from .dialogs.setup_wizard import SetupWizard
|
||||
from .settings import GENERAL_SETTINGS
|
||||
from .utils.progress_dialog import ProgressDialog
|
||||
@@ -60,9 +62,10 @@ from .utils.download_project import DownloadProjectWorker
|
||||
from .project import Project
|
||||
from .http_client import HTTPClient
|
||||
from .progress import Progress
|
||||
from .licence import checkLicence
|
||||
from .image_manager import ImageManager
|
||||
from .update_manager import UpdateManager
|
||||
from .utils.analytics import AnalyticsClient
|
||||
from .dialogs.appliance_wizard import ApplianceWizard
|
||||
from .registry.appliance import ApplianceError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,12 +79,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
|
||||
# signal to tell the view if the user is adding a link or not
|
||||
adding_link_signal = QtCore.Signal(bool)
|
||||
adding_link_signal = QtCore.pyqtSignal(bool)
|
||||
|
||||
# signal to tell a new project was created
|
||||
project_new_signal = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, project=None, parent=None):
|
||||
# signal to tell the windows is ready to load his first project
|
||||
ready_signal = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
@@ -92,25 +98,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
self._project = None
|
||||
self._createTemporaryProject()
|
||||
self._project_from_cmdline = project
|
||||
self._first_file_load = True
|
||||
self._loadSettings()
|
||||
self._connections()
|
||||
self._ignore_unsaved_state = False
|
||||
self._max_recent_files = 5
|
||||
self._soft_exit = True
|
||||
self._project_dialog = None
|
||||
self._recent_file_actions = []
|
||||
self._start_time = time.time()
|
||||
local_config = LocalConfig.instance()
|
||||
local_config.config_changed_signal.connect(self._localConfigChangedSlot)
|
||||
|
||||
self._uiNewsDockWidget = None
|
||||
if not checkLicence():
|
||||
try:
|
||||
from .news_dock_widget import NewsDockWidget
|
||||
self._uiNewsDockWidget = NewsDockWidget(self)
|
||||
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.BottomDockWidgetArea), self._uiNewsDockWidget)
|
||||
except ImportError:
|
||||
pass
|
||||
self._local_config_timer = QtCore.QTimer(self)
|
||||
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
|
||||
self._local_config_timer.start(1000) # milliseconds
|
||||
self._analytics_client = AnalyticsClient()
|
||||
|
||||
# restore the geometry and state of the main window.
|
||||
self.restoreGeometry(QtCore.QByteArray().fromBase64(self._settings["geometry"].encode()))
|
||||
@@ -118,6 +120,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
# populate the view -> docks menu
|
||||
self.uiDocksMenu.addAction(self.uiTopologySummaryDockWidget.toggleViewAction())
|
||||
self.uiDocksMenu.addAction(self.uiServerSummaryDockWidget.toggleViewAction())
|
||||
self.uiDocksMenu.addAction(self.uiConsoleDockWidget.toggleViewAction())
|
||||
self.uiDocksMenu.addAction(self.uiNodesDockWidget.toggleViewAction())
|
||||
|
||||
@@ -144,6 +147,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# restore the style
|
||||
self._setStyle(self._settings.get("style"))
|
||||
|
||||
|
||||
self.setWindowTitle("[*] GNS3")
|
||||
|
||||
# load initial stuff once the event loop isn't busy
|
||||
self.run_later(0, self.startupLoading)
|
||||
|
||||
@@ -196,6 +202,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# file menu connections
|
||||
self.uiNewProjectAction.triggered.connect(self._newProjectActionSlot)
|
||||
self.uiOpenProjectAction.triggered.connect(self.openProjectActionSlot)
|
||||
self.uiOpenApplianceAction.triggered.connect(self.openApplianceActionSlot)
|
||||
self.uiSaveProjectAction.triggered.connect(self._saveProjectActionSlot)
|
||||
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
|
||||
self.uiDownloadRemoteProject.triggered.connect(self._downloadRemoteProjectActionSlot)
|
||||
@@ -216,7 +223,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiFitInViewAction.triggered.connect(self._fitInViewActionSlot)
|
||||
self.uiShowLayersAction.triggered.connect(self._showLayersActionSlot)
|
||||
self.uiResetPortLabelsAction.triggered.connect(self._resetPortLabelsActionSlot)
|
||||
self.uiShowNamesAction.triggered.connect(self._showNamesActionSlot)
|
||||
self.uiShowPortNamesAction.triggered.connect(self._showPortNamesActionSlot)
|
||||
|
||||
# control menu connections
|
||||
@@ -243,10 +249,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiOnlineHelpAction.triggered.connect(self._onlineHelpActionSlot)
|
||||
self.uiCheckForUpdateAction.triggered.connect(self._checkForUpdateActionSlot)
|
||||
self.uiSetupWizard.triggered.connect(self._setupWizardActionSlot)
|
||||
self.uiGettingStartedAction.triggered.connect(self._gettingStartedActionSlot)
|
||||
self.uiLabInstructionsAction.triggered.connect(self._labInstructionsActionSlot)
|
||||
self.uiAboutQtAction.triggered.connect(self._aboutQtActionSlot)
|
||||
self.uiAboutAction.triggered.connect(self._aboutActionSlot)
|
||||
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
|
||||
self.uiDoctorAction.triggered.connect(self._doctorSlot)
|
||||
self.uiIOUVMConverterAction.triggered.connect(self._IOUVMConverterActionSlot)
|
||||
|
||||
# browsers tool bar connections
|
||||
@@ -263,6 +270,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# project
|
||||
self.project_new_signal.connect(self.project_created)
|
||||
|
||||
self.ready_signal.connect(self._readySlot)
|
||||
|
||||
def project(self):
|
||||
"""
|
||||
Return current project
|
||||
@@ -280,33 +289,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._project = project
|
||||
self._setCurrentFile(project.topologyFile())
|
||||
|
||||
def telnetConsoleCommand(self):
|
||||
"""
|
||||
Returns the Telnet console command line.
|
||||
|
||||
:returns: command (string)
|
||||
"""
|
||||
|
||||
return self._settings["telnet_console_command"]
|
||||
|
||||
def serialConsoleCommand(self):
|
||||
"""
|
||||
Returns the Serial console command line.
|
||||
|
||||
:returns: command (string)
|
||||
"""
|
||||
|
||||
return self._settings["serial_console_command"]
|
||||
|
||||
def vncConsoleCommand(self):
|
||||
"""
|
||||
Returns the VNC command line.
|
||||
|
||||
:returns: command (string)
|
||||
"""
|
||||
|
||||
return self._settings["vnc_console_command"]
|
||||
|
||||
def setUnsavedState(self):
|
||||
"""
|
||||
Sets the project in a unsaved state.
|
||||
@@ -337,9 +319,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiGraphicsView.reset()
|
||||
# create the destination directory for project files
|
||||
try:
|
||||
os.makedirs(new_project_settings["project_files_dir"])
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(new_project_settings["project_files_dir"], exist_ok=True)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "New project", "Could not create project files directory {}: {}".format(new_project_settings["project_files_dir"], e))
|
||||
self._createTemporaryProject()
|
||||
@@ -361,19 +341,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
|
||||
if self.checkForUnsavedChanges():
|
||||
project_dialog = NewProjectDialog(self)
|
||||
project_dialog.show()
|
||||
create_new_project = project_dialog.exec_()
|
||||
self._project_dialog = NewProjectDialog(self)
|
||||
self._project_dialog.show()
|
||||
create_new_project = self._project_dialog.exec_()
|
||||
# Close the device dock so it repopulates. Done in case switching
|
||||
# between cloud and local.
|
||||
self.uiNodesDockWidget.setVisible(False)
|
||||
self.uiNodesDockWidget.setWindowTitle("")
|
||||
|
||||
if create_new_project:
|
||||
new_project_settings = project_dialog.getNewProjectSettings()
|
||||
new_project_settings = self._project_dialog.getNewProjectSettings()
|
||||
self._createNewProject(new_project_settings)
|
||||
else:
|
||||
self._createTemporaryProject()
|
||||
self._project_dialog = None
|
||||
|
||||
def _IOUVMConverterActionSlot(self):
|
||||
command = shutil.which("gns3-iouvm-converter")
|
||||
@@ -385,6 +366,22 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 IOU VM Converter", "Error when running gns3-iouvm-converter {}".format(e))
|
||||
|
||||
def openApplianceActionSlot(self):
|
||||
"""
|
||||
Slot called to open an appliance.
|
||||
"""
|
||||
|
||||
directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
|
||||
if len(directory) == 0:
|
||||
directory = self.projectsDirPath()
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Open appliance",
|
||||
directory,
|
||||
"All files (*.*);;GNS3 appliance (*.gns3a)",
|
||||
"GNS3 appliance (*.gns3a)")
|
||||
if path:
|
||||
self.loadPath(path)
|
||||
|
||||
def openProjectActionSlot(self):
|
||||
"""
|
||||
Slot called to open a project.
|
||||
@@ -396,7 +393,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"All files (*.*);;GNS3 project files (*.gns3);;NET files (*.net)",
|
||||
"GNS3 project files (*.gns3)")
|
||||
if path:
|
||||
self._loadPath(path)
|
||||
self.loadPath(path)
|
||||
|
||||
def openRecentFileSlot(self):
|
||||
"""
|
||||
@@ -409,7 +406,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if not os.path.isfile(path):
|
||||
QtWidgets.QMessageBox.critical(self, "Recent file", "{}: no such file".format(path))
|
||||
return
|
||||
self._loadPath(path)
|
||||
self.loadPath(path)
|
||||
|
||||
def loadSnapshot(self, path):
|
||||
"""Loads a snapshot"""
|
||||
@@ -418,18 +415,38 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
|
||||
self._project.close()
|
||||
|
||||
def _loadPath(self, path):
|
||||
def loadPath(self, path):
|
||||
"""Open a file and close the previous project"""
|
||||
|
||||
if path and self.checkForUnsavedChanges():
|
||||
self._open_project_path = path
|
||||
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
|
||||
self._project.close()
|
||||
if path:
|
||||
if self._first_file_load is True:
|
||||
self._first_file_load = False
|
||||
time.sleep(0.5) # give some time to the server to initialize
|
||||
|
||||
if self._project_dialog:
|
||||
self._project_dialog.reject()
|
||||
self._project_dialog = None
|
||||
|
||||
if path.endswith(".gns3a"):
|
||||
try:
|
||||
self._appliance_wizard = ApplianceWizard(self, path)
|
||||
except ApplianceError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Appliance", "Error when importing appliance {}: {}".format(path, str(e)))
|
||||
return
|
||||
self._appliance_wizard.show()
|
||||
self._appliance_wizard.exec_()
|
||||
elif self.checkForUnsavedChanges():
|
||||
self._open_project_path = path
|
||||
if self._project.closed():
|
||||
self._projectClosedContinueLoadPath()
|
||||
else:
|
||||
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
|
||||
self._project.close()
|
||||
|
||||
def _projectClosedContinueLoadPath(self):
|
||||
|
||||
path = self._open_project_path
|
||||
if self.loadProject(path):
|
||||
if self._loadProject(path):
|
||||
self.project_new_signal.emit(path)
|
||||
|
||||
def _saveProjectActionSlot(self):
|
||||
@@ -519,7 +536,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
|
||||
# supported image file formats
|
||||
file_formats = "PNG File (*.png);;JPG File (*.jpeg *.jpg);;BMP File (*.bmp);;XPM File (*.xpm *.xbm);;PPM File (*.ppm);;TIFF File (*.tiff)"
|
||||
file_formats = "PNG File (*.png);;JPG File (*.jpeg *.jpg);;BMP File (*.bmp);;XPM File (*.xpm *.xbm);;PPM File (*.ppm);;TIFF File (*.tiff);; GIF File (*.gif)"
|
||||
path, selected_filter = QtWidgets.QFileDialog.getSaveFileName(self, "Screenshot", self._screenshots_dir, file_formats)
|
||||
if not path:
|
||||
return
|
||||
@@ -635,16 +652,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot called to reset the port labels on the scene.
|
||||
"""
|
||||
|
||||
# TODO: reset port labels
|
||||
pass
|
||||
|
||||
def _showNamesActionSlot(self):
|
||||
"""
|
||||
Slot called to show the node names on the scene.
|
||||
"""
|
||||
|
||||
# TODO: show/hide node names
|
||||
pass
|
||||
for item in self.uiGraphicsView.scene().items():
|
||||
if isinstance(item, LinkItem):
|
||||
item.resetPortLabels()
|
||||
item.adjust()
|
||||
|
||||
def _showPortNamesActionSlot(self):
|
||||
"""
|
||||
@@ -727,9 +738,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
try:
|
||||
working_dir = os.path.join(self._project.filesDir(), "project-files", "vpcs", "multi-host")
|
||||
os.makedirs(working_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(working_dir, exist_ok=True)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "VPCS", "Could not create the VPCS working directory: {}".format(e))
|
||||
return
|
||||
@@ -758,12 +767,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot called when inserting an image on the scene.
|
||||
"""
|
||||
|
||||
if self._project.filesDir() is None:
|
||||
files_dir = self._project.filesDir()
|
||||
if files_dir is None:
|
||||
QtWidgets.QMessageBox.critical(self, "Image", "Please create a node first")
|
||||
return
|
||||
|
||||
# supported image file formats
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
|
||||
file_formats = "Image files (*.svg *.bmp *.jpeg *.jpg *.gif *.pbm *.pgm *.png *.ppm *.xbm *.xpm);;All files (*.*)"
|
||||
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Image", self._pictures_dir, file_formats)
|
||||
if not path:
|
||||
@@ -775,7 +785,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
QtWidgets.QMessageBox.critical(self, "Image", "Image file format not supported")
|
||||
return
|
||||
|
||||
destination_dir = os.path.join(self._project.filesDir(), "project-files", "images")
|
||||
destination_dir = os.path.join(files_dir, "project-files", "images")
|
||||
try:
|
||||
os.makedirs(destination_dir, exist_ok=True)
|
||||
except OSError as e:
|
||||
@@ -819,7 +829,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot to launch a browser pointing to the documentation page.
|
||||
"""
|
||||
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://www.gns3.net/documentation/"))
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://gns3.com/support/docs"))
|
||||
|
||||
def _checkForUpdateActionSlot(self, silent=False):
|
||||
"""
|
||||
@@ -841,25 +851,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
setup_wizard.show()
|
||||
setup_wizard.exec_()
|
||||
|
||||
def _gettingStartedActionSlot(self, auto=False):
|
||||
"""
|
||||
Slot to open the news dialog.
|
||||
"""
|
||||
|
||||
try:
|
||||
# QtWebKit which is used by GettingStartedDialog is not installed
|
||||
# by default on FreeBSD, Solaris and possibly other systems.
|
||||
from .dialogs.getting_started_dialog import GettingStartedDialog
|
||||
except ImportError as e:
|
||||
print(e)
|
||||
return
|
||||
|
||||
dialog = GettingStartedDialog(self)
|
||||
if auto is True and dialog.showit() is False:
|
||||
return
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _labInstructionsActionSlot(self, silent=False):
|
||||
"""
|
||||
Slot to open lab instructions.
|
||||
@@ -895,7 +886,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _showNodesDockWidget(self, title, category=Node.routers):
|
||||
def _exportDebugInformationSlot(self):
|
||||
"""
|
||||
Slot to display a window for exporting debug information
|
||||
"""
|
||||
|
||||
dialog = ExportDebugDialog(self, self._project)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _doctorSlot(self):
|
||||
"""
|
||||
Slot to display a window for exporting debug information
|
||||
"""
|
||||
|
||||
dialog = DoctorDialog(self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def _showNodesDockWidget(self, title, category):
|
||||
"""
|
||||
Makes the NodesDockWidget appear with the appropriate title and the devices
|
||||
from the specified category listed.
|
||||
@@ -954,7 +963,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Slot to browse all the devices.
|
||||
"""
|
||||
|
||||
self._showNodesDockWidget("All devices")
|
||||
self._showNodesDockWidget("All devices", None)
|
||||
|
||||
def _addLinkActionSlot(self):
|
||||
"""
|
||||
@@ -1005,31 +1014,29 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
progress.setCancelButtonText("Force quit")
|
||||
|
||||
log.debug("Close the main Windows")
|
||||
|
||||
self._analytics_client.sendScreenView("Main Window", session_start=False)
|
||||
|
||||
servers = Servers.instance()
|
||||
if self._project.closed() and not servers.localServerIsRunning():
|
||||
if self._project.closed():
|
||||
log.debug("Project is closed killing server and closing main windows")
|
||||
self._finish_application_closing(close_windows=False)
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
elif not self._soft_exit or self.checkForUnsavedChanges():
|
||||
log.debug("Project is not closed asking for project closing")
|
||||
self._project.project_closed_signal.connect(self._finish_application_closing)
|
||||
if servers.localServerIsRunning():
|
||||
self._project.close(local_server_shutdown=True)
|
||||
else:
|
||||
self._project.close(local_server_shutdown=False)
|
||||
|
||||
if self._project.closed() and not servers.localServerIsRunning():
|
||||
log.debug("Disconnect all servers")
|
||||
servers.disconnectAllServers()
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
else:
|
||||
event.ignore()
|
||||
self._project.close(local_server_shutdown=True)
|
||||
event.ignore()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def _finish_application_closing(self):
|
||||
def _finish_application_closing(self, close_windows=True):
|
||||
"""
|
||||
Handles the event when the main window is closed.
|
||||
And project closed.
|
||||
|
||||
:params closing: True the windows is currently closing do not try to reclose it
|
||||
"""
|
||||
|
||||
log.debug("_finish_application_closing")
|
||||
@@ -1045,7 +1052,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
time_spent = "{:.0f}".format(time.time() - self._start_time)
|
||||
log.debug("Time spend in the software is {}".format(time_spent))
|
||||
self.close()
|
||||
|
||||
if close_windows:
|
||||
self.close()
|
||||
|
||||
def checkForUnsavedChanges(self):
|
||||
"""
|
||||
@@ -1096,44 +1105,33 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
# restore the style
|
||||
self._setStyle(self._settings.get("style"))
|
||||
|
||||
# show the news dock widget
|
||||
if self._uiNewsDockWidget and not self._uiNewsDockWidget.isVisible():
|
||||
self.addDockWidget(QtCore.Qt.DockWidgetArea(QtCore.Qt.BottomDockWidgetArea), self._uiNewsDockWidget)
|
||||
|
||||
# FIXME: do something with getting started dialog
|
||||
# self._gettingStartedActionSlot(auto=True)
|
||||
|
||||
servers = Servers.instance()
|
||||
|
||||
# start the GNS3 VM
|
||||
gns3_vm = GNS3VM.instance()
|
||||
if gns3_vm.autoStart() and not gns3_vm.isRunning():
|
||||
servers.initVMServer()
|
||||
worker = WaitForVMWorker()
|
||||
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self)
|
||||
progress_dialog = ProgressDialog(worker, "GNS3 VM", "Starting the GNS3 VM...", "Cancel", busy=True, parent=self, delay=5)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_():
|
||||
gns3_vm.adjustLocalServerIP()
|
||||
|
||||
# start and connect to the local server
|
||||
server = servers.localServer()
|
||||
if servers.localServerAutoStart():
|
||||
if server.isLocalServerRunning():
|
||||
log.info("Connecting to a server already running on this host")
|
||||
else:
|
||||
if servers.initLocalServer() and servers.startLocalServer():
|
||||
worker = WaitForConnectionWorker(server.host(), server.port())
|
||||
progress_dialog = ProgressDialog(worker,
|
||||
"Local server",
|
||||
"Connecting to server {} on port {}...".format(server.host(), server.port()),
|
||||
"Cancel", busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
else:
|
||||
if servers.shouldLocalServerAutoStart():
|
||||
if not servers.localServerAutoStart():
|
||||
QtWidgets.QMessageBox.critical(self, "Local server", "Could not start the local server process: {}".format(servers.localServerPath()))
|
||||
return
|
||||
|
||||
worker = WaitForConnectionWorker(server.host(), server.port())
|
||||
progress_dialog = ProgressDialog(worker,
|
||||
"Local server",
|
||||
"Connecting to server {} on port {}...".format(server.host(), server.port()),
|
||||
"Cancel", busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if not progress_dialog.exec_():
|
||||
return
|
||||
|
||||
# show the setup wizard
|
||||
with Progress.instance().context(min_duration=0):
|
||||
setup_wizard = SetupWizard(self)
|
||||
@@ -1141,17 +1139,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
setup_wizard.show()
|
||||
setup_wizard.exec_()
|
||||
|
||||
self._analytics_client.sendScreenView("Main Window")
|
||||
|
||||
self._createTemporaryProject()
|
||||
if self._project_from_cmdline:
|
||||
time.sleep(0.5) # give some time to the server to initialize
|
||||
self._loadPath(self._project_from_cmdline)
|
||||
elif self._settings["auto_launch_project_dialog"]:
|
||||
project_dialog = NewProjectDialog(self, showed_from_startup=True)
|
||||
project_dialog.show()
|
||||
create_new_project = project_dialog.exec_()
|
||||
if create_new_project:
|
||||
new_project_settings = project_dialog.getNewProjectSettings()
|
||||
self._createNewProject(new_project_settings)
|
||||
|
||||
self.ready_signal.emit()
|
||||
|
||||
if self._settings["check_for_update"]:
|
||||
# automatic check for update every week (604800 seconds)
|
||||
@@ -1162,6 +1154,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._settings["last_check_for_update"] = current_epoch
|
||||
self.setSettings(self._settings)
|
||||
|
||||
def _readySlot(self):
|
||||
"""
|
||||
Called when the application is ready to load a project
|
||||
"""
|
||||
if self._settings["auto_launch_project_dialog"] and self._first_file_load:
|
||||
self._project_dialog = NewProjectDialog(self, showed_from_startup=True)
|
||||
self._project_dialog.accepted.connect(self._newProjectDialodAcceptedSlot)
|
||||
self._project_dialog.show()
|
||||
|
||||
def _newProjectDialodAcceptedSlot(self):
|
||||
"""
|
||||
Called when user accept the new project dialog
|
||||
"""
|
||||
if self._project_dialog:
|
||||
new_project_settings = self._project_dialog.getNewProjectSettings()
|
||||
self._createNewProject(new_project_settings)
|
||||
self._project_dialog = None
|
||||
|
||||
def _running_nodes(self):
|
||||
"""
|
||||
:returns: Return the list of running nodes
|
||||
@@ -1173,6 +1183,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
running_nodes.append(node.name())
|
||||
return running_nodes
|
||||
|
||||
def _isTopologyOnRemoteServer(self):
|
||||
"""
|
||||
:returns: Boolean True if topology run on a remote server
|
||||
"""
|
||||
topology = Topology.instance()
|
||||
running_nodes = []
|
||||
for node in topology.nodes():
|
||||
if not node.server().isLocal():
|
||||
return True
|
||||
return False
|
||||
|
||||
def saveProjectAs(self):
|
||||
"""
|
||||
Saves a project to another location/name.
|
||||
@@ -1190,12 +1211,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
MessageBox(self, "Save project", "Please stop the following nodes before saving the topology to a new location", nodes)
|
||||
return
|
||||
|
||||
if self._isTopologyOnRemoteServer() and not self._project.temporary():
|
||||
MessageBox(self, "Save project", "You can not use the save as function on a remote project for the moment.")
|
||||
return
|
||||
|
||||
if self._project.temporary():
|
||||
default_project_name = "untitled"
|
||||
else:
|
||||
default_project_name = os.path.basename(self._project.topologyFile())
|
||||
if default_project_name.endswith(".gns3"):
|
||||
default_project_name = default_project_name[:-5]
|
||||
default_project_name = self._project.name()
|
||||
|
||||
projects_dir_path = os.path.normpath(os.path.expanduser(self.projectsDirPath()))
|
||||
file_dialog = QtWidgets.QFileDialog(self)
|
||||
@@ -1217,9 +1240,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
# create the destination directory for project files
|
||||
try:
|
||||
os.makedirs(project_dir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(project_dir, exist_ok=True)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Save project", "Could not create project directory {}: {}".format(project_dir, e))
|
||||
return
|
||||
@@ -1256,7 +1277,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
os.remove(old_topology_file_path)
|
||||
except OSError as e:
|
||||
MessageBox(self, "Save project", "Errors detected while saving the project", str(e), icon=QtWidgets.QMessageBox.Warning)
|
||||
return self._loadPath(topology_file_path)
|
||||
return self.loadPath(topology_file_path)
|
||||
|
||||
def saveProject(self, path, random_id=False):
|
||||
"""
|
||||
@@ -1284,6 +1305,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiStatusBar.showMessage("Project saved to {}".format(path), 2000)
|
||||
self._project.setTopologyFile(path)
|
||||
self._setCurrentFile(path)
|
||||
|
||||
self._analytics_client.sendScreenView("Main Window")
|
||||
|
||||
return True
|
||||
|
||||
def _convertOldProject(self, path):
|
||||
@@ -1298,6 +1322,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
except ImportError as e:
|
||||
log.error("GNS3 converter is missing: {}".format(str(e)))
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Please install gns3-converter in order to open old ini-style GNS3 projects")
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -1313,6 +1338,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
project_name = text
|
||||
project_dir = os.path.join(self.projectsDirPath(), project_name)
|
||||
else:
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
for snapshot_def in get_snapshots(path):
|
||||
@@ -1322,19 +1348,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
do_conversion(topology_def, project_name, project_dir, quiet=True)
|
||||
except ConvertError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Could not convert {}: {}".format(path, e))
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
except Exception:
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
tb = "".join(lines)
|
||||
MessageBox(self, "GNS3 converter", "Unexpected exception while converting {}".format(path), details=tb)
|
||||
self._createTemporaryProject()
|
||||
return
|
||||
|
||||
QtWidgets.QMessageBox.information(self, "GNS3 converter", "Your project has been converted to a new format and can be found in: {}".format(project_dir))
|
||||
project_path = os.path.join(project_dir, project_name + ".gns3")
|
||||
self.loadProject(project_path)
|
||||
self._loadProject(project_path)
|
||||
|
||||
def loadProject(self, path):
|
||||
def _loadProject(self, path):
|
||||
"""
|
||||
Loads a project into GNS3.
|
||||
|
||||
@@ -1351,9 +1379,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if extension == ".net":
|
||||
self._convertOldProject(path)
|
||||
return
|
||||
|
||||
topology.loadFile(path, self._project)
|
||||
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Load", "Could not load project {}: {}".format(os.path.basename(path), e))
|
||||
# log.error("exception {type}".format(type=type(e)), exc_info=1)
|
||||
@@ -1403,9 +1429,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
if not path:
|
||||
self.setWindowFilePath("Unsaved project")
|
||||
self.setWindowTitle("Unsaved project[*] - GNS3")
|
||||
else:
|
||||
path = os.path.normpath(path)
|
||||
self.setWindowFilePath(path)
|
||||
self.setWindowTitle("{path}[*] - GNS3".format(path=os.path.basename(path)))
|
||||
self._updateRecentFileSettings(path)
|
||||
self._updateRecentFileActions()
|
||||
|
||||
@@ -1444,12 +1472,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
index = 0
|
||||
size = len(self._settings["recent_files"])
|
||||
for file_path in self._settings["recent_files"]:
|
||||
if file_path and os.path.exists(file_path):
|
||||
action = self._recent_file_actions[index]
|
||||
action.setText(" {}. {}".format(index + 1, os.path.basename(file_path)))
|
||||
action.setData(file_path)
|
||||
action.setVisible(True)
|
||||
index += 1
|
||||
try:
|
||||
if file_path and os.path.exists(file_path):
|
||||
action = self._recent_file_actions[index]
|
||||
action.setText(" {}. {}".format(index + 1, os.path.basename(file_path)))
|
||||
action.setData(file_path)
|
||||
action.setVisible(True)
|
||||
index += 1
|
||||
# We can have this error if user save a file with unicode char
|
||||
# and change his system locale.
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
for index in range(size + 1, self._max_recent_files):
|
||||
self._recent_file_actions[index].setVisible(False)
|
||||
@@ -1550,6 +1583,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.setStyleSheet("")
|
||||
self.uiNewProjectAction.setIcon(QtGui.QIcon(":/icons/new-project.svg"))
|
||||
self.uiOpenProjectAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
|
||||
self.uiOpenApplianceAction.setIcon(QtGui.QIcon(":/icons/open.svg"))
|
||||
self.uiSaveProjectAction.setIcon(QtGui.QIcon(":/icons/save.svg"))
|
||||
self.uiSaveProjectAsAction.setIcon(QtGui.QIcon(":/icons/save-as.svg"))
|
||||
self.uiImportExportConfigsAction.setIcon(QtGui.QIcon(":/icons/import_export_configs.svg"))
|
||||
@@ -1586,6 +1620,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.setStyleSheet("")
|
||||
self.uiNewProjectAction.setIcon(self._getStyleIcon(":/classic_icons/new-project.svg", ":/classic_icons/new-project-hover.svg"))
|
||||
self.uiOpenProjectAction.setIcon(self._getStyleIcon(":/classic_icons/open.svg", ":/classic_icons/open-hover.svg"))
|
||||
self.uiOpenApplianceAction.setIcon(self._getStyleIcon(":/classic_icons/open.svg", ":/classic_icons/open-hover.svg"))
|
||||
self.uiSaveProjectAction.setIcon(self._getStyleIcon(":/classic_icons/save-project.svg", ":/classic_icons/save-project-hover.svg"))
|
||||
self.uiSaveProjectAsAction.setIcon(self._getStyleIcon(":/classic_icons/save-as-project.svg", ":/classic_icons/save-as-project-hover.svg"))
|
||||
self.uiImportExportConfigsAction.setIcon(self._getStyleIcon(":/classic_icons/import_export_configs.svg", ":/classic_icons/import_export_configs-hover.svg"))
|
||||
@@ -1645,6 +1680,7 @@ QComboBox QAbstractItemView {background-color: #dedede}
|
||||
self.setStyleSheet(style)
|
||||
self.uiNewProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/new-project.svg", ":/charcoal_icons/new-project-hover.svg"))
|
||||
self.uiOpenProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/open.svg", ":/charcoal_icons/open-hover.svg"))
|
||||
self.uiOpenApplianceAction.setIcon(self._getStyleIcon(":/charcoal_icons/open.svg", ":/charcoal_icons/open-hover.svg"))
|
||||
self.uiSaveProjectAction.setIcon(self._getStyleIcon(":/charcoal_icons/save-project.svg", ":/charcoal_icons/save-project-hover.svg"))
|
||||
self.uiSaveProjectAsAction.setIcon(self._getStyleIcon(":/charcoal_icons/save-as-project.svg", ":/charcoal_icons/save-as-project-hover.svg"))
|
||||
self.uiImportExportConfigsAction.setIcon(self._getStyleIcon(":/charcoal_icons/import_export_configs.svg", ":/charcoal_icons/import_export_configs-hover.svg"))
|
||||
|
||||
@@ -20,9 +20,7 @@ Built-in module implementation.
|
||||
"""
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.servers import Servers
|
||||
from ..module import Module
|
||||
from ..module_error import ModuleError
|
||||
from .cloud import Cloud
|
||||
from .host import Host
|
||||
|
||||
@@ -72,61 +70,6 @@ class Builtin(Module):
|
||||
log.info("Built-in module reset")
|
||||
self._nodes.clear()
|
||||
|
||||
def allocateServer(self, node_class):
|
||||
"""
|
||||
Allocates a server.
|
||||
|
||||
:param node_class: Node object
|
||||
|
||||
:returns: allocated server (HTTPClient instance)
|
||||
"""
|
||||
|
||||
# check all other modules to find if they
|
||||
# are using a local server
|
||||
using_local_server = []
|
||||
from gns3.modules import MODULES
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
if instance != self:
|
||||
module_settings = instance.settings()
|
||||
if "use_local_server" in module_settings:
|
||||
using_local_server.append(module_settings["use_local_server"])
|
||||
|
||||
# allocate a server for the node
|
||||
servers = Servers.instance()
|
||||
local_server = servers.localServer()
|
||||
remote_servers = servers.remoteServers()
|
||||
gns3_vm = Servers.instance().vmServer()
|
||||
|
||||
if not all(using_local_server) and (gns3_vm or len(remote_servers)):
|
||||
# a module is not using a local server
|
||||
|
||||
server_list = ["Local server ({})".format(local_server.url())]
|
||||
if gns3_vm:
|
||||
server_list.append("GNS3 VM ({})".format(gns3_vm.url()))
|
||||
if len(remote_servers):
|
||||
if True not in using_local_server and len(remote_servers) == 1:
|
||||
# no module is using a local server and there is only one
|
||||
# remote server available, so no need to ask the user.
|
||||
return next(iter(servers))
|
||||
for remote_server in remote_servers:
|
||||
server_list.append("{}".format(remote_server))
|
||||
|
||||
# TODO: move this to graphics_view
|
||||
from gns3.main_window import MainWindow
|
||||
mainwindow = MainWindow.instance()
|
||||
(selection, ok) = QtWidgets.QInputDialog.getItem(mainwindow, "Server", "Please choose a server", server_list, 0, False)
|
||||
if ok:
|
||||
if selection.startswith("Local server"):
|
||||
return local_server
|
||||
elif selection.startswith("GNS3 VM"):
|
||||
return gns3_vm
|
||||
else:
|
||||
return remote_servers[selection]
|
||||
else:
|
||||
raise ModuleError("Please select a server")
|
||||
return local_server
|
||||
|
||||
def createNode(self, node_class, server, project):
|
||||
"""
|
||||
Creates a new node.
|
||||
|
||||
@@ -20,7 +20,7 @@ Configuration page for clouds.
|
||||
"""
|
||||
|
||||
import re
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
|
||||
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:31 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_cloudConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, cloudConfigPageWidget):
|
||||
cloudConfigPageWidget.setObjectName("cloudConfigPageWidget")
|
||||
cloudConfigPageWidget.resize(653, 478)
|
||||
@@ -456,4 +457,3 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONullTab), _translate("cloudConfigPageWidget", "NULL"))
|
||||
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:"))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc."))
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ import shutil
|
||||
import hashlib
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.servers import Servers
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.image_manager import ImageManager
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.gns3_vm import GNS3VM
|
||||
|
||||
from ..module import Module
|
||||
from ..module_error import ModuleError
|
||||
@@ -47,7 +45,6 @@ from .nodes.frame_relay_switch import FrameRelaySwitch
|
||||
from .nodes.atm_switch import ATMSwitch
|
||||
from .settings import DYNAMIPS_SETTINGS
|
||||
from .settings import IOS_ROUTER_SETTINGS
|
||||
from .settings import PLATFORMS_DEFAULT_RAM
|
||||
from .settings import DEFAULT_IDLEPC
|
||||
|
||||
PLATFORM_TO_CLASS = {
|
||||
@@ -201,7 +198,7 @@ class Dynamips(Module):
|
||||
node.server().decreaseAllocatedRAM(node.settings()["ram"])
|
||||
self._nodes.remove(node)
|
||||
|
||||
def iosRouters(self):
|
||||
def VMs(self):
|
||||
"""
|
||||
Returns IOS routers settings.
|
||||
|
||||
@@ -210,7 +207,7 @@ class Dynamips(Module):
|
||||
|
||||
return self._ios_routers
|
||||
|
||||
def setIOSRouters(self, new_ios_routers):
|
||||
def setVMs(self, new_ios_routers):
|
||||
"""
|
||||
Sets IOS images settings.
|
||||
|
||||
@@ -220,6 +217,11 @@ class Dynamips(Module):
|
||||
self._ios_routers = new_ios_routers.copy()
|
||||
self._saveIOSRouters()
|
||||
|
||||
@staticmethod
|
||||
def vmConfigurationPage():
|
||||
from .pages.ios_router_configuration_page import IOSRouterConfigurationPage
|
||||
return IOSRouterConfigurationPage
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Returns the module settings
|
||||
@@ -279,10 +281,9 @@ class Dynamips(Module):
|
||||
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
|
||||
vm_settings[setting_name] = value
|
||||
|
||||
base_name = "R"
|
||||
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
|
||||
# must be an EtherSwitch router
|
||||
base_name = "ESW"
|
||||
default_name_format = IOS_ROUTER_SETTINGS["default_name_format"]
|
||||
if ios_router["default_name_format"]:
|
||||
default_name_format = ios_router["default_name_format"]
|
||||
|
||||
# Older GNS3 versions may have the following invalid settings in the VM template
|
||||
if "console" in vm_settings:
|
||||
@@ -296,7 +297,7 @@ class Dynamips(Module):
|
||||
image = vm_settings.pop("image", None)
|
||||
if image is None:
|
||||
raise ModuleError("No IOS image has been associated with this IOS router")
|
||||
node.setup(image, ram, additional_settings=vm_settings, base_name=base_name)
|
||||
node.setup(image, ram, additional_settings=vm_settings, default_name_format=default_name_format)
|
||||
else:
|
||||
node.setup()
|
||||
|
||||
@@ -312,8 +313,8 @@ class Dynamips(Module):
|
||||
if os.path.basename(ios_router["image"]) == image_path:
|
||||
if ios_router["idlepc"] != idlepc:
|
||||
ios_router["idlepc"] = idlepc
|
||||
log.info("Idle-PC value {} saved into '{}' template".format(idlepc, ios_router["name"]))
|
||||
self._saveIOSRouters()
|
||||
break
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
@@ -360,7 +361,7 @@ class Dynamips(Module):
|
||||
|
||||
from gns3.main_window import MainWindow
|
||||
mainwindow = MainWindow.instance()
|
||||
ios_routers = self.iosRouters()
|
||||
ios_routers = self.VMs()
|
||||
candidate_ios_images = {}
|
||||
alternative_image = {"image": image,
|
||||
"ram": None,
|
||||
@@ -376,7 +377,6 @@ class Dynamips(Module):
|
||||
"IOS image", "IOS image {} could not be found\nPlease select an alternative from your existing images:".format(image),
|
||||
list(candidate_ios_images.keys()), 0, False)
|
||||
if ok:
|
||||
ios_image = candidate_ios_images[selection] # FIXME
|
||||
alternative_image["image"] = ios_router["image"]
|
||||
alternative_image["ram"] = ios_router["ram"]
|
||||
alternative_image["idlepc"] = ios_router["idlepc"]
|
||||
@@ -422,24 +422,14 @@ class Dynamips(Module):
|
||||
in the nodes view and create a node on the scene.
|
||||
"""
|
||||
|
||||
server = "local"
|
||||
if not self._settings["use_local_server"]:
|
||||
if GNS3VM.instance().isRunning():
|
||||
server = "vm"
|
||||
else:
|
||||
# pick up a remote server (round-robin method) #FIXME: review this
|
||||
remote_server = next(iter(Servers.instance()))
|
||||
if remote_server:
|
||||
server = remote_server.url()
|
||||
|
||||
nodes = []
|
||||
for node_class in [EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
|
||||
nodes.append(
|
||||
{"class": node_class.__name__,
|
||||
"name": node_class.symbolName(),
|
||||
"server": server,
|
||||
"categories": node_class.categories(),
|
||||
"symbol": node_class.defaultSymbol()}
|
||||
"symbol": node_class.defaultSymbol(),
|
||||
"builtin": True}
|
||||
)
|
||||
|
||||
for ios_router in self._ios_routers.values():
|
||||
|
||||
@@ -452,6 +452,7 @@ class IOSRouterWizard(VMWithImagesWizard, Ui_IOSRouterWizard):
|
||||
}
|
||||
|
||||
if self.uiEtherSwitchCheckBox.isChecked():
|
||||
settings["default_name_format"] = "ESW"
|
||||
settings["startup_config"] = get_default_base_config(self._base_etherswitch_startup_config_template)
|
||||
settings["symbol"] = ":/symbols/multilayer_switch.svg"
|
||||
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat
|
||||
|
||||
@@ -83,7 +83,8 @@ class EthernetSwitch(Device):
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
self._settings["ports"][port.portNumber()] = {"type": initial_port["type"],
|
||||
"vlan": initial_port["vlan"]}
|
||||
"vlan": initial_port["vlan"],
|
||||
"ethertype": initial_port.get("ethertype", "")}
|
||||
|
||||
params = {"name": name,
|
||||
"device_type": "ethernet_switch"}
|
||||
@@ -182,7 +183,8 @@ class EthernetSwitch(Device):
|
||||
params["nio"] = self.getNIOInfo(nio)
|
||||
port_info = self._settings["ports"][port.portNumber()]
|
||||
port_settings = {"vlan": port_info["vlan"],
|
||||
"type": port_info["type"]}
|
||||
"type": port_info["type"],
|
||||
"ethertype": port_info["ethertype"]}
|
||||
params["port_settings"] = port_settings
|
||||
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
|
||||
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
|
||||
@@ -217,17 +219,21 @@ class EthernetSwitch(Device):
|
||||
port_info += " Port {} is empty\n".format(port.name())
|
||||
else:
|
||||
port_type = self._settings["ports"][port.portNumber()]["type"]
|
||||
port_ethertype = self._settings["ports"][port.portNumber()]["ethertype"]
|
||||
port_vlan = str(self._settings["ports"][port.portNumber()]["vlan"])
|
||||
port_ethertype_info = ""
|
||||
if port_type == "access":
|
||||
port_vlan_info = "VLAN ID {}".format(port_vlan)
|
||||
elif port_type == "dot1q":
|
||||
port_vlan_info = "native VLAN {}".format(port_vlan)
|
||||
elif port_type == "qinq":
|
||||
port_vlan_info = "outer VLAN {}".format(port_vlan)
|
||||
port_ethertype_info = "({})".format(port_ethertype)
|
||||
|
||||
port_info += " Port {name} is in {port_type} mode, with {port_vlan_info},\n".format(name=port.name(),
|
||||
port_type=port_type,
|
||||
port_vlan_info=port_vlan_info)
|
||||
port_info += " Port {name} is in {port_type} {port_ethertype_info} mode, with {port_vlan_info},\n".format(name=port.name(),
|
||||
port_type=port_type,
|
||||
port_ethertype_info=port_ethertype_info,
|
||||
port_vlan_info=port_vlan_info)
|
||||
port_info += " {port_description}\n".format(port_description=port.description())
|
||||
|
||||
return info + port_info
|
||||
@@ -254,6 +260,8 @@ class EthernetSwitch(Device):
|
||||
port_info = port.dump()
|
||||
if port.portNumber() in self._settings["ports"]:
|
||||
port_info["type"] = self._settings["ports"][port.portNumber()]["type"]
|
||||
if port_info["type"] == "qinq" and "ethertype" != "0x8100":
|
||||
port_info["ethertype"] = self._settings["ports"][port.portNumber()]["ethertype"]
|
||||
port_info["vlan"] = self._settings["ports"][port.portNumber()]["vlan"]
|
||||
ports.append(port_info)
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ class Router(VM):
|
||||
self._addWICPorts(wic, wic_slot_number)
|
||||
self._updateWICNumbering()
|
||||
|
||||
def setup(self, image, ram, name=None, vm_id=None, dynamips_id=None, additional_settings={}, base_name="R"):
|
||||
def setup(self, image, ram, name=None, vm_id=None, dynamips_id=None, additional_settings={}, default_name_format="R{0}"):
|
||||
"""
|
||||
Setups this router.
|
||||
|
||||
@@ -232,7 +232,7 @@ class Router(VM):
|
||||
|
||||
# let's create a unique name if none has been chosen
|
||||
if not name:
|
||||
name = self.allocateName(base_name)
|
||||
name = self.allocateName(default_name_format)
|
||||
|
||||
if not name:
|
||||
self.error_signal.emit(self.id(), "could not allocate a name for this router")
|
||||
@@ -258,18 +258,16 @@ class Router(VM):
|
||||
|
||||
# push the startup-config
|
||||
if not vm_id and "startup_config" in additional_settings:
|
||||
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del additional_settings["startup_config"]
|
||||
|
||||
# push the private-config
|
||||
if not vm_id and "private_config" in additional_settings:
|
||||
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del additional_settings["private_config"]
|
||||
|
||||
params.update(additional_settings)
|
||||
@@ -316,10 +314,9 @@ class Router(VM):
|
||||
|
||||
params = {}
|
||||
if "startup_config" in new_settings:
|
||||
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del new_settings["startup_config"]
|
||||
|
||||
if "private_config" in new_settings:
|
||||
@@ -344,10 +341,8 @@ class Router(VM):
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
return
|
||||
if not super()._updateCallback(result, error=error, **kwargs):
|
||||
return False
|
||||
|
||||
updated = False
|
||||
for name, value in result.items():
|
||||
@@ -487,7 +482,8 @@ class Router(VM):
|
||||
self.httpGet("/dynamips/vms/{vm_id}/idlepc_proposals".format(
|
||||
vm_id=self._vm_id),
|
||||
callback,
|
||||
context={"router": self})
|
||||
context={"router": self},
|
||||
progressText="Computing Idle-PC values, please wait...")
|
||||
|
||||
def computeAutoIdlepc(self, callback):
|
||||
"""
|
||||
@@ -498,7 +494,8 @@ class Router(VM):
|
||||
self.httpGet("/dynamips/vms/{vm_id}/auto_idlepc".format(
|
||||
vm_id=self._vm_id),
|
||||
callback,
|
||||
context={"router": self})
|
||||
context={"router": self},
|
||||
progressText="Computing Idle-PC values, please wait...")
|
||||
|
||||
def idlepc(self):
|
||||
"""
|
||||
@@ -664,25 +661,15 @@ class Router(VM):
|
||||
:returns: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
router = {"id": self.id(),
|
||||
"vm_id": self._vm_id,
|
||||
"dynamips_id": self._dynamips_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {},
|
||||
"server_id": self._server.id()}
|
||||
router = super().dump()
|
||||
router["vm_id"] = self._vm_id
|
||||
router["dynamips_id"] = self._dynamips_id
|
||||
|
||||
# add the properties
|
||||
for name, value in self._settings.items():
|
||||
if value is not None and value != "":
|
||||
router["properties"][name] = value
|
||||
|
||||
# add the ports
|
||||
if self._ports:
|
||||
ports = router["ports"] = []
|
||||
for port in self._ports:
|
||||
ports.append(port.dump())
|
||||
|
||||
return router
|
||||
|
||||
def load(self, node_info):
|
||||
@@ -693,6 +680,8 @@ class Router(VM):
|
||||
:param node_info: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
super().load(node_info)
|
||||
|
||||
# for backward compatibility
|
||||
vm_id = dynamips_id = node_info.get("router_id")
|
||||
if not vm_id:
|
||||
@@ -705,7 +694,7 @@ class Router(VM):
|
||||
vm_settings[name] = value
|
||||
name = vm_settings.pop("name")
|
||||
ram = vm_settings.pop("ram", PLATFORMS_DEFAULT_RAM[self._settings["platform"]])
|
||||
image = vm_settings.pop("image")
|
||||
image = vm_settings.pop("image", "")
|
||||
|
||||
if self.server().isLocal():
|
||||
# check and update the path to use the image in the images directory
|
||||
@@ -722,35 +711,8 @@ class Router(VM):
|
||||
|
||||
log.info("router {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self._loading = True
|
||||
self._node_info = node_info
|
||||
self.loaded_signal.connect(self._updatePortSettings)
|
||||
self.setup(image, ram, name, vm_id, dynamips_id, vm_settings)
|
||||
|
||||
def _updatePortSettings(self):
|
||||
"""
|
||||
Updates port settings when loading a topology.
|
||||
"""
|
||||
|
||||
self.loaded_signal.disconnect(self._updatePortSettings)
|
||||
|
||||
# update the port with the correct names and IDs
|
||||
if "ports" in self._node_info:
|
||||
ports = self._node_info["ports"]
|
||||
for topology_port in ports:
|
||||
for port in self._ports:
|
||||
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
|
||||
port.setName(topology_port["name"])
|
||||
port.setId(topology_port["id"])
|
||||
|
||||
# now we can set the node as initialized and trigger the created signal
|
||||
self.setInitialized(True)
|
||||
log.info("router {} has been loaded".format(self.name()))
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
self._loading = False
|
||||
self._node_info = None
|
||||
|
||||
def saveConfig(self):
|
||||
"""
|
||||
Save the configs
|
||||
@@ -881,7 +843,11 @@ class Router(VM):
|
||||
:param directory: source directory path
|
||||
"""
|
||||
|
||||
contents = os.listdir(directory)
|
||||
try:
|
||||
contents = os.listdir(directory)
|
||||
except OSError as e:
|
||||
self.warning_signal.emit(self.id(), "Configuration could not be loaded from directory {}: {}".format(directory, e))
|
||||
return
|
||||
startup_config = normalize_filename(self.name()) + "_startup-config.cfg"
|
||||
private_config = normalize_filename(self.name()) + "_private-config.cfg"
|
||||
new_settings = {}
|
||||
|
||||
@@ -20,7 +20,7 @@ Configuration page for Dynamips ATM bridges.
|
||||
"""
|
||||
|
||||
import re
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..ui.atm_bridge_configuration_page_ui import Ui_atmBridgeConfigPageWidget
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Configuration page for Dynamips ATM switches.
|
||||
"""
|
||||
|
||||
import re
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..ui.atm_switch_configuration_page_ui import Ui_atmSwitchConfigPageWidget
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Configuration page for Dynamips Ethernet switches.
|
||||
"""
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..utils.tree_widget_item import TreeWidgetItem
|
||||
from ..ui.ethernet_switch_configuration_page_ui import Ui_ethernetSwitchConfigPageWidget
|
||||
|
||||
@@ -41,6 +41,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
self.uiDeletePushButton.clicked.connect(self._deletePortSlot)
|
||||
self.uiPortsTreeWidget.itemActivated.connect(self._portSelectedSlot)
|
||||
self.uiPortsTreeWidget.itemSelectionChanged.connect(self._portSelectionChangedSlot)
|
||||
self.uiPortTypeComboBox.currentIndexChanged.connect(self._typeSelectionChangedSlot)
|
||||
|
||||
# enable sorting
|
||||
self.uiPortsTreeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||
@@ -57,11 +58,19 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
port = int(item.text(0))
|
||||
vlan = int(item.text(1))
|
||||
port_type = item.text(2)
|
||||
port_ethertype = item.text(3)
|
||||
self.uiPortSpinBox.setValue(port)
|
||||
self.uiVlanSpinBox.setValue(vlan)
|
||||
index = self.uiPortTypeComboBox.findText(port_type)
|
||||
if index != -1:
|
||||
self.uiPortTypeComboBox.setCurrentIndex(index)
|
||||
index = self.uiPortEtherTypeComboBox.findText(port_ethertype)
|
||||
if index != -1:
|
||||
self.uiPortEtherTypeComboBox.setCurrentIndex(index)
|
||||
if port_type == "qinq":
|
||||
self.uiPortEtherTypeComboBox.setEnabled(True)
|
||||
else:
|
||||
self.uiPortEtherTypeComboBox.setEnabled(False)
|
||||
|
||||
def _portSelectionChangedSlot(self):
|
||||
"""
|
||||
@@ -74,6 +83,17 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
else:
|
||||
self.uiDeletePushButton.setEnabled(False)
|
||||
|
||||
def _typeSelectionChangedSlot(self):
|
||||
"""
|
||||
Disable Q-in-Q EtherType for access and dot1q ports.
|
||||
"""
|
||||
|
||||
port_type = self.uiPortTypeComboBox.currentText()
|
||||
if port_type == "qinq":
|
||||
self.uiPortEtherTypeComboBox.setEnabled(True)
|
||||
else:
|
||||
self.uiPortEtherTypeComboBox.setEnabled(False)
|
||||
|
||||
def _addPortSlot(self):
|
||||
"""
|
||||
Adds a new port.
|
||||
@@ -82,12 +102,17 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
port = self.uiPortSpinBox.value()
|
||||
vlan = self.uiVlanSpinBox.value()
|
||||
port_type = self.uiPortTypeComboBox.currentText()
|
||||
if port_type == "qinq":
|
||||
port_ethertype = self.uiPortEtherTypeComboBox.currentText()
|
||||
else:
|
||||
port_ethertype = ""
|
||||
|
||||
if port in self._ports:
|
||||
# update a given entry in the tree widget
|
||||
item = self.uiPortsTreeWidget.findItems(str(port), QtCore.Qt.MatchFixedString)[0]
|
||||
item.setText(1, str(vlan))
|
||||
item.setText(2, port_type)
|
||||
item.setText(3, port_ethertype)
|
||||
|
||||
else:
|
||||
# add a new entry in the tree widget
|
||||
@@ -95,10 +120,12 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
item.setText(0, str(port))
|
||||
item.setText(1, str(vlan))
|
||||
item.setText(2, port_type)
|
||||
item.setText(3, port_ethertype)
|
||||
self.uiPortsTreeWidget.addTopLevelItem(item)
|
||||
|
||||
self._ports[port] = {"type": port_type,
|
||||
"vlan": vlan}
|
||||
"vlan": vlan,
|
||||
"ethertype": port_ethertype}
|
||||
|
||||
self.uiPortSpinBox.setValue(max(self._ports) + 1)
|
||||
self.uiPortsTreeWidget.resizeColumnToContents(0)
|
||||
@@ -147,6 +174,7 @@ class EthernetSwitchConfigurationPage(QtWidgets.QWidget, Ui_ethernetSwitchConfig
|
||||
item.setText(0, str(port))
|
||||
item.setText(1, str(info["vlan"]))
|
||||
item.setText(2, info["type"])
|
||||
item.setText(3, info["ethertype"])
|
||||
self.uiPortsTreeWidget.addTopLevelItem(item)
|
||||
self._ports[port] = info
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Configuration page for Dynamips Frame Relay switches.
|
||||
"""
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from ..ui.frame_relay_switch_configuration_page_ui import Ui_frameRelaySwitchConfigPageWidget
|
||||
|
||||
|
||||
|
||||
@@ -281,6 +281,14 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
self.uiBaseMACLineEdit.hide()
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# rename the label from "Name" to "Template name"
|
||||
self.uiNameLabel.setText("Template name:")
|
||||
|
||||
# load the default name format
|
||||
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
|
||||
|
||||
# load the startup-config
|
||||
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
|
||||
|
||||
@@ -296,6 +304,8 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if index != -1:
|
||||
self.uiCategoryComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
self.uiDefaultNameFormatLineEdit.hide()
|
||||
self.uiStartupConfigLabel.hide()
|
||||
self.uiStartupConfigLineEdit.hide()
|
||||
self.uiStartupConfigToolButton.hide()
|
||||
@@ -519,11 +529,20 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
del settings["image"]
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# save the default name format
|
||||
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
|
||||
if '{0}' not in default_name_format and '{id}' not in default_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
|
||||
else:
|
||||
settings["default_name_format"] = default_name_format
|
||||
|
||||
startup_config = self.uiStartupConfigLineEdit.text().strip()
|
||||
if not startup_config:
|
||||
settings["startup_config"] = ""
|
||||
elif startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
if self._configFileValid(startup_config):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
@@ -532,7 +551,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if not private_config:
|
||||
settings["private_config"] = ""
|
||||
elif private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
if self._configFileValid(private_config):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
@@ -636,3 +655,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
|
||||
if node:
|
||||
self._checkForLinkConnectedToWIC(wic_number, settings, node)
|
||||
settings["wic" + str(wic_number)] = None
|
||||
|
||||
def _configFileValid(self, path):
|
||||
"""
|
||||
Return true if it's a valid configuration file
|
||||
"""
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
@@ -160,7 +160,6 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
for item in self.uiIOSRoutersTreeWidget.selectedItems():
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
ios_router = self._ios_routers[key]
|
||||
|
||||
del self._ios_routers[key]
|
||||
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
|
||||
@@ -228,7 +227,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
try:
|
||||
os.makedirs(cls.getImageDirectory(), exist_ok=True)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, e))
|
||||
QtWidgets.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(cls.getImageDirectory(), e))
|
||||
return
|
||||
|
||||
compressed = False
|
||||
@@ -271,7 +270,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
decompressed_size += zip_info.file_size
|
||||
else:
|
||||
decompressed_size = os.path.getsize(path)
|
||||
except (zipfile.BadZipFile, OSError):
|
||||
except (zipfile.BadZipFile, OSError, ValueError):
|
||||
return 0
|
||||
|
||||
# get the size in MB
|
||||
@@ -337,7 +336,8 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
|
||||
# fill out the General section
|
||||
section_item = self._createSectionItem("General")
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Name:", ios_router["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", ios_router["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", ios_router["default_name_format"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", ios_router["server"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Platform:", ios_router["platform"]])
|
||||
if ios_router["chassis"]:
|
||||
@@ -350,8 +350,8 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
if ios_router["private_config"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Private-config:", ios_router["private_config"]])
|
||||
if ios_router["platform"] == "c7200":
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Midplane:", ios_router["midplane"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["NPE:", ios_router["npe"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Midplane:", ios_router.get("midplane", "vxr")])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["NPE:", ios_router.get("npe", "npe-400")])
|
||||
|
||||
# fill out the Memories and disk section
|
||||
section_item = self._createSectionItem("Memories and disks")
|
||||
@@ -391,7 +391,7 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
"""
|
||||
|
||||
dynamips_module = Dynamips.instance()
|
||||
self._ios_routers = copy.deepcopy(dynamips_module.iosRouters())
|
||||
self._ios_routers = copy.deepcopy(dynamips_module.VMs())
|
||||
self._items.clear()
|
||||
|
||||
for key, ios_router in self._ios_routers.items():
|
||||
@@ -410,4 +410,4 @@ class IOSRouterPreferencesPage(QtWidgets.QWidget, Ui_IOSRouterPreferencesPageWid
|
||||
Saves the IOS router preferences.
|
||||
"""
|
||||
|
||||
Dynamips.instance().setIOSRouters(self._ios_routers)
|
||||
Dynamips.instance().setVMs(self._ios_routers)
|
||||
|
||||
@@ -32,6 +32,7 @@ DYNAMIPS_SETTINGS = {
|
||||
|
||||
IOS_ROUTER_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "R{0}",
|
||||
"image": "",
|
||||
"symbol": ":/symbols/router.svg",
|
||||
"category": Node.routers,
|
||||
@@ -73,6 +74,7 @@ PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
|
||||
# MD5 checksum done on uncompressed IOS images
|
||||
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
|
||||
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
|
||||
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
|
||||
@@ -87,7 +89,8 @@ DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adv
|
||||
"64f8c427ed48fd21bd02cf1ff254c4eb": "0x60c09aa0", # c3725-adventerprisek9-mz.124-15.T14
|
||||
"ddbaf74274822b50fa9670e10c75b08f": "0x60aa1da0", # c3745-adventerprisek9-mz.124-25d
|
||||
"4af2e752220ed1397924150ff7bbe4ce": "0x602701e4", # c3745-adventerprisek9-mz.124-15.T14
|
||||
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838"} # c7200-adventerprisek9-mz.124-24.T5
|
||||
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838", # c7200-adventerprisek9-mz.124-24.T5
|
||||
"dda82f22a39215bc6b27af891e12b8f6": "0x6018c294"} # c7200-adventerprisek9-mz.155-2.XB
|
||||
|
||||
# platforms with supported chassis
|
||||
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_bridge_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_atmBridgeConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, atmBridgeConfigPageWidget):
|
||||
atmBridgeConfigPageWidget.setObjectName("atmBridgeConfigPageWidget")
|
||||
atmBridgeConfigPageWidget.resize(432, 358)
|
||||
@@ -151,4 +152,3 @@ class Ui_atmBridgeConfigPageWidget(object):
|
||||
self.uiDeletePushButton.setText(_translate("atmBridgeConfigPageWidget", "&Delete"))
|
||||
self.uiGeneralGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "General"))
|
||||
self.uiNameLabel.setText(_translate("atmBridgeConfigPageWidget", "Name:"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/atm_switch_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_atmSwitchConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, atmSwitchConfigPageWidget):
|
||||
atmSwitchConfigPageWidget.setObjectName("atmSwitchConfigPageWidget")
|
||||
atmSwitchConfigPageWidget.resize(459, 419)
|
||||
@@ -185,4 +186,3 @@ class Ui_atmSwitchConfigPageWidget(object):
|
||||
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:"))
|
||||
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:"))
|
||||
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/dynamips_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_DynamipsPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, DynamipsPreferencesPageWidget):
|
||||
DynamipsPreferencesPageWidget.setObjectName("DynamipsPreferencesPageWidget")
|
||||
DynamipsPreferencesPageWidget.resize(435, 200)
|
||||
@@ -108,4 +109,3 @@ class Ui_DynamipsPreferencesPageWidget(object):
|
||||
self.uiSparseMemorySupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable sparse memory support"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiAdvancedSettingsTabWidget), _translate("DynamipsPreferencesPageWidget", "Advanced settings"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("DynamipsPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ethernet_hub_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ethernetHubConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, ethernetHubConfigPageWidget):
|
||||
ethernetHubConfigPageWidget.setObjectName("ethernetHubConfigPageWidget")
|
||||
ethernetHubConfigPageWidget.resize(381, 270)
|
||||
@@ -57,4 +58,3 @@ class Ui_ethernetHubConfigPageWidget(object):
|
||||
self.uiSettingsGroupBox.setTitle(_translate("ethernetHubConfigPageWidget", "Settings"))
|
||||
self.uiNameLabel.setText(_translate("ethernetHubConfigPageWidget", "Name:"))
|
||||
self.uiPortsLabel.setText(_translate("ethernetHubConfigPageWidget", "Number of ports:"))
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>EtherType</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -141,6 +146,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>QinQ EtherType:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="uiPortTypeComboBox">
|
||||
<item>
|
||||
@@ -160,6 +172,33 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiPortEtherTypeComboBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0x8100</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0x88A8</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0x9100</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0x9200</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ethernet_switch_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.5
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_ethernetSwitchConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, ethernetSwitchConfigPageWidget):
|
||||
ethernetSwitchConfigPageWidget.setObjectName("ethernetSwitchConfigPageWidget")
|
||||
ethernetSwitchConfigPageWidget.resize(397, 315)
|
||||
@@ -80,12 +81,23 @@ class Ui_ethernetSwitchConfigPageWidget(object):
|
||||
self.label_2 = QtWidgets.QLabel(self.uiEthernetSwitchSettingsGroupBox)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridlayout.addWidget(self.label_2, 2, 0, 1, 1)
|
||||
self.label_4 = QtWidgets.QLabel(self.uiEthernetSwitchSettingsGroupBox)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridlayout.addWidget(self.label_4, 3, 0, 1, 1)
|
||||
self.uiPortTypeComboBox = QtWidgets.QComboBox(self.uiEthernetSwitchSettingsGroupBox)
|
||||
self.uiPortTypeComboBox.setObjectName("uiPortTypeComboBox")
|
||||
self.uiPortTypeComboBox.addItem("")
|
||||
self.uiPortTypeComboBox.addItem("")
|
||||
self.uiPortTypeComboBox.addItem("")
|
||||
self.gridlayout.addWidget(self.uiPortTypeComboBox, 2, 1, 1, 1)
|
||||
self.uiPortEtherTypeComboBox = QtWidgets.QComboBox(self.uiEthernetSwitchSettingsGroupBox)
|
||||
self.uiPortEtherTypeComboBox.setEnabled(False)
|
||||
self.uiPortEtherTypeComboBox.setObjectName("uiPortEtherTypeComboBox")
|
||||
self.uiPortEtherTypeComboBox.addItem("")
|
||||
self.uiPortEtherTypeComboBox.addItem("")
|
||||
self.uiPortEtherTypeComboBox.addItem("")
|
||||
self.uiPortEtherTypeComboBox.addItem("")
|
||||
self.gridlayout.addWidget(self.uiPortEtherTypeComboBox, 3, 1, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.uiEthernetSwitchSettingsGroupBox, 1, 0, 1, 2)
|
||||
self.uiAddPushButton = QtWidgets.QPushButton(ethernetSwitchConfigPageWidget)
|
||||
self.uiAddPushButton.setObjectName("uiAddPushButton")
|
||||
@@ -116,13 +128,18 @@ class Ui_ethernetSwitchConfigPageWidget(object):
|
||||
self.uiPortsTreeWidget.headerItem().setText(0, _translate("ethernetSwitchConfigPageWidget", "Port"))
|
||||
self.uiPortsTreeWidget.headerItem().setText(1, _translate("ethernetSwitchConfigPageWidget", "VLAN"))
|
||||
self.uiPortsTreeWidget.headerItem().setText(2, _translate("ethernetSwitchConfigPageWidget", "Type"))
|
||||
self.uiPortsTreeWidget.headerItem().setText(3, _translate("ethernetSwitchConfigPageWidget", "EtherType"))
|
||||
self.uiEthernetSwitchSettingsGroupBox.setTitle(_translate("ethernetSwitchConfigPageWidget", "Settings"))
|
||||
self.label.setText(_translate("ethernetSwitchConfigPageWidget", "Port:"))
|
||||
self.label_3.setText(_translate("ethernetSwitchConfigPageWidget", "VLAN:"))
|
||||
self.label_2.setText(_translate("ethernetSwitchConfigPageWidget", "Type:"))
|
||||
self.label_4.setText(_translate("ethernetSwitchConfigPageWidget", "QinQ EtherType:"))
|
||||
self.uiPortTypeComboBox.setItemText(0, _translate("ethernetSwitchConfigPageWidget", "access"))
|
||||
self.uiPortTypeComboBox.setItemText(1, _translate("ethernetSwitchConfigPageWidget", "dot1q"))
|
||||
self.uiPortTypeComboBox.setItemText(2, _translate("ethernetSwitchConfigPageWidget", "qinq"))
|
||||
self.uiPortEtherTypeComboBox.setItemText(0, _translate("ethernetSwitchConfigPageWidget", "0x8100"))
|
||||
self.uiPortEtherTypeComboBox.setItemText(1, _translate("ethernetSwitchConfigPageWidget", "0x88A8"))
|
||||
self.uiPortEtherTypeComboBox.setItemText(2, _translate("ethernetSwitchConfigPageWidget", "0x9100"))
|
||||
self.uiPortEtherTypeComboBox.setItemText(3, _translate("ethernetSwitchConfigPageWidget", "0x9200"))
|
||||
self.uiAddPushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Add"))
|
||||
self.uiDeletePushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Delete"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/frame_relay_switch_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:32 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_frameRelaySwitchConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, frameRelaySwitchConfigPageWidget):
|
||||
frameRelaySwitchConfigPageWidget.setObjectName("frameRelaySwitchConfigPageWidget")
|
||||
frameRelaySwitchConfigPageWidget.resize(499, 405)
|
||||
@@ -148,4 +149,3 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
|
||||
self.uiDestinationDLCILabel.setText(_translate("frameRelaySwitchConfigPageWidget", "DLCI:"))
|
||||
self.uiAddPushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Add"))
|
||||
self.uiDeletePushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Delete"))
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>476</width>
|
||||
<height>510</height>
|
||||
<width>499</width>
|
||||
<height>507</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -24,58 +24,13 @@
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiPlatformLabel">
|
||||
<property name="text">
|
||||
<string>Platform:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="uiPlatformTextLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiChassisLabel">
|
||||
<property name="text">
|
||||
<string>Chassis:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="uiChassisTextLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiIOSImageLabel">
|
||||
<property name="text">
|
||||
<string>IOS image path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item row="10" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiIOSImageLineEdit"/>
|
||||
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiIOSImageToolButton">
|
||||
<widget class="QToolButton" name="uiPrivateConfigToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
@@ -86,10 +41,47 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="uiStartupConfigLabel">
|
||||
<item row="17" column="1">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>263</width>
|
||||
<height>151</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="uiAuxPortLabel">
|
||||
<property name="text">
|
||||
<string>Initial startup-config:</string>
|
||||
<string>Aux port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="uiChassisTextLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -110,20 +102,20 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="uiPrivateConfigLabel">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiPlatformLabel">
|
||||
<property name="text">
|
||||
<string>Initial private-config:</string>
|
||||
<string>Platform:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
|
||||
<widget class="QLineEdit" name="uiIOSImageLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="uiPrivateConfigToolButton">
|
||||
<widget class="QToolButton" name="uiIOSImageToolButton">
|
||||
<property name="text">
|
||||
<string>&Browse...</string>
|
||||
</property>
|
||||
@@ -134,91 +126,38 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="uiAuxPortLabel">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="uiPrivateConfigLabel">
|
||||
<property name="text">
|
||||
<string>Aux port:</string>
|
||||
<string>Initial private-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QSpinBox" name="uiAuxPortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="uiMidplaneLabel">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="uiStartupConfigLabel">
|
||||
<property name="text">
|
||||
<string>Midplane:</string>
|
||||
<string>Initial startup-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QComboBox" name="uiMidplaneComboBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="uiNPELabel">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="uiPlatformTextLabel">
|
||||
<property name="text">
|
||||
<string>NPE:</string>
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="1">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>263</width>
|
||||
<height>151</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="QComboBox" name="uiNPEComboBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -239,16 +178,87 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiChassisLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
<string>Chassis:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="QComboBox" name="uiNPEComboBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QComboBox" name="uiMidplaneComboBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="uiMidplaneLabel">
|
||||
<property name="text">
|
||||
<string>Midplane:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiIOSImageLabel">
|
||||
<property name="text">
|
||||
<string>IOS image path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="uiNPELabel">
|
||||
<property name="text">
|
||||
<string>NPE:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QSpinBox" name="uiAuxPortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiDefaultNameFormatLabel">
|
||||
<property name="text">
|
||||
<string>Default name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiMemoriesPageWidget">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
# Created: Thu Feb 4 21:08:13 2016
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -11,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_iosRouterConfigPageWidget(object):
|
||||
def setupUi(self, iosRouterConfigPageWidget):
|
||||
iosRouterConfigPageWidget.setObjectName("iosRouterConfigPageWidget")
|
||||
iosRouterConfigPageWidget.resize(476, 510)
|
||||
iosRouterConfigPageWidget.resize(499, 507)
|
||||
self.vboxlayout = QtWidgets.QVBoxLayout(iosRouterConfigPageWidget)
|
||||
self.vboxlayout.setObjectName("vboxlayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(iosRouterConfigPageWidget)
|
||||
@@ -20,55 +21,6 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiGeneralPageWidget.setObjectName("uiGeneralPageWidget")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiGeneralPageWidget)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout_2.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout_2.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiPlatformLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPlatformLabel.setObjectName("uiPlatformLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPlatformLabel, 3, 0, 1, 1)
|
||||
self.uiPlatformTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPlatformTextLabel.setText("")
|
||||
self.uiPlatformTextLabel.setObjectName("uiPlatformTextLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPlatformTextLabel, 3, 1, 1, 1)
|
||||
self.uiChassisLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiChassisLabel.setObjectName("uiChassisLabel")
|
||||
self.gridLayout_2.addWidget(self.uiChassisLabel, 4, 0, 1, 1)
|
||||
self.uiChassisTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiChassisTextLabel.setText("")
|
||||
self.uiChassisTextLabel.setObjectName("uiChassisTextLabel")
|
||||
self.gridLayout_2.addWidget(self.uiChassisTextLabel, 4, 1, 1, 1)
|
||||
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
|
||||
self.gridLayout_2.addWidget(self.uiIOSImageLabel, 8, 0, 1, 1)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.uiIOSImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageLineEdit.setObjectName("uiIOSImageLineEdit")
|
||||
self.horizontalLayout_5.addWidget(self.uiIOSImageLineEdit)
|
||||
self.uiIOSImageToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiIOSImageToolButton.setObjectName("uiIOSImageToolButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiIOSImageToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_5, 8, 1, 1, 1)
|
||||
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
|
||||
self.gridLayout_2.addWidget(self.uiStartupConfigLabel, 9, 0, 1, 1)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigLineEdit.setObjectName("uiStartupConfigLineEdit")
|
||||
self.horizontalLayout_4.addWidget(self.uiStartupConfigLineEdit)
|
||||
self.uiStartupConfigToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
|
||||
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_4, 9, 1, 1, 1)
|
||||
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPrivateConfigLabel, 10, 0, 1, 1)
|
||||
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
|
||||
self.uiPrivateConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
@@ -79,49 +31,63 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiPrivateConfigToolButton.setObjectName("uiPrivateConfigToolButton")
|
||||
self.horizontalLayout_6.addWidget(self.uiPrivateConfigToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_6, 10, 1, 1, 1)
|
||||
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
|
||||
self.gridLayout_2.addWidget(self.uiConsolePortLabel, 13, 0, 1, 1)
|
||||
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
|
||||
self.uiConsolePortSpinBox.setMaximum(65535)
|
||||
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
|
||||
self.gridLayout_2.addWidget(self.uiConsolePortSpinBox, 13, 1, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem, 17, 1, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout_2.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiAuxPortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiAuxPortLabel.setObjectName("uiAuxPortLabel")
|
||||
self.gridLayout_2.addWidget(self.uiAuxPortLabel, 14, 0, 1, 1)
|
||||
self.uiAuxPortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
|
||||
self.uiAuxPortSpinBox.setMaximum(65535)
|
||||
self.uiAuxPortSpinBox.setObjectName("uiAuxPortSpinBox")
|
||||
self.gridLayout_2.addWidget(self.uiAuxPortSpinBox, 14, 1, 1, 1)
|
||||
self.uiMidplaneLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiMidplaneLabel.setObjectName("uiMidplaneLabel")
|
||||
self.gridLayout_2.addWidget(self.uiMidplaneLabel, 15, 0, 1, 1)
|
||||
self.uiMidplaneComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiMidplaneComboBox.setEnabled(True)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiMidplaneComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiMidplaneComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiMidplaneComboBox.setObjectName("uiMidplaneComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiMidplaneComboBox, 15, 1, 1, 1)
|
||||
self.uiNPELabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiNPELabel.setObjectName("uiNPELabel")
|
||||
self.gridLayout_2.addWidget(self.uiNPELabel, 16, 0, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 151, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem, 17, 1, 1, 1)
|
||||
self.uiNPEComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiNPEComboBox.setEnabled(True)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiNPEComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiNPEComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiNPEComboBox.setObjectName("uiNPEComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiNPEComboBox, 16, 1, 1, 1)
|
||||
self.uiChassisTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiChassisTextLabel.setText("")
|
||||
self.uiChassisTextLabel.setObjectName("uiChassisTextLabel")
|
||||
self.gridLayout_2.addWidget(self.uiChassisTextLabel, 4, 1, 1, 1)
|
||||
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
|
||||
self.gridLayout_2.addWidget(self.uiSymbolLabel, 5, 0, 1, 1)
|
||||
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
|
||||
self.gridLayout_2.addWidget(self.uiConsolePortLabel, 13, 0, 1, 1)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigLineEdit.setObjectName("uiStartupConfigLineEdit")
|
||||
self.horizontalLayout_4.addWidget(self.uiStartupConfigLineEdit)
|
||||
self.uiStartupConfigToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
|
||||
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_4, 9, 1, 1, 1)
|
||||
self.uiPlatformLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPlatformLabel.setObjectName("uiPlatformLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPlatformLabel, 3, 0, 1, 1)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.uiIOSImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageLineEdit.setObjectName("uiIOSImageLineEdit")
|
||||
self.horizontalLayout_5.addWidget(self.uiIOSImageLineEdit)
|
||||
self.uiIOSImageToolButton = QtWidgets.QToolButton(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiIOSImageToolButton.setObjectName("uiIOSImageToolButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiIOSImageToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_5, 8, 1, 1, 1)
|
||||
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout_2.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPrivateConfigLabel, 10, 0, 1, 1)
|
||||
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
|
||||
self.gridLayout_2.addWidget(self.uiStartupConfigLabel, 9, 0, 1, 1)
|
||||
self.uiPlatformTextLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiPlatformTextLabel.setText("")
|
||||
self.uiPlatformTextLabel.setObjectName("uiPlatformTextLabel")
|
||||
self.gridLayout_2.addWidget(self.uiPlatformTextLabel, 3, 1, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout_2.addWidget(self.uiCategoryLabel, 6, 0, 1, 1)
|
||||
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
|
||||
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
@@ -132,12 +98,53 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
|
||||
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_7, 5, 1, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout_2.addWidget(self.uiCategoryLabel, 6, 0, 1, 1)
|
||||
self.uiChassisLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiChassisLabel.setObjectName("uiChassisLabel")
|
||||
self.gridLayout_2.addWidget(self.uiChassisLabel, 4, 0, 1, 1)
|
||||
self.uiNPEComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiNPEComboBox.setEnabled(True)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiNPEComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiNPEComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiNPEComboBox.setObjectName("uiNPEComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiNPEComboBox, 16, 1, 1, 1)
|
||||
self.uiMidplaneComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiMidplaneComboBox.setEnabled(True)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiMidplaneComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiMidplaneComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiMidplaneComboBox.setObjectName("uiMidplaneComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiMidplaneComboBox, 15, 1, 1, 1)
|
||||
self.uiMidplaneLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiMidplaneLabel.setObjectName("uiMidplaneLabel")
|
||||
self.gridLayout_2.addWidget(self.uiMidplaneLabel, 15, 0, 1, 1)
|
||||
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
|
||||
self.gridLayout_2.addWidget(self.uiIOSImageLabel, 8, 0, 1, 1)
|
||||
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralPageWidget)
|
||||
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
|
||||
self.gridLayout_2.addWidget(self.uiCategoryComboBox, 6, 1, 1, 1)
|
||||
self.uiNPELabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiNPELabel.setObjectName("uiNPELabel")
|
||||
self.gridLayout_2.addWidget(self.uiNPELabel, 16, 0, 1, 1)
|
||||
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
|
||||
self.uiConsolePortSpinBox.setMaximum(65535)
|
||||
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
|
||||
self.gridLayout_2.addWidget(self.uiConsolePortSpinBox, 13, 1, 1, 1)
|
||||
self.uiAuxPortSpinBox = QtWidgets.QSpinBox(self.uiGeneralPageWidget)
|
||||
self.uiAuxPortSpinBox.setMaximum(65535)
|
||||
self.uiAuxPortSpinBox.setObjectName("uiAuxPortSpinBox")
|
||||
self.gridLayout_2.addWidget(self.uiAuxPortSpinBox, 14, 1, 1, 1)
|
||||
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralPageWidget)
|
||||
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
|
||||
self.gridLayout_2.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
|
||||
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralPageWidget)
|
||||
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
|
||||
self.gridLayout_2.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
|
||||
self.uiTabWidget.addTab(self.uiGeneralPageWidget, "")
|
||||
self.uiMemoriesPageWidget = QtWidgets.QWidget()
|
||||
self.uiMemoriesPageWidget.setObjectName("uiMemoriesPageWidget")
|
||||
@@ -545,22 +552,23 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
def retranslateUi(self, iosRouterConfigPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
iosRouterConfigPageWidget.setWindowTitle(_translate("iosRouterConfigPageWidget", "Dynamips IOS Router configuration"))
|
||||
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:"))
|
||||
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:"))
|
||||
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:"))
|
||||
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:"))
|
||||
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:"))
|
||||
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:"))
|
||||
self.uiPrivateConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:"))
|
||||
self.uiAuxPortLabel.setText(_translate("iosRouterConfigPageWidget", "Aux port:"))
|
||||
self.uiMidplaneLabel.setText(_translate("iosRouterConfigPageWidget", "Midplane:"))
|
||||
self.uiNPELabel.setText(_translate("iosRouterConfigPageWidget", "NPE:"))
|
||||
self.uiSymbolLabel.setText(_translate("iosRouterConfigPageWidget", "Symbol:"))
|
||||
self.uiSymbolToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:"))
|
||||
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:"))
|
||||
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:"))
|
||||
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:"))
|
||||
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:"))
|
||||
self.uiCategoryLabel.setText(_translate("iosRouterConfigPageWidget", "Category:"))
|
||||
self.uiSymbolToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse..."))
|
||||
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:"))
|
||||
self.uiMidplaneLabel.setText(_translate("iosRouterConfigPageWidget", "Midplane:"))
|
||||
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:"))
|
||||
self.uiNPELabel.setText(_translate("iosRouterConfigPageWidget", "NPE:"))
|
||||
self.uiDefaultNameFormatLabel.setText(_translate("iosRouterConfigPageWidget", "Default name format:"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralPageWidget), _translate("iosRouterConfigPageWidget", "General"))
|
||||
self.uiRamLabel.setText(_translate("iosRouterConfigPageWidget", "RAM size:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB"))
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:33 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_IOSRouterPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, IOSRouterPreferencesPageWidget):
|
||||
IOSRouterPreferencesPageWidget.setObjectName("IOSRouterPreferencesPageWidget")
|
||||
IOSRouterPreferencesPageWidget.resize(505, 350)
|
||||
@@ -77,4 +78,3 @@ class Ui_IOSRouterPreferencesPageWidget(object):
|
||||
self.uiDecompressIOSPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Decompress"))
|
||||
self.uiEditIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Edit"))
|
||||
self.uiDeleteIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Delete"))
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
<property name="title">
|
||||
<string>Server type</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiRemoteRadioButton">
|
||||
<property name="text">
|
||||
<string>Remote</string>
|
||||
<string>Run the IOS on a remote computer</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@@ -43,30 +43,17 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiVMRadioButton">
|
||||
<property name="text">
|
||||
<string>GNS3 VM</string>
|
||||
<string>Run the IOS on the GNS3 VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiLocalRadioButton">
|
||||
<property name="text">
|
||||
<string>Local</string>
|
||||
<string>Run the IOS on your local computer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:33 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -20,20 +19,18 @@ class Ui_IOSRouterWizard(object):
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
|
||||
self.verticalLayout_3.addWidget(self.uiRemoteRadioButton)
|
||||
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiVMRadioButton)
|
||||
self.verticalLayout_3.addWidget(self.uiVMRadioButton)
|
||||
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.verticalLayout_3.addWidget(self.uiLocalRadioButton)
|
||||
self.verticalLayout.addWidget(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
|
||||
@@ -71,8 +68,8 @@ class Ui_IOSRouterWizard(object):
|
||||
self.uiIOSNewImageRadioButton.setChecked(False)
|
||||
self.uiIOSNewImageRadioButton.setObjectName("uiIOSNewImageRadioButton")
|
||||
self.horizontalLayout_4.addWidget(self.uiIOSNewImageRadioButton)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_4.addItem(spacerItem1)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_4.addItem(spacerItem)
|
||||
self.verticalLayout_2.addLayout(self.horizontalLayout_4)
|
||||
self.uiIOSImageLabel = QtWidgets.QLabel(self.uiIOSImageWizardPage)
|
||||
self.uiIOSImageLabel.setObjectName("uiIOSImageLabel")
|
||||
@@ -295,9 +292,9 @@ class Ui_IOSRouterWizard(object):
|
||||
self.uiServerWizardPage.setTitle(_translate("IOSRouterWizard", "Server"))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose a server type to run your new IOS router."))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("IOSRouterWizard", "Server type"))
|
||||
self.uiRemoteRadioButton.setText(_translate("IOSRouterWizard", "Remote"))
|
||||
self.uiVMRadioButton.setText(_translate("IOSRouterWizard", "GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("IOSRouterWizard", "Local"))
|
||||
self.uiRemoteRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on a remote computer"))
|
||||
self.uiVMRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on the GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("IOSRouterWizard", "Run the IOS on your local computer"))
|
||||
self.uiRemoteServersGroupBox.setTitle(_translate("IOSRouterWizard", "Remote servers"))
|
||||
self.uiLoadBalanceCheckBox.setText(_translate("IOSRouterWizard", "Load balance across all available remote servers"))
|
||||
self.uiRemoteServersLabel.setText(_translate("IOSRouterWizard", "Run on server:"))
|
||||
|
||||
@@ -70,4 +70,6 @@ class DecompressIOSWorker(QtCore.QObject):
|
||||
Cancel this worker.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
return
|
||||
self._is_running = False
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3.qt import QtGui, QtWidgets
|
||||
from gns3.qt import QtWidgets
|
||||
|
||||
|
||||
class TreeWidgetItem(QtWidgets.QTreeWidgetItem):
|
||||
|
||||
@@ -23,7 +23,7 @@ import sys
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from gns3.qt import QtCore, QtWidgets
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
@@ -141,7 +141,7 @@ class IOU(Module):
|
||||
node.server().decreaseAllocatedRAM(node.settings()["ram"])
|
||||
self._nodes.remove(node)
|
||||
|
||||
def iouDevices(self):
|
||||
def VMs(self):
|
||||
"""
|
||||
Returns IOU devices settings.
|
||||
|
||||
@@ -150,7 +150,7 @@ class IOU(Module):
|
||||
|
||||
return self._iou_devices
|
||||
|
||||
def setIOUDevices(self, new_iou_devices):
|
||||
def setVMs(self, new_iou_devices):
|
||||
"""
|
||||
Sets IOS devices settings.
|
||||
|
||||
@@ -160,6 +160,11 @@ class IOU(Module):
|
||||
self._iou_devices = new_iou_devices.copy()
|
||||
self._saveIOUDevices()
|
||||
|
||||
@staticmethod
|
||||
def vmConfigurationPage():
|
||||
from .pages.iou_device_configuration_page import iouDeviceConfigurationPage
|
||||
return iouDeviceConfigurationPage
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Returns the module settings
|
||||
@@ -236,6 +241,10 @@ class IOU(Module):
|
||||
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
|
||||
vm_settings[setting_name] = value
|
||||
|
||||
default_name_format = IOU_DEVICE_SETTINGS["default_name_format"]
|
||||
if self._iou_devices[iouimage]["default_name_format"]:
|
||||
default_name_format = self._iou_devices[iouimage]["default_name_format"]
|
||||
|
||||
if vm_settings["use_default_iou_values"]:
|
||||
del vm_settings["ram"]
|
||||
del vm_settings["nvram"]
|
||||
@@ -245,7 +254,7 @@ class IOU(Module):
|
||||
del vm_settings["console"]
|
||||
|
||||
iou_path = vm_settings.pop("path")
|
||||
node.setup(iou_path, additional_settings=vm_settings)
|
||||
node.setup(iou_path, additional_settings=vm_settings, default_name_format=default_name_format)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
@@ -291,7 +300,7 @@ class IOU(Module):
|
||||
|
||||
from gns3.main_window import MainWindow
|
||||
mainwindow = MainWindow.instance()
|
||||
iou_devices = self.iouDevices()
|
||||
iou_devices = self.VMs()
|
||||
candidate_iou_images = {}
|
||||
|
||||
alternative_image = image
|
||||
|
||||
@@ -23,7 +23,6 @@ import os
|
||||
import re
|
||||
from gns3.vm import VM
|
||||
from gns3.node import Node
|
||||
from gns3.servers import Servers
|
||||
from gns3.packet_capture import PacketCapture
|
||||
from gns3.ports.ethernet_port import EthernetPort
|
||||
from gns3.ports.serial_port import SerialPort
|
||||
@@ -104,7 +103,7 @@ class IOUDevice(VM):
|
||||
self._ports.remove(port)
|
||||
log.info("port {} has been removed".format(port.name()))
|
||||
|
||||
def setup(self, iou_path, name=None, vm_id=None, additional_settings={}, base_name="IOU"):
|
||||
def setup(self, iou_path, name=None, vm_id=None, additional_settings={}, default_name_format="IOU{0}"):
|
||||
"""
|
||||
Setups this IOU device.
|
||||
|
||||
@@ -115,7 +114,7 @@ class IOUDevice(VM):
|
||||
|
||||
# let's create a unique name if none has been chosen
|
||||
if not name:
|
||||
name = self.allocateName(base_name)
|
||||
name = self.allocateName(default_name_format)
|
||||
|
||||
if not name:
|
||||
self.error_signal.emit(self.id(), "could not allocate a name for this IOU device")
|
||||
@@ -130,24 +129,22 @@ class IOUDevice(VM):
|
||||
|
||||
# push the startup-config
|
||||
if "startup_config" in additional_settings:
|
||||
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del additional_settings["startup_config"]
|
||||
|
||||
# push the startup-config
|
||||
if "private_config" in additional_settings:
|
||||
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(additional_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del additional_settings["private_config"]
|
||||
|
||||
params = self._addIourcContentToParams(params)
|
||||
|
||||
params.update(additional_settings)
|
||||
self.httpPost("/iou/vms", self._setupCallback, body=params)
|
||||
self.httpPost("/iou/vms", self._setupCallback, body=params, progressText="Creating {}".format(name))
|
||||
|
||||
def _setupCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -188,7 +185,7 @@ class IOUDevice(VM):
|
||||
params = self._addIourcContentToParams(params)
|
||||
|
||||
log.debug("{} is starting".format(self.name()))
|
||||
self.httpPost("/{prefix}/vms/{vm_id}/start".format(prefix=self.URL_PREFIX, vm_id=self._vm_id), self._startCallback, body=params)
|
||||
self.httpPost("/{prefix}/vms/{vm_id}/start".format(prefix=self.URL_PREFIX, vm_id=self._vm_id), self._startCallback, body=params, progressText="{} is starting".format(self.name()))
|
||||
|
||||
def _addIourcContentToParams(self, params):
|
||||
"""
|
||||
@@ -219,17 +216,15 @@ class IOUDevice(VM):
|
||||
|
||||
params = {}
|
||||
if "startup_config" in new_settings:
|
||||
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["startup_config"])
|
||||
if base_config_content is not None:
|
||||
params["startup_config_content"] = base_config_content
|
||||
del new_settings["startup_config"]
|
||||
|
||||
if "private_config" in new_settings:
|
||||
if new_settings["private_config"] and os.path.isfile(new_settings["private_config"]):
|
||||
base_config_content = self._readBaseConfig(new_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
base_config_content = self._readBaseConfig(new_settings["private_config"])
|
||||
if base_config_content is not None:
|
||||
params["private_config_content"] = base_config_content
|
||||
del new_settings["private_config"]
|
||||
|
||||
for name, value in new_settings.items():
|
||||
@@ -247,10 +242,8 @@ class IOUDevice(VM):
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
return
|
||||
if not super()._updateCallback(result, error=error, **kwargs):
|
||||
return False
|
||||
|
||||
updated = False
|
||||
nb_adapters_changed = False
|
||||
@@ -401,24 +394,14 @@ class IOUDevice(VM):
|
||||
:returns: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
iou = {"id": self.id(),
|
||||
"vm_id": self._vm_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {},
|
||||
"server_id": self._server.id()}
|
||||
iou = super().dump()
|
||||
iou["vm_id"] = self._vm_id
|
||||
|
||||
# add the properties
|
||||
for name, value in self._settings.items():
|
||||
if value is not None and value != "":
|
||||
iou["properties"][name] = value
|
||||
|
||||
# add the ports
|
||||
if self._ports:
|
||||
ports = iou["ports"] = []
|
||||
for port in self._ports:
|
||||
ports.append(port.dump())
|
||||
|
||||
return iou
|
||||
|
||||
def load(self, node_info):
|
||||
@@ -429,6 +412,8 @@ class IOUDevice(VM):
|
||||
:param node_info: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
super().load(node_info)
|
||||
|
||||
# for backward compatibility
|
||||
vm_id = node_info.get("iou_id")
|
||||
if not vm_id:
|
||||
@@ -460,30 +445,6 @@ class IOUDevice(VM):
|
||||
self.loaded_signal.connect(self._updatePortSettings)
|
||||
self.setup(path, name, vm_id, vm_settings)
|
||||
|
||||
def _updatePortSettings(self):
|
||||
"""
|
||||
Updates port settings when loading a topology.
|
||||
"""
|
||||
|
||||
self.loaded_signal.disconnect(self._updatePortSettings)
|
||||
|
||||
# assign the correct names and IDs to the ports
|
||||
if "ports" in self._node_info:
|
||||
ports = self._node_info["ports"]
|
||||
for topology_port in ports:
|
||||
for port in self._ports:
|
||||
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
|
||||
port.setName(topology_port["name"])
|
||||
port.setId(topology_port["id"])
|
||||
|
||||
# now we can set the node as initialized and trigger the created signal
|
||||
self.setInitialized(True)
|
||||
log.info("IOU device {} has been loaded".format(self.name()))
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
self._loading = False
|
||||
self._node_info = None
|
||||
|
||||
def saveConfig(self):
|
||||
"""
|
||||
Save the configs
|
||||
|
||||
@@ -171,6 +171,14 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
self.uiGeneralgroupBox.hide()
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# rename the label from "Name" to "Template name"
|
||||
self.uiNameLabel.setText("Template name:")
|
||||
|
||||
# load the default name format
|
||||
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
|
||||
|
||||
# load the startup-config and private-config
|
||||
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
|
||||
self.uiPrivateConfigLineEdit.setText(settings["private_config"])
|
||||
@@ -184,6 +192,8 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
if index != -1:
|
||||
self.uiCategoryComboBox.setCurrentIndex(index)
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
self.uiDefaultNameFormatLineEdit.hide()
|
||||
self.uiStartupConfigLabel.hide()
|
||||
self.uiStartupConfigLineEdit.hide()
|
||||
self.uiStartupConfigToolButton.hide()
|
||||
@@ -244,12 +254,21 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
del settings["console"]
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# save the default name format
|
||||
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
|
||||
if '{0}' not in default_name_format and '{id}' not in default_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
|
||||
else:
|
||||
settings["default_name_format"] = default_name_format
|
||||
|
||||
# save the startup-config
|
||||
startup_config = self.uiStartupConfigLineEdit.text().strip()
|
||||
if not startup_config:
|
||||
settings["startup_config"] = ""
|
||||
elif startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
if self._configFileValid(startup_config):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
@@ -259,7 +278,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
if not private_config:
|
||||
settings["private_config"] = ""
|
||||
elif private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
if self._configFileValid(private_config):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
@@ -296,3 +315,11 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
|
||||
settings["ethernet_adapters"] = ethernet_adapters
|
||||
settings["serial_adapters"] = serial_adapters
|
||||
|
||||
def _configFileValid(self, path):
|
||||
"""
|
||||
Return true if it's a valid configuration file
|
||||
"""
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
@@ -70,7 +70,8 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
|
||||
# fill out the General section
|
||||
section_item = self._createSectionItem("General")
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Name:", iou_device["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", iou_device["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", iou_device["default_name_format"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", iou_device["server"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Image:", iou_device["image"]])
|
||||
if iou_device["startup_config"]:
|
||||
@@ -179,7 +180,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
"""
|
||||
|
||||
iou_module = IOU.instance()
|
||||
self._iou_devices = copy.deepcopy(iou_module.iouDevices())
|
||||
self._iou_devices = copy.deepcopy(iou_module.VMs())
|
||||
self._items.clear()
|
||||
|
||||
for key, iou_device in self._iou_devices.items():
|
||||
@@ -199,7 +200,7 @@ class IOUDevicePreferencesPage(QtWidgets.QWidget, Ui_IOUDevicePreferencesPageWid
|
||||
"""
|
||||
|
||||
# self._iouImageSaveSlot()
|
||||
IOU.instance().setIOUDevices(self._iou_devices)
|
||||
IOU.instance().setVMs(self._iou_devices)
|
||||
|
||||
def _imageUploadComplete(self):
|
||||
if self._upload_image_progress_dialog.wasCanceled():
|
||||
|
||||
@@ -36,6 +36,7 @@ if not sys.platform.startswith("linux"):
|
||||
|
||||
IOU_DEVICE_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "IOU{0}",
|
||||
"path": "",
|
||||
"symbol": ":/symbols/multilayer_switch.svg",
|
||||
"category": Node.routers,
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>461</width>
|
||||
<height>520</height>
|
||||
<width>569</width>
|
||||
<height>564</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -43,14 +43,14 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
|
||||
@@ -67,24 +67,24 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiIOUImageLabel">
|
||||
<property name="text">
|
||||
<string>IOU image path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiIOUImageLineEdit"/>
|
||||
@@ -101,14 +101,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiStartupConfigLabel">
|
||||
<property name="text">
|
||||
<string>Startup-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="6" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiStartupConfigLineEdit"/>
|
||||
@@ -125,14 +125,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="uiPrivateConfigLabel">
|
||||
<property name="text">
|
||||
<string>Private-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiPrivateConfigLineEdit"/>
|
||||
@@ -149,20 +149,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiDefaultNameFormatLabel">
|
||||
<property name="text">
|
||||
<string>Default name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/iou/ui/iou_device_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:33 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created: Thu Feb 4 21:08:13 2016
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -12,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_iouDeviceConfigPageWidget(object):
|
||||
def setupUi(self, iouDeviceConfigPageWidget):
|
||||
iouDeviceConfigPageWidget.setObjectName("iouDeviceConfigPageWidget")
|
||||
iouDeviceConfigPageWidget.resize(461, 520)
|
||||
iouDeviceConfigPageWidget.resize(569, 564)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(iouDeviceConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiTabWidget = QtWidgets.QTabWidget(iouDeviceConfigPageWidget)
|
||||
@@ -34,7 +34,7 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
|
||||
self.gridLayout.addWidget(self.uiSymbolLabel, 1, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
|
||||
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
|
||||
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
|
||||
@@ -44,16 +44,16 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
|
||||
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_7, 1, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_7, 2, 1, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout.addWidget(self.uiCategoryLabel, 2, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
|
||||
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralgroupBox)
|
||||
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
|
||||
self.gridLayout.addWidget(self.uiCategoryComboBox, 2, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiCategoryComboBox, 3, 1, 1, 1)
|
||||
self.uiIOUImageLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiIOUImageLabel.setObjectName("uiIOUImageLabel")
|
||||
self.gridLayout.addWidget(self.uiIOUImageLabel, 3, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiIOUImageLabel, 5, 0, 1, 1)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.uiIOUImageLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
|
||||
@@ -63,10 +63,10 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.uiIOUImageToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiIOUImageToolButton.setObjectName("uiIOUImageToolButton")
|
||||
self.horizontalLayout_5.addWidget(self.uiIOUImageToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_5, 3, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_5, 5, 1, 1, 1)
|
||||
self.uiStartupConfigLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiStartupConfigLabel.setObjectName("uiStartupConfigLabel")
|
||||
self.gridLayout.addWidget(self.uiStartupConfigLabel, 4, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiStartupConfigLabel, 6, 0, 1, 1)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.uiStartupConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
|
||||
@@ -76,10 +76,10 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.uiStartupConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiStartupConfigToolButton.setObjectName("uiStartupConfigToolButton")
|
||||
self.horizontalLayout_4.addWidget(self.uiStartupConfigToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_4, 4, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_4, 6, 1, 1, 1)
|
||||
self.uiPrivateConfigLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiPrivateConfigLabel.setObjectName("uiPrivateConfigLabel")
|
||||
self.gridLayout.addWidget(self.uiPrivateConfigLabel, 5, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiPrivateConfigLabel, 7, 0, 1, 1)
|
||||
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
|
||||
self.uiPrivateConfigLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
|
||||
@@ -89,14 +89,21 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.uiPrivateConfigToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiPrivateConfigToolButton.setObjectName("uiPrivateConfigToolButton")
|
||||
self.horizontalLayout_6.addWidget(self.uiPrivateConfigToolButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_6, 5, 1, 1, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_6, 7, 1, 1, 1)
|
||||
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
|
||||
self.gridLayout.addWidget(self.uiConsolePortLabel, 6, 0, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsolePortLabel, 8, 0, 1, 1)
|
||||
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralgroupBox)
|
||||
self.uiConsolePortSpinBox.setMaximum(65535)
|
||||
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
|
||||
self.gridLayout.addWidget(self.uiConsolePortSpinBox, 6, 1, 1, 1)
|
||||
self.gridLayout.addWidget(self.uiConsolePortSpinBox, 8, 1, 1, 1)
|
||||
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralgroupBox)
|
||||
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
|
||||
self.gridLayout.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
|
||||
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralgroupBox)
|
||||
self.uiDefaultNameFormatLineEdit.setText("")
|
||||
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
|
||||
self.gridLayout.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.uiGeneralgroupBox)
|
||||
self.uiOtherSettingsGroupBox = QtWidgets.QGroupBox(self.tab)
|
||||
self.uiOtherSettingsGroupBox.setObjectName("uiOtherSettingsGroupBox")
|
||||
@@ -206,6 +213,7 @@ class Ui_iouDeviceConfigPageWidget(object):
|
||||
self.uiPrivateConfigLabel.setText(_translate("iouDeviceConfigPageWidget", "Private-config:"))
|
||||
self.uiPrivateConfigToolButton.setText(_translate("iouDeviceConfigPageWidget", "&Browse..."))
|
||||
self.uiConsolePortLabel.setText(_translate("iouDeviceConfigPageWidget", "Console port:"))
|
||||
self.uiDefaultNameFormatLabel.setText(_translate("iouDeviceConfigPageWidget", "Default name format:"))
|
||||
self.uiOtherSettingsGroupBox.setTitle(_translate("iouDeviceConfigPageWidget", "Other settings"))
|
||||
self.uiL1KeepalivesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Enable layer 1 keepalive messages (testing only)"))
|
||||
self.uiDefaultValuesCheckBox.setText(_translate("iouDeviceConfigPageWidget", "Use default IOU values for memories"))
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:33 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_IOUDevicePreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, IOUDevicePreferencesPageWidget):
|
||||
IOUDevicePreferencesPageWidget.setObjectName("IOUDevicePreferencesPageWidget")
|
||||
IOUDevicePreferencesPageWidget.resize(505, 350)
|
||||
@@ -74,4 +75,3 @@ class Ui_IOUDevicePreferencesPageWidget(object):
|
||||
self.uiDeleteIOUDevicePushButton.setText(_translate("IOUDevicePreferencesPageWidget", "&Delete"))
|
||||
self.uiIOUDeviceInfoTreeWidget.headerItem().setText(0, _translate("IOUDevicePreferencesPageWidget", "1"))
|
||||
self.uiIOUDeviceInfoTreeWidget.headerItem().setText(1, _translate("IOUDevicePreferencesPageWidget", "2"))
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
<property name="title">
|
||||
<string>Server type</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiRemoteRadioButton">
|
||||
<property name="text">
|
||||
<string>Remote</string>
|
||||
<string>Run the IOU on a remote computers</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@@ -43,30 +43,17 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiVMRadioButton">
|
||||
<property name="text">
|
||||
<string>GNS3 VM</string>
|
||||
<string>Run the IOU on the GNS3 VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiLocalRadioButton">
|
||||
<property name="text">
|
||||
<string>Local</string>
|
||||
<string>Run the IOU on your local computer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_device_wizard.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:33 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -20,20 +19,18 @@ class Ui_IOUDeviceWizard(object):
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
|
||||
self.verticalLayout.addWidget(self.uiRemoteRadioButton)
|
||||
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiVMRadioButton)
|
||||
self.verticalLayout.addWidget(self.uiVMRadioButton)
|
||||
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.verticalLayout.addWidget(self.uiLocalRadioButton)
|
||||
self.gridLayout_2.addWidget(self.uiServerTypeGroupBox, 0, 0, 1, 1)
|
||||
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
|
||||
@@ -87,8 +84,8 @@ class Ui_IOUDeviceWizard(object):
|
||||
self.uiNewImageRadioButton.setChecked(False)
|
||||
self.uiNewImageRadioButton.setObjectName("uiNewImageRadioButton")
|
||||
self.horizontalLayout_2.addWidget(self.uiNewImageRadioButton)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem1)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem)
|
||||
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
|
||||
self.formLayout_8 = QtWidgets.QFormLayout()
|
||||
self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||||
@@ -128,9 +125,9 @@ class Ui_IOUDeviceWizard(object):
|
||||
self.uiServerWizardPage.setTitle(_translate("IOUDeviceWizard", "Server"))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("IOUDeviceWizard", "Please choose a server type to run your new IOU device."))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("IOUDeviceWizard", "Server type"))
|
||||
self.uiRemoteRadioButton.setText(_translate("IOUDeviceWizard", "Remote"))
|
||||
self.uiVMRadioButton.setText(_translate("IOUDeviceWizard", "GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("IOUDeviceWizard", "Local"))
|
||||
self.uiRemoteRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on a remote computers"))
|
||||
self.uiVMRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on the GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("IOUDeviceWizard", "Run the IOU on your local computer"))
|
||||
self.uiRemoteServersGroupBox.setTitle(_translate("IOUDeviceWizard", "Remote servers"))
|
||||
self.uiLoadBalanceCheckBox.setText(_translate("IOUDeviceWizard", "Load balance across all available remote servers"))
|
||||
self.uiRemoteServersLabel.setText(_translate("IOUDeviceWizard", "Run on server:"))
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/iou/ui/iou_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:34 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_IOUPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, IOUPreferencesPageWidget):
|
||||
IOUPreferencesPageWidget.setObjectName("IOUPreferencesPageWidget")
|
||||
IOUPreferencesPageWidget.resize(400, 300)
|
||||
@@ -105,4 +106,3 @@ class Ui_IOUPreferencesPageWidget(object):
|
||||
self.uiIOURCPathToolButton.setText(_translate("IOUPreferencesPageWidget", "&Browse..."))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("IOUPreferencesPageWidget", "General settings"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("IOUPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ Base class (interface) for modules.
|
||||
from ..qt import QtCore
|
||||
from ..local_config import LocalConfig
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,7 +39,6 @@ class Module(QtCore.QObject):
|
||||
super().__init__()
|
||||
LocalConfig.instance().config_changed_signal.connect(self.configChangedSlot)
|
||||
|
||||
|
||||
def configChangedSlot(self):
|
||||
"""
|
||||
Call when the configuration file has changed
|
||||
|
||||
@@ -107,7 +107,7 @@ class Qemu(Module):
|
||||
self._settings["vms"] = list(self._qemu_vms.values())
|
||||
self._saveSettings()
|
||||
|
||||
def qemuVMs(self):
|
||||
def VMs(self):
|
||||
"""
|
||||
Returns QEMU VMs settings.
|
||||
|
||||
@@ -116,11 +116,11 @@ class Qemu(Module):
|
||||
|
||||
return self._qemu_vms
|
||||
|
||||
def setQemuVMs(self, new_qemu_vms):
|
||||
def setVMs(self, new_qemu_vms):
|
||||
"""
|
||||
Sets QEMU VM settings.
|
||||
|
||||
:param new_iou_images: IOS images settings (dictionary)
|
||||
:param new_qemu_vms: Qemu images settings (dictionary)
|
||||
"""
|
||||
|
||||
self._qemu_vms = new_qemu_vms.copy()
|
||||
@@ -216,6 +216,13 @@ class Qemu(Module):
|
||||
else:
|
||||
vm = selected_vms[0]
|
||||
|
||||
linked_base = self._qemu_vms[vm]["linked_base"]
|
||||
if not linked_base:
|
||||
for other_node in self._nodes:
|
||||
if other_node.settings()["name"] == self._qemu_vms[vm]["name"] and \
|
||||
(self._qemu_vms[vm]["server"] == "local" and other_node.server().isLocal() or self._qemu_vms[vm]["server"] == other_node.server().host):
|
||||
raise ModuleError("Sorry a Qemu VM without the linked base setting enabled can only be used once in your topology")
|
||||
|
||||
vm_settings = {}
|
||||
for setting_name, value in self._qemu_vms[vm].items():
|
||||
if setting_name in node.settings() and value != "" and value is not None:
|
||||
@@ -226,12 +233,22 @@ class Qemu(Module):
|
||||
port_name_format = self._qemu_vms[vm]["port_name_format"]
|
||||
port_segment_size = self._qemu_vms[vm]["port_segment_size"]
|
||||
first_port_name = self._qemu_vms[vm]["first_port_name"]
|
||||
|
||||
default_name_format = QEMU_VM_SETTINGS["default_name_format"]
|
||||
if self._qemu_vms[vm]["default_name_format"]:
|
||||
default_name_format = self._qemu_vms[vm]["default_name_format"]
|
||||
if linked_base:
|
||||
default_name_format = default_name_format.replace('{name}', name)
|
||||
name = None
|
||||
|
||||
node.setup(qemu_path,
|
||||
name=name,
|
||||
port_name_format=port_name_format,
|
||||
port_segment_size=port_segment_size,
|
||||
first_port_name=first_port_name,
|
||||
linked_clone=linked_base,
|
||||
additional_settings=vm_settings,
|
||||
base_name=name)
|
||||
default_name_format=default_name_format)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
@@ -241,15 +258,19 @@ class Qemu(Module):
|
||||
log.info("QEMU module reset")
|
||||
self._nodes.clear()
|
||||
|
||||
def getQemuBinariesFromServer(self, server, callback):
|
||||
def getQemuBinariesFromServer(self, server, callback, archs=None):
|
||||
"""
|
||||
Gets the QEMU binaries list from a server.
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
|
||||
"""
|
||||
|
||||
server.get("/qemu/binaries", callback)
|
||||
request_body = None
|
||||
if archs is not None:
|
||||
request_body = {"archs": archs}
|
||||
server.get("/qemu/binaries", callback, body=request_body)
|
||||
|
||||
def getQemuImgBinariesFromServer(self, server, callback):
|
||||
"""
|
||||
@@ -261,6 +282,16 @@ class Qemu(Module):
|
||||
|
||||
server.get(r"/qemu/img-binaries", callback)
|
||||
|
||||
def getQemuCapabilitiesFromServer(self, server, callback):
|
||||
"""
|
||||
Gets the capabilities of Qemu at a server.
|
||||
|
||||
:param server: server to send the request to
|
||||
:param callback: callback for the reply from the server
|
||||
"""
|
||||
|
||||
server.get(r"/qemu/capabilities", callback)
|
||||
|
||||
def createDiskImage(self, server, callback, options):
|
||||
"""
|
||||
Create a disk image on the remote server
|
||||
@@ -323,6 +354,11 @@ class Qemu(Module):
|
||||
from .pages.qemu_vm_preferences_page import QemuVMPreferencesPage
|
||||
return [QemuPreferencesPage, QemuVMPreferencesPage]
|
||||
|
||||
@staticmethod
|
||||
def vmConfigurationPage():
|
||||
from .pages.qemu_vm_configuration_page import QemuVMConfigurationPage
|
||||
return QemuVMConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
|
||||
@@ -164,11 +164,14 @@ class QemuImageWizard(QtWidgets.QWizard, Ui_QemuImageWizard):
|
||||
|
||||
cluster_size = self.uiQcow2ClusterSizeComboBox.currentText()
|
||||
if not '<default>' == cluster_size:
|
||||
options["cluster_size"] = cluster_size
|
||||
if cluster_size.endswith('k'):
|
||||
options["cluster_size"] = int(cluster_size[:-1]) * 1024
|
||||
else:
|
||||
options["cluster_size"] = int(cluster_size)
|
||||
|
||||
refcount_bits = self.uiRefcountEntrySizeComboBox.currentText()
|
||||
if not '<default>' == refcount_bits:
|
||||
options["refcount_bits"] = refcount_bits
|
||||
options["refcount_bits"] = int(refcount_bits)
|
||||
|
||||
options["lazy_refcounts"] = 'on' if QtCore.Qt.Checked == self.uiLazyRefcountsCheckBox.checkState() else 'off'
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from ..ui.qemu_vm_wizard_ui import Ui_QemuVMWizard
|
||||
from ..pages.qemu_vm_configuration_page import QemuVMConfigurationPage
|
||||
from .qemu_image_wizard import QemuImageWizard
|
||||
|
||||
|
||||
class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
|
||||
"""
|
||||
@@ -70,7 +71,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
:param vm_type: type of VM
|
||||
"""
|
||||
|
||||
self.uiASADeprecatedWarningLabel.hide()
|
||||
if vm_type == "IOSv":
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/iosv_virl.svg"))
|
||||
self.uiNameLineEdit.setText("vIOS")
|
||||
@@ -86,7 +86,6 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
elif vm_type == "ASA 8.4(2)":
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/asa.svg"))
|
||||
self.uiNameLineEdit.setText("ASA")
|
||||
self.uiASADeprecatedWarningLabel.show()
|
||||
elif vm_type == "IDS":
|
||||
self.setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(":/symbols/ids.svg"))
|
||||
self.uiNameLineEdit.setText("IDS")
|
||||
@@ -108,7 +107,10 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
if not self.uiQemuListComboBox.count():
|
||||
QtWidgets.QMessageBox.critical(self, "QEMU binaries", "Sorry, no QEMU binary has been found. Please make sure QEMU is installed before continuing")
|
||||
return False
|
||||
qemu_path = self.uiQemuListComboBox.itemData(self.uiQemuListComboBox.currentIndex())
|
||||
|
||||
if (sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu binaries", "This version of qemu is obsolete and provided only for compatibility with old GNS3 versions.\nPlease use Qemu in the GNS3 VM for full Qemu support.")
|
||||
return True
|
||||
|
||||
def initializePage(self, page_id):
|
||||
@@ -203,9 +205,11 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
elif self.uiTypeComboBox.currentText() == "ASA 8.4(2)":
|
||||
settings["adapters"] = 4
|
||||
settings["initrd"] = self.uiInitrdImageLineEdit.text()
|
||||
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text()
|
||||
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
|
||||
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt"
|
||||
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
|
||||
settings["options"] = "-no-kvm -icount auto -hdachs 980,16,32"
|
||||
|
||||
if not sys.platform.startswith("darwin"):
|
||||
settings["cpu_throttling"] = 80 # limit to 80% CPU usage
|
||||
settings["process_priority"] = "low"
|
||||
@@ -224,7 +228,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
|
||||
|
||||
if "options" not in settings:
|
||||
settings["options"] = ""
|
||||
if server == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.13.0\qemu-system-i386w.exe")) or \
|
||||
if server == "local" and (sys.platform.startswith("win") and qemu_path.endswith(r"qemu-0.11.0\qemu.exe")) or \
|
||||
(sys.platform.startswith("darwin") and "GNS3.app" in qemu_path):
|
||||
settings["options"] += " -vga none -vnc none"
|
||||
settings["legacy_networking"] = True
|
||||
|
||||
@@ -22,12 +22,11 @@ Configuration page for QEMU VMs.
|
||||
import os
|
||||
import re
|
||||
|
||||
from functools import partial
|
||||
from collections import OrderedDict
|
||||
from gns3.modules.qemu.dialogs.qemu_image_wizard import QemuImageWizard
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.node import Node
|
||||
from gns3.qt import QtGui, QtCore, QtWidgets
|
||||
from gns3.qt import QtGui, QtCore, QtWidgets, qpartial
|
||||
from gns3.servers import Servers
|
||||
from gns3.modules.module_error import ModuleError
|
||||
from gns3.dialogs.node_properties_dialog import ConfigurationError
|
||||
@@ -53,6 +52,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
self.uiBootPriorityComboBox.addItem("HDD", "c")
|
||||
self.uiBootPriorityComboBox.addItem("CD/DVD-ROM", "d")
|
||||
self.uiBootPriorityComboBox.addItem("Network", "n")
|
||||
self.uiBootPriorityComboBox.addItem("HDD or Network", "cn")
|
||||
self.uiBootPriorityComboBox.addItem("HDD or CD/DVD-ROM", "cd")
|
||||
|
||||
self.uiHdaDiskImageToolButton.clicked.connect(self._hdaDiskImageBrowserSlot)
|
||||
self.uiHdbDiskImageToolButton.clicked.connect(self._hdbDiskImageBrowserSlot)
|
||||
@@ -268,7 +270,6 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
if qemu_path and "/" not in qemu_path and "\\" not in qemu_path:
|
||||
self.uiQemuListComboBox.addItem("{path}".format(path=qemu_path), qemu_path)
|
||||
|
||||
|
||||
index = self.uiQemuListComboBox.findData("{path}".format(path=qemu_path))
|
||||
if index != -1:
|
||||
self.uiQemuListComboBox.setCurrentIndex(index)
|
||||
@@ -310,12 +311,15 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
else:
|
||||
self._server = Servers.instance().getServerFromString(settings["server"])
|
||||
|
||||
callback = partial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
|
||||
try:
|
||||
Qemu.instance().getQemuBinariesFromServer(self._server, callback)
|
||||
except ModuleError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "Error while getting the QEMU binaries: {}".format(e))
|
||||
self.uiQemuListComboBox.clear()
|
||||
if self._server is None:
|
||||
QtWidgets.QMessageBox.warning(self, "Qemu", "Server {} is not running, cannot retrieve the QEMU binaries list".format(settings["server"]))
|
||||
else:
|
||||
callback = qpartial(self._getQemuBinariesFromServerCallback, qemu_path=settings["qemu_path"])
|
||||
try:
|
||||
Qemu.instance().getQemuBinariesFromServer(self._server, callback)
|
||||
except ModuleError as e:
|
||||
QtWidgets.QMessageBox.critical(self, "Qemu", "Error while getting the QEMU binaries list: {}".format(e))
|
||||
self.uiQemuListComboBox.clear()
|
||||
|
||||
if not group:
|
||||
# set the device name
|
||||
@@ -325,6 +329,12 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
else:
|
||||
self.uiConsolePortLabel.hide()
|
||||
self.uiConsolePortSpinBox.hide()
|
||||
|
||||
if "linked_base" in settings:
|
||||
self.uiBaseVMCheckBox.setChecked(settings["linked_base"])
|
||||
else:
|
||||
self.uiBaseVMCheckBox.hide()
|
||||
|
||||
self.uiHdaDiskImageLineEdit.setText(settings["hda_disk_image"])
|
||||
self.uiHdbDiskImageLineEdit.setText(settings["hdb_disk_image"])
|
||||
self.uiHdcDiskImageLineEdit.setText(settings["hdc_disk_image"])
|
||||
@@ -351,6 +361,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiKernelImageToolButton.hide()
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# rename the label from "Name" to "Template name"
|
||||
self.uiNameLabel.setText("Template name:")
|
||||
|
||||
# load the default name format
|
||||
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
|
||||
|
||||
# load the symbol
|
||||
self.uiSymbolLineEdit.setText(settings["symbol"])
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
|
||||
@@ -364,6 +382,8 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
|
||||
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
self.uiDefaultNameFormatLineEdit.hide()
|
||||
self.uiSymbolLabel.hide()
|
||||
self.uiSymbolLineEdit.hide()
|
||||
self.uiSymbolToolButton.hide()
|
||||
@@ -437,6 +457,9 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
if "console" in settings:
|
||||
settings["console"] = self.uiConsolePortSpinBox.value()
|
||||
if "linked_base" in settings:
|
||||
settings["linked_base"] = self.uiBaseVMCheckBox.isChecked()
|
||||
|
||||
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text().strip()
|
||||
settings["hdb_disk_image"] = self.uiHdbDiskImageLineEdit.text().strip()
|
||||
settings["hdc_disk_image"] = self.uiHdcDiskImageLineEdit.text().strip()
|
||||
@@ -479,6 +502,15 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
del settings["mac_address"]
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# save the default name format
|
||||
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
|
||||
if '{0}' not in default_name_format and '{id}' not in default_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
|
||||
else:
|
||||
settings["default_name_format"] = default_name_format
|
||||
|
||||
symbol_path = self.uiSymbolLineEdit.text()
|
||||
pixmap = QtGui.QPixmap(symbol_path)
|
||||
if pixmap.isNull():
|
||||
@@ -488,14 +520,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
|
||||
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
port_name_format = self.uiPortNameFormatLineEdit.text()
|
||||
if '{0}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
|
||||
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
|
||||
else:
|
||||
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
|
||||
|
||||
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
|
||||
if port_segment_size and '{1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
|
||||
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
|
||||
else:
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
Configuration page for QEMU VM preferences.
|
||||
"""
|
||||
|
||||
import ntpath
|
||||
import os
|
||||
import copy
|
||||
import sys
|
||||
|
||||
from gns3.qt import QtCore, QtGui, QtWidgets
|
||||
from gns3.main_window import MainWindow
|
||||
@@ -69,11 +67,14 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
|
||||
|
||||
# fill out the General section
|
||||
section_item = self._createSectionItem("General")
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["VM name:", qemu_vm["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", qemu_vm["name"]])
|
||||
if qemu_vm["linked_base"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", qemu_vm["default_name_format"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", qemu_vm["server"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Console type:", qemu_vm["console_type"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["CPUs:", str(qemu_vm["cpus"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Memory:", "{} MB".format(qemu_vm["ram"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Linked base VM:", "{}".format(qemu_vm["linked_base"])])
|
||||
|
||||
if qemu_vm["qemu_path"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["QEMU binary:", os.path.basename(qemu_vm["qemu_path"])])
|
||||
@@ -222,7 +223,7 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
|
||||
"""
|
||||
|
||||
qemu_module = Qemu.instance()
|
||||
self._qemu_vms = copy.deepcopy(qemu_module.qemuVMs())
|
||||
self._qemu_vms = copy.deepcopy(qemu_module.VMs())
|
||||
self._items.clear()
|
||||
|
||||
for key, qemu_vm in self._qemu_vms.items():
|
||||
@@ -241,4 +242,4 @@ class QemuVMPreferencesPage(QtWidgets.QWidget, Ui_QemuVMPreferencesPageWidget):
|
||||
Saves the QEMU VM preferences.
|
||||
"""
|
||||
|
||||
Qemu.instance().setQemuVMs(self._qemu_vms)
|
||||
Qemu.instance().setVMs(self._qemu_vms)
|
||||
|
||||
@@ -48,8 +48,10 @@ class QemuVM(VM):
|
||||
self._port_name_format = None
|
||||
self._port_segment_size = 0
|
||||
self._first_port_name = None
|
||||
self._linked_clone = True
|
||||
|
||||
self._settings = {"name": "",
|
||||
"usage": "",
|
||||
"qemu_path": "",
|
||||
"hda_disk_image": "",
|
||||
"hdb_disk_image": "",
|
||||
@@ -97,7 +99,14 @@ class QemuVM(VM):
|
||||
if self._first_port_name and adapter_number == 0:
|
||||
port_name = self._first_port_name
|
||||
else:
|
||||
port_name = self._port_name_format.format(interface_number, segment_number)
|
||||
port_name = self._port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
port0=interface_number,
|
||||
port1=1 + interface_number,
|
||||
segment0=segment_number,
|
||||
segment1=1 + segment_number
|
||||
)
|
||||
interface_number += 1
|
||||
if self._port_segment_size and interface_number % self._port_segment_size == 0:
|
||||
segment_number += 1
|
||||
@@ -109,8 +118,8 @@ class QemuVM(VM):
|
||||
self._ports.append(new_port)
|
||||
log.debug("Adapter {} with port {} has been added".format(adapter_number, port_name))
|
||||
|
||||
def setup(self, qemu_path, name=None, vm_id=None, port_name_format="Ethernet{0}",
|
||||
port_segment_size=0, first_port_name="", additional_settings={}, base_name=None):
|
||||
def setup(self, qemu_path, name=None, vm_id=None, port_name_format="Ethernet{0}", port_segment_size=0,
|
||||
first_port_name="", linked_clone=True, additional_settings={}, default_name_format=None):
|
||||
"""
|
||||
Setups this QEMU VM.
|
||||
|
||||
@@ -119,16 +128,19 @@ class QemuVM(VM):
|
||||
"""
|
||||
|
||||
# let's create a unique name if none has been chosen
|
||||
if not name:
|
||||
name = self.allocateName(base_name + "-")
|
||||
if not name and linked_clone:
|
||||
name = self.allocateName(default_name_format)
|
||||
|
||||
if not name:
|
||||
self.error_signal.emit(self.id(), "could not allocate a name for this QEMU VM")
|
||||
return
|
||||
|
||||
self.setName(name)
|
||||
self._settings["name"] = name
|
||||
self._linked_clone = linked_clone
|
||||
params = {"name": name,
|
||||
"qemu_path": qemu_path}
|
||||
"qemu_path": qemu_path,
|
||||
"linked_clone": linked_clone}
|
||||
|
||||
if vm_id:
|
||||
params["vm_id"] = vm_id
|
||||
@@ -137,7 +149,7 @@ class QemuVM(VM):
|
||||
self._port_segment_size = port_segment_size
|
||||
self._first_port_name = first_port_name
|
||||
params.update(additional_settings)
|
||||
self.httpPost("/qemu/vms", self._setupCallback, body=params)
|
||||
self.httpPost("/qemu/vms", self._setupCallback, body=params, progressText="Creating {}".format(name))
|
||||
|
||||
def _setupCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
@@ -161,42 +173,13 @@ class QemuVM(VM):
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
|
||||
for image_field in ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "initrd", "kernel_image"]:
|
||||
for image_field in ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "initrd", "kernel_image", "cdrom_image"]:
|
||||
if image_field in result and result[image_field] is not None and result[image_field] != "":
|
||||
# The image is missing on remote server
|
||||
field = "{}_md5sum".format(image_field)
|
||||
if field not in result or result[field] is None or len(result[field]) == 0:
|
||||
ImageManager.instance().addMissingImage(result[image_field], self._server, "QEMU")
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this QEMU VM instance.
|
||||
"""
|
||||
|
||||
log.debug("QEMU VM instance {} is being deleted".format(self.name()))
|
||||
# first delete all the links attached to this node
|
||||
self.delete_links_signal.emit()
|
||||
if self._vm_id:
|
||||
self.httpDelete("/qemu/vms/{vm_id}".format(vm_id=self._vm_id), self._deleteCallback)
|
||||
else:
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
|
||||
def _deleteCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for delete.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
log.info("{} has been deleted".format(self.name()))
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
|
||||
def update(self, new_settings):
|
||||
"""
|
||||
Updates the settings for this QEMU VM.
|
||||
@@ -213,9 +196,6 @@ class QemuVM(VM):
|
||||
if name in self._settings and self._settings[name] != value:
|
||||
params[name] = value
|
||||
|
||||
if "cloud_path" in new_settings:
|
||||
params["cloud_path"] = self._settings["cloud_path"] = new_settings.pop("cloud_path")
|
||||
|
||||
log.debug("{} is updating settings: {}".format(self.name(), params))
|
||||
self.httpPut("/qemu/vms/{vm_id}".format(vm_id=self._vm_id), self._updateCallback, body=params)
|
||||
|
||||
@@ -227,10 +207,8 @@ class QemuVM(VM):
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
return
|
||||
if not super()._updateCallback(result, error=error, **kwargs):
|
||||
return False
|
||||
|
||||
updated = False
|
||||
nb_adapters_changed = False
|
||||
@@ -316,13 +294,10 @@ class QemuVM(VM):
|
||||
:returns: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
qemu_vm = {"id": self.id(),
|
||||
"vm_id": self._vm_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {},
|
||||
"port_name_format": self._port_name_format,
|
||||
"server_id": self._server.id()}
|
||||
qemu_vm = super().dump()
|
||||
qemu_vm["vm_id"] = self._vm_id
|
||||
qemu_vm["linked_clone"] = self._linked_clone
|
||||
qemu_vm["port_name_format"] = self._port_name_format
|
||||
|
||||
if self._port_segment_size:
|
||||
qemu_vm["port_segment_size"] = self._port_segment_size
|
||||
@@ -334,12 +309,6 @@ class QemuVM(VM):
|
||||
if value is not None and value != "":
|
||||
qemu_vm["properties"][name] = value
|
||||
|
||||
# add the ports
|
||||
if self._ports:
|
||||
ports = qemu_vm["ports"] = []
|
||||
for port in self._ports:
|
||||
ports.append(port.dump())
|
||||
|
||||
return qemu_vm
|
||||
|
||||
def info(self):
|
||||
@@ -375,6 +344,9 @@ class QemuVM(VM):
|
||||
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
|
||||
port_description=port.description())
|
||||
|
||||
if "usage" in self._settings and len(self._settings["usage"]) > 0:
|
||||
info += " Usage: {}\n".format(self._settings["usage"])
|
||||
|
||||
return info + port_info
|
||||
|
||||
def load(self, node_info):
|
||||
@@ -385,10 +357,12 @@ class QemuVM(VM):
|
||||
:param node_info: representation of the node (dictionary)
|
||||
"""
|
||||
|
||||
super().load(node_info)
|
||||
# for backward compatibility
|
||||
vm_id = node_info.get("qemu_id")
|
||||
if not vm_id:
|
||||
vm_id = node_info.get("vm_id")
|
||||
linked_clone = node_info.get("linked_clone", True)
|
||||
port_name_format = node_info.get("port_name_format", "Ethernet{0}")
|
||||
port_segment_size = node_info.get("port_segment_size", 0)
|
||||
first_port_name = node_info.get("first_port_name", "")
|
||||
@@ -402,35 +376,7 @@ class QemuVM(VM):
|
||||
qemu_path = vm_settings.pop("qemu_path")
|
||||
log.info("QEMU VM {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self._loading = True
|
||||
self._node_info = node_info
|
||||
self.loaded_signal.connect(self._updatePortSettings)
|
||||
self.setup(qemu_path, name, vm_id, port_name_format, port_segment_size, first_port_name, vm_settings)
|
||||
|
||||
def _updatePortSettings(self):
|
||||
"""
|
||||
Updates port settings when loading a topology.
|
||||
"""
|
||||
|
||||
self.loaded_signal.disconnect(self._updatePortSettings)
|
||||
|
||||
# assign the correct names and IDs to the ports
|
||||
if "ports" in self._node_info:
|
||||
ports = self._node_info["ports"]
|
||||
for topology_port in ports:
|
||||
for port in self._ports:
|
||||
adapter_number = topology_port.get("adapter_number", topology_port["port_number"])
|
||||
if adapter_number == port.adapterNumber():
|
||||
port.setName(topology_port["name"])
|
||||
port.setId(topology_port["id"])
|
||||
|
||||
# now we can set the node as initialized and trigger the created signal
|
||||
self.setInitialized(True)
|
||||
log.info("QEMU VM {} has been loaded".format(self.name()))
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
self._loading = False
|
||||
self._node_info = None
|
||||
self.setup(qemu_path, name, vm_id, port_name_format, port_segment_size, first_port_name, linked_clone, vm_settings)
|
||||
|
||||
def name(self):
|
||||
"""
|
||||
|
||||
@@ -28,6 +28,8 @@ QEMU_SETTINGS = {
|
||||
|
||||
QEMU_VM_SETTINGS = {
|
||||
"name": "",
|
||||
"default_name_format": "{name}-{0}",
|
||||
"usage": "",
|
||||
"symbol": ":/symbols/qemu_guest.svg",
|
||||
"category": Node.end_devices,
|
||||
"port_name_format": "Ethernet{0}",
|
||||
@@ -59,5 +61,6 @@ QEMU_VM_SETTINGS = {
|
||||
"kernel_image": "",
|
||||
"initrd": "",
|
||||
"kernel_command_line": "",
|
||||
"linked_base": True,
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_image_wizard.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:34 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuImageWizard(object):
|
||||
|
||||
def setupUi(self, QemuImageWizard):
|
||||
QemuImageWizard.setObjectName("QemuImageWizard")
|
||||
QemuImageWizard.resize(535, 369)
|
||||
@@ -318,11 +319,11 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiFormatQcow2Radio.setToolTip(_translate("QemuImageWizard", "Qcow2 is the current Qemu format, supporting many special features."))
|
||||
self.uiFormatQcowRadio.setToolTip(_translate("QemuImageWizard", "Qcow is a legacy Qemu format that is also supported by VirtualBox."))
|
||||
self.uiFormatVhdRadio.setToolTip(_translate("QemuImageWizard", "VHD is the format used by Microsoft VirtualPC, and is also supported by Qemu and VirtualBox.\n"
|
||||
"On Windows 7 and above, it can be mounted on the host PC."))
|
||||
"On Windows 7 and above, it can be mounted on the host PC."))
|
||||
self.uiFormatVdiRadio.setToolTip(_translate("QemuImageWizard", "VDI is the native format of VirtualBox"))
|
||||
self.uiFormatVmdkRadio.setToolTip(_translate("QemuImageWizard", "VMDK is the native format for VMware and is also supported by Qemu and VirtualBox."))
|
||||
self.uiFormatRawRadio.setToolTip(_translate("QemuImageWizard", "Raw image files represent the actual data on the image, with zero special features.\n"
|
||||
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
|
||||
"It can easily be converted to various other formats by various utilities, making it the most portable format."))
|
||||
self.uiFormatRawRadio.setText(_translate("QemuImageWizard", "Raw"))
|
||||
self.uiQcow2OptionsWizardPage.setTitle(_translate("QemuImageWizard", "Qcow2 options"))
|
||||
self.uiSizeOptionsGroupBox.setTitle(_translate("QemuImageWizard", "Size options"))
|
||||
@@ -330,12 +331,12 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiQcow2PreallocationOffRadio.setToolTip(_translate("QemuImageWizard", "The file only takes as much space from the host as needed. The VM will still see the full capacity you specify."))
|
||||
self.uiQcow2PreallocationOffRadio.setText(_translate("QemuImageWizard", "off"))
|
||||
self.uiQcow2PreallocationMetadataRadio.setToolTip(_translate("QemuImageWizard", "Same as \"off\", but preallocates enough space to hold any potenial metadata for the HDD.\n"
|
||||
"This improves performance when the image file needs to grow."))
|
||||
"This improves performance when the image file needs to grow."))
|
||||
self.uiQcow2PreallocationMetadataRadio.setText(_translate("QemuImageWizard", "metadata"))
|
||||
self.uiQcow2PreallocationFallocRadio.setToolTip(_translate("QemuImageWizard", "Same as \"full\", but uses C\'s posix_fallocate() if available on the host, instead of zero filling the file."))
|
||||
self.uiQcow2PreallocationFallocRadio.setText(_translate("QemuImageWizard", "falloc"))
|
||||
self.uiQcow2PreallocationFullRadio.setToolTip(_translate("QemuImageWizard", "The file will start off at the full size you specify.\n"
|
||||
"Free space will be zero filled."))
|
||||
"Free space will be zero filled."))
|
||||
self.uiQcow2PreallocationFullRadio.setText(_translate("QemuImageWizard", "full"))
|
||||
self.uiClusterSizeLabel.setText(_translate("QemuImageWizard", "Cluster size:"))
|
||||
self.uiQcow2ClusterSizeComboBox.setItemText(0, _translate("QemuImageWizard", "<default>"))
|
||||
@@ -395,4 +396,3 @@ class Ui_QemuImageWizard(object):
|
||||
self.uiLocationBrowseToolButton.setText(_translate("QemuImageWizard", "Browse"))
|
||||
self.uiSizeLabel.setText(_translate("QemuImageWizard", "Disk size:"))
|
||||
self.uiSizeSpinBox.setSuffix(_translate("QemuImageWizard", " MiB"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:34 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, QemuPreferencesPageWidget):
|
||||
QemuPreferencesPageWidget.setObjectName("QemuPreferencesPageWidget")
|
||||
QemuPreferencesPageWidget.resize(366, 336)
|
||||
@@ -53,4 +54,3 @@ class Ui_QemuPreferencesPageWidget(object):
|
||||
self.uiKVMAccelerationCheckBox.setText(_translate("QemuPreferencesPageWidget", "Enable KVM acceleration"))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiServerSettingsTabWidget), _translate("QemuPreferencesPageWidget", "General settings"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("QemuPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>611</width>
|
||||
<height>524</height>
|
||||
<width>574</width>
|
||||
<height>523</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -24,57 +24,6 @@
|
||||
<string>General settings</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QComboBox" name="uiQemuListComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="2">
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>263</width>
|
||||
<height>94</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiQemuListLabel">
|
||||
<property name="text">
|
||||
<string>Qemu binary:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiRamLabel">
|
||||
<property name="text">
|
||||
@@ -82,23 +31,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QSpinBox" name="uiRamSpinBox">
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
|
||||
@@ -115,34 +48,36 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>VM name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiConsoleTypeLabel">
|
||||
<property name="text">
|
||||
<string>Console type:</string>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="uiRamSpinBox">
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>32</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="uiConsoleTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -156,8 +91,12 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<widget class="QComboBox" name="uiBootPriorityComboBox"/>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="uiConsoleTypeLabel">
|
||||
<property name="text">
|
||||
<string>Console type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="uiBootPriorityLabel">
|
||||
@@ -166,14 +105,28 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiCPULabel">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiQemuListLabel">
|
||||
<property name="text">
|
||||
<string>vCPUs:</string>
|
||||
<string>Qemu binary:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiCPUSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
@@ -183,6 +136,63 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="uiBootPriorityComboBox"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="uiQemuListComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>263</width>
|
||||
<height>94</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiCPULabel">
|
||||
<property name="text">
|
||||
<string>vCPUs:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiDefaultNameFormatLabel">
|
||||
<property name="text">
|
||||
<string>Default name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="uiHddTab">
|
||||
@@ -523,6 +533,9 @@
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
@@ -745,10 +758,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiQemuOptionsLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiBaseVMCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use as a linked base VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiACPIShutdownCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable ACPI shutdown (experimental)</string>
|
||||
@@ -759,6 +782,7 @@
|
||||
<zorder>uiQemuOptionsLineEdit</zorder>
|
||||
<zorder>uiQemuOptionsLabel</zorder>
|
||||
<zorder>uiACPIShutdownCheckBox</zorder>
|
||||
<zorder>uiBaseVMCheckBox</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
|
||||
#
|
||||
# Created: Wed Aug 5 15:49:25 2015
|
||||
# Created: Thu Feb 4 21:54:18 2016
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@@ -12,7 +12,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_QemuVMConfigPageWidget(object):
|
||||
def setupUi(self, QemuVMConfigPageWidget):
|
||||
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
|
||||
QemuVMConfigPageWidget.resize(611, 524)
|
||||
QemuVMConfigPageWidget.resize(574, 523)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(QemuVMConfigPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiQemutabWidget = QtWidgets.QTabWidget(QemuVMConfigPageWidget)
|
||||
@@ -21,38 +21,9 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiGeneralSettingsTab.setObjectName("uiGeneralSettingsTab")
|
||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.uiGeneralSettingsTab)
|
||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
|
||||
self.gridLayout_4.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
|
||||
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 2, 1, 1)
|
||||
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiConsolePortSpinBox.setMaximum(65535)
|
||||
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiConsolePortSpinBox, 9, 2, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_4.addItem(spacerItem, 11, 2, 1, 1)
|
||||
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 2)
|
||||
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
|
||||
self.gridLayout_4.addWidget(self.uiConsolePortLabel, 9, 0, 1, 2)
|
||||
self.uiRamLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiRamLabel.setObjectName("uiRamLabel")
|
||||
self.gridLayout_4.addWidget(self.uiRamLabel, 4, 0, 1, 1)
|
||||
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiRamSpinBox.setMinimum(32)
|
||||
self.uiRamSpinBox.setMaximum(65535)
|
||||
self.uiRamSpinBox.setProperty("value", 256)
|
||||
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 2, 1, 1)
|
||||
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
|
||||
self.uiSymbolLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
|
||||
@@ -62,41 +33,76 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiSymbolToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
|
||||
self.uiSymbolToolButton.setObjectName("uiSymbolToolButton")
|
||||
self.horizontalLayout_7.addWidget(self.uiSymbolToolButton)
|
||||
self.gridLayout_4.addLayout(self.horizontalLayout_7, 2, 2, 1, 1)
|
||||
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout_4.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout_4.addWidget(self.uiNameLineEdit, 0, 2, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout_4.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
|
||||
self.gridLayout_4.addLayout(self.horizontalLayout_7, 2, 1, 1, 1)
|
||||
self.uiCategoryComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiCategoryComboBox.setObjectName("uiCategoryComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiCategoryComboBox, 3, 2, 1, 1)
|
||||
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
|
||||
self.gridLayout_4.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
|
||||
self.gridLayout_4.addWidget(self.uiCategoryComboBox, 3, 1, 1, 1)
|
||||
self.uiRamSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiRamSpinBox.setMinimum(32)
|
||||
self.uiRamSpinBox.setMaximum(65535)
|
||||
self.uiRamSpinBox.setProperty("value", 256)
|
||||
self.uiRamSpinBox.setObjectName("uiRamSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiRamSpinBox, 4, 1, 1, 1)
|
||||
self.uiSymbolLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiSymbolLabel.setObjectName("uiSymbolLabel")
|
||||
self.gridLayout_4.addWidget(self.uiSymbolLabel, 2, 0, 1, 1)
|
||||
self.uiNameLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
|
||||
self.uiNameLineEdit.setObjectName("uiNameLineEdit")
|
||||
self.gridLayout_4.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
self.uiConsoleTypeComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiConsoleTypeComboBox.setObjectName("uiConsoleTypeComboBox")
|
||||
self.uiConsoleTypeComboBox.addItem("")
|
||||
self.uiConsoleTypeComboBox.addItem("")
|
||||
self.gridLayout_4.addWidget(self.uiConsoleTypeComboBox, 8, 2, 1, 1)
|
||||
self.uiBootPriorityComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiBootPriorityComboBox.setObjectName("uiBootPriorityComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiBootPriorityComboBox, 7, 2, 1, 1)
|
||||
self.gridLayout_4.addWidget(self.uiConsoleTypeComboBox, 8, 1, 1, 1)
|
||||
self.uiConsoleTypeLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiConsoleTypeLabel.setObjectName("uiConsoleTypeLabel")
|
||||
self.gridLayout_4.addWidget(self.uiConsoleTypeLabel, 8, 0, 1, 1)
|
||||
self.uiBootPriorityLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiBootPriorityLabel.setObjectName("uiBootPriorityLabel")
|
||||
self.gridLayout_4.addWidget(self.uiBootPriorityLabel, 7, 0, 1, 1)
|
||||
self.uiCPULabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiCPULabel.setObjectName("uiCPULabel")
|
||||
self.gridLayout_4.addWidget(self.uiCPULabel, 5, 0, 1, 1)
|
||||
self.uiQemuListLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListLabel, 6, 0, 1, 1)
|
||||
self.uiConsolePortLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiConsolePortLabel.setObjectName("uiConsolePortLabel")
|
||||
self.gridLayout_4.addWidget(self.uiConsolePortLabel, 9, 0, 1, 1)
|
||||
self.uiCategoryLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiCategoryLabel.setObjectName("uiCategoryLabel")
|
||||
self.gridLayout_4.addWidget(self.uiCategoryLabel, 3, 0, 1, 1)
|
||||
self.uiCPUSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiCPUSpinBox.setMinimum(1)
|
||||
self.uiCPUSpinBox.setMaximum(255)
|
||||
self.uiCPUSpinBox.setObjectName("uiCPUSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 2, 1, 1)
|
||||
self.gridLayout_4.addWidget(self.uiCPUSpinBox, 5, 1, 1, 1)
|
||||
self.uiConsolePortSpinBox = QtWidgets.QSpinBox(self.uiGeneralSettingsTab)
|
||||
self.uiConsolePortSpinBox.setMaximum(65535)
|
||||
self.uiConsolePortSpinBox.setObjectName("uiConsolePortSpinBox")
|
||||
self.gridLayout_4.addWidget(self.uiConsolePortSpinBox, 9, 1, 1, 1)
|
||||
self.uiBootPriorityComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
self.uiBootPriorityComboBox.setObjectName("uiBootPriorityComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiBootPriorityComboBox, 7, 1, 1, 1)
|
||||
self.uiNameLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiNameLabel.setObjectName("uiNameLabel")
|
||||
self.gridLayout_4.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiGeneralSettingsTab)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
|
||||
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
|
||||
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
|
||||
self.gridLayout_4.addWidget(self.uiQemuListComboBox, 6, 1, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(263, 94, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_4.addItem(spacerItem, 10, 1, 1, 1)
|
||||
self.uiCPULabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiCPULabel.setObjectName("uiCPULabel")
|
||||
self.gridLayout_4.addWidget(self.uiCPULabel, 5, 0, 1, 1)
|
||||
self.uiDefaultNameFormatLabel = QtWidgets.QLabel(self.uiGeneralSettingsTab)
|
||||
self.uiDefaultNameFormatLabel.setObjectName("uiDefaultNameFormatLabel")
|
||||
self.gridLayout_4.addWidget(self.uiDefaultNameFormatLabel, 1, 0, 1, 1)
|
||||
self.uiDefaultNameFormatLineEdit = QtWidgets.QLineEdit(self.uiGeneralSettingsTab)
|
||||
self.uiDefaultNameFormatLineEdit.setObjectName("uiDefaultNameFormatLineEdit")
|
||||
self.gridLayout_4.addWidget(self.uiDefaultNameFormatLineEdit, 1, 1, 1, 1)
|
||||
self.uiQemutabWidget.addTab(self.uiGeneralSettingsTab, "")
|
||||
self.uiHddTab = QtWidgets.QWidget()
|
||||
self.uiHddTab.setObjectName("uiHddTab")
|
||||
@@ -378,10 +384,14 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLabel, 0, 0, 1, 1)
|
||||
self.uiQemuOptionsLineEdit = QtWidgets.QLineEdit(self.groupBox)
|
||||
self.uiQemuOptionsLineEdit.setObjectName("uiQemuOptionsLineEdit")
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 2, 1, 1)
|
||||
self.gridLayout_3.addWidget(self.uiQemuOptionsLineEdit, 0, 1, 1, 1)
|
||||
self.uiBaseVMCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiBaseVMCheckBox.setEnabled(True)
|
||||
self.uiBaseVMCheckBox.setObjectName("uiBaseVMCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiBaseVMCheckBox, 1, 0, 1, 2)
|
||||
self.uiACPIShutdownCheckBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
self.uiACPIShutdownCheckBox.setObjectName("uiACPIShutdownCheckBox")
|
||||
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 1, 0, 1, 3)
|
||||
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 2, 0, 1, 2)
|
||||
self.verticalLayout_2.addWidget(self.groupBox)
|
||||
spacerItem4 = QtWidgets.QSpacerItem(20, 90, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_2.addItem(spacerItem4)
|
||||
@@ -396,19 +406,20 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
def retranslateUi(self, QemuVMConfigPageWidget):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
QemuVMConfigPageWidget.setWindowTitle(_translate("QemuVMConfigPageWidget", "QEMU VM configuration"))
|
||||
self.uiSymbolLabel.setText(_translate("QemuVMConfigPageWidget", "Symbol:"))
|
||||
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
|
||||
self.uiConsolePortLabel.setText(_translate("QemuVMConfigPageWidget", "Console port:"))
|
||||
self.uiRamLabel.setText(_translate("QemuVMConfigPageWidget", "RAM:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
|
||||
self.uiSymbolToolButton.setText(_translate("QemuVMConfigPageWidget", "&Browse..."))
|
||||
self.uiNameLabel.setText(_translate("QemuVMConfigPageWidget", "VM name:"))
|
||||
self.uiCategoryLabel.setText(_translate("QemuVMConfigPageWidget", "Category:"))
|
||||
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
|
||||
self.uiRamSpinBox.setSuffix(_translate("QemuVMConfigPageWidget", " MB"))
|
||||
self.uiSymbolLabel.setText(_translate("QemuVMConfigPageWidget", "Symbol:"))
|
||||
self.uiConsoleTypeComboBox.setItemText(0, _translate("QemuVMConfigPageWidget", "telnet"))
|
||||
self.uiConsoleTypeComboBox.setItemText(1, _translate("QemuVMConfigPageWidget", "vnc"))
|
||||
self.uiConsoleTypeLabel.setText(_translate("QemuVMConfigPageWidget", "Console type:"))
|
||||
self.uiBootPriorityLabel.setText(_translate("QemuVMConfigPageWidget", "Boot priority:"))
|
||||
self.uiQemuListLabel.setText(_translate("QemuVMConfigPageWidget", "Qemu binary:"))
|
||||
self.uiConsolePortLabel.setText(_translate("QemuVMConfigPageWidget", "Console port:"))
|
||||
self.uiCategoryLabel.setText(_translate("QemuVMConfigPageWidget", "Category:"))
|
||||
self.uiNameLabel.setText(_translate("QemuVMConfigPageWidget", "Name:"))
|
||||
self.uiCPULabel.setText(_translate("QemuVMConfigPageWidget", "vCPUs:"))
|
||||
self.uiDefaultNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Default name format:"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiGeneralSettingsTab), _translate("QemuVMConfigPageWidget", "General settings"))
|
||||
self.uiHdaGroupBox.setTitle(_translate("QemuVMConfigPageWidget", "HDA (Primary Master)"))
|
||||
self.uiHdaDiskImageLabel.setText(_translate("QemuVMConfigPageWidget", "Disk image:"))
|
||||
@@ -440,6 +451,7 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
|
||||
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
|
||||
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
|
||||
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
|
||||
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
|
||||
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
|
||||
@@ -462,6 +474,7 @@ class Ui_QemuVMConfigPageWidget(object):
|
||||
self.uiProcessPriorityComboBox.setItemText(5, _translate("QemuVMConfigPageWidget", "Very low"))
|
||||
self.groupBox.setTitle(_translate("QemuVMConfigPageWidget", "Aditional settings"))
|
||||
self.uiQemuOptionsLabel.setText(_translate("QemuVMConfigPageWidget", "Options:"))
|
||||
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
|
||||
self.uiACPIShutdownCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable ACPI shutdown (experimental)"))
|
||||
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced settings"))
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:34 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_QemuVMPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, QemuVMPreferencesPageWidget):
|
||||
QemuVMPreferencesPageWidget.setObjectName("QemuVMPreferencesPageWidget")
|
||||
QemuVMPreferencesPageWidget.resize(505, 350)
|
||||
@@ -74,4 +75,3 @@ class Ui_QemuVMPreferencesPageWidget(object):
|
||||
self.uiDeleteQemuVMPushButton.setText(_translate("QemuVMPreferencesPageWidget", "&Delete"))
|
||||
self.uiQemuVMInfoTreeWidget.headerItem().setText(0, _translate("QemuVMPreferencesPageWidget", "1"))
|
||||
self.uiQemuVMInfoTreeWidget.headerItem().setText(1, _translate("QemuVMPreferencesPageWidget", "2"))
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
<property name="title">
|
||||
<string>Server type</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiRemoteRadioButton">
|
||||
<property name="text">
|
||||
<string>Remote</string>
|
||||
<string>Run the Qemu VM on a remote computer</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@@ -43,30 +43,17 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiVMRadioButton">
|
||||
<property name="text">
|
||||
<string>GNS3 VM</string>
|
||||
<string>Run the Qemu VM on the GNS3 VM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="uiLocalRadioButton">
|
||||
<property name="text">
|
||||
<string>Local</string>
|
||||
<string>Run the Qemu VM on your local computer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -133,20 +120,6 @@ font: 18pt;</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="uiASADeprecatedWarningLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: red;
|
||||
font: 18pt;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Note</span>: The recommended way to run ASA is to use ASAv with VMware.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/qemu/ui/qemu_vm_wizard.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:34 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.5.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -20,20 +19,18 @@ class Ui_QemuVMWizard(object):
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiServerTypeGroupBox.setObjectName("uiServerTypeGroupBox")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.uiServerTypeGroupBox)
|
||||
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||
self.uiRemoteRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteRadioButton.setChecked(True)
|
||||
self.uiRemoteRadioButton.setObjectName("uiRemoteRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiRemoteRadioButton)
|
||||
self.verticalLayout_5.addWidget(self.uiRemoteRadioButton)
|
||||
self.uiVMRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiVMRadioButton.setObjectName("uiVMRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiVMRadioButton)
|
||||
self.verticalLayout_5.addWidget(self.uiVMRadioButton)
|
||||
self.uiLocalRadioButton = QtWidgets.QRadioButton(self.uiServerTypeGroupBox)
|
||||
self.uiLocalRadioButton.setObjectName("uiLocalRadioButton")
|
||||
self.horizontalLayout.addWidget(self.uiLocalRadioButton)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.verticalLayout_5.addWidget(self.uiLocalRadioButton)
|
||||
self.verticalLayout_3.addWidget(self.uiServerTypeGroupBox)
|
||||
self.uiRemoteServersGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
|
||||
self.uiRemoteServersGroupBox.setObjectName("uiRemoteServersGroupBox")
|
||||
@@ -67,12 +64,6 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiOSDeprecatedWarningLabel.setWordWrap(True)
|
||||
self.uiOSDeprecatedWarningLabel.setObjectName("uiOSDeprecatedWarningLabel")
|
||||
self.verticalLayout.addWidget(self.uiOSDeprecatedWarningLabel)
|
||||
self.uiASADeprecatedWarningLabel = QtWidgets.QLabel(self.uiTypeWizardPage)
|
||||
self.uiASADeprecatedWarningLabel.setStyleSheet("color: red;\n"
|
||||
"font: 18pt;")
|
||||
self.uiASADeprecatedWarningLabel.setWordWrap(True)
|
||||
self.uiASADeprecatedWarningLabel.setObjectName("uiASADeprecatedWarningLabel")
|
||||
self.verticalLayout.addWidget(self.uiASADeprecatedWarningLabel)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.uiTypeLabel = QtWidgets.QLabel(self.uiTypeWizardPage)
|
||||
@@ -138,8 +129,8 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiNewImageRadioButton_2.setChecked(False)
|
||||
self.uiNewImageRadioButton_2.setObjectName("uiNewImageRadioButton_2")
|
||||
self.horizontalLayout_3.addWidget(self.uiNewImageRadioButton_2)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_3.addItem(spacerItem1)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_3.addItem(spacerItem)
|
||||
self.verticalLayout_2.addLayout(self.horizontalLayout_3)
|
||||
self.horizontalLayout_8 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
|
||||
@@ -184,8 +175,8 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiNewImageRadioButton_4.setChecked(False)
|
||||
self.uiNewImageRadioButton_4.setObjectName("uiNewImageRadioButton_4")
|
||||
self.horizontalLayout_7.addWidget(self.uiNewImageRadioButton_4)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_7.addItem(spacerItem2)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_7.addItem(spacerItem1)
|
||||
self.verticalLayout_4.addLayout(self.horizontalLayout_7)
|
||||
self.formLayout_2 = QtWidgets.QFormLayout()
|
||||
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||||
@@ -223,8 +214,8 @@ class Ui_QemuVMWizard(object):
|
||||
self.horizontalLayout_14.addWidget(self.uiKernelImageToolButton)
|
||||
self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_14)
|
||||
self.verticalLayout_4.addLayout(self.formLayout_2)
|
||||
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_4.addItem(spacerItem3)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_4.addItem(spacerItem2)
|
||||
self.horizontalLayout_10.addWidget(self.uiLinuxBootGroupBox)
|
||||
QemuVMWizard.addPage(self.uiASAWizardPage)
|
||||
self.uiDiskImageHdbWizardPage = QtWidgets.QWizardPage()
|
||||
@@ -241,8 +232,8 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiNewImageRadioButton_5.setChecked(False)
|
||||
self.uiNewImageRadioButton_5.setObjectName("uiNewImageRadioButton_5")
|
||||
self.horizontalLayout_9.addWidget(self.uiNewImageRadioButton_5)
|
||||
spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_9.addItem(spacerItem4)
|
||||
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_9.addItem(spacerItem3)
|
||||
self.verticalLayout_6.addLayout(self.horizontalLayout_9)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
@@ -278,16 +269,15 @@ class Ui_QemuVMWizard(object):
|
||||
self.uiServerWizardPage.setTitle(_translate("QemuVMWizard", "Server"))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a server type to run your new QEMU VM."))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("QemuVMWizard", "Server type"))
|
||||
self.uiRemoteRadioButton.setText(_translate("QemuVMWizard", "Remote"))
|
||||
self.uiVMRadioButton.setText(_translate("QemuVMWizard", "GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("QemuVMWizard", "Local"))
|
||||
self.uiRemoteRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on a remote computer"))
|
||||
self.uiVMRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on the GNS3 VM"))
|
||||
self.uiLocalRadioButton.setText(_translate("QemuVMWizard", "Run the Qemu VM on your local computer"))
|
||||
self.uiRemoteServersGroupBox.setTitle(_translate("QemuVMWizard", "Remote servers"))
|
||||
self.uiLoadBalanceCheckBox.setText(_translate("QemuVMWizard", "Load balance across all available remote servers"))
|
||||
self.uiRemoteServersLabel.setText(_translate("QemuVMWizard", "Run on server:"))
|
||||
self.uiTypeWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM type"))
|
||||
self.uiTypeWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a type of QEMU VM to help with pre-configuration."))
|
||||
self.uiOSDeprecatedWarningLabel.setText(_translate("QemuVMWizard", "<html><head/><body><p><span style=\" font-weight:600;\">WARNING</span>: The recommended way to run QEMU on Windows and OSX is to use the GNS3 VM</p></body></html>"))
|
||||
self.uiASADeprecatedWarningLabel.setText(_translate("QemuVMWizard", "<html><head/><body><p><span style=\" font-weight:600;\">Note</span>: The recommended way to run ASA is to use ASAv with VMware.</p></body></html>"))
|
||||
self.uiTypeLabel.setText(_translate("QemuVMWizard", "Type:"))
|
||||
self.uiNameWizardPage.setTitle(_translate("QemuVMWizard", "QEMU VM name"))
|
||||
self.uiNameWizardPage.setSubTitle(_translate("QemuVMWizard", "Please choose a descriptive name for your new QEMU virtual machine."))
|
||||
|
||||
@@ -127,6 +127,9 @@ class VirtualBox(Module):
|
||||
continue
|
||||
vm_settings = VBOX_VM_SETTINGS.copy()
|
||||
vm_settings.update(vm)
|
||||
# For backward compatibility we use vmname
|
||||
if not vm_settings["name"]:
|
||||
vm_settings["name"] = vmname
|
||||
# for backward compatibility before version 1.4
|
||||
if "symbol" not in vm_settings:
|
||||
vm_settings["symbol"] = vm_settings.get("default_symbol", vm_settings["symbol"])
|
||||
@@ -141,7 +144,7 @@ class VirtualBox(Module):
|
||||
self._settings["vms"] = list(self._virtualbox_vms.values())
|
||||
self._saveSettings()
|
||||
|
||||
def virtualBoxVMs(self):
|
||||
def VMs(self):
|
||||
"""
|
||||
Returns VirtualBox VMs settings.
|
||||
|
||||
@@ -150,7 +153,12 @@ class VirtualBox(Module):
|
||||
|
||||
return self._virtualbox_vms
|
||||
|
||||
def setVirtualBoxVMs(self, new_virtualbox_vms):
|
||||
@staticmethod
|
||||
def vmConfigurationPage():
|
||||
from .pages.virtualbox_vm_configuration_page import VirtualBoxVMConfigurationPage
|
||||
return VirtualBoxVMConfigurationPage
|
||||
|
||||
def setVMs(self, new_virtualbox_vms):
|
||||
"""
|
||||
Sets VirtualBox VM settings.
|
||||
|
||||
@@ -255,25 +263,36 @@ class VirtualBox(Module):
|
||||
for other_node in self._nodes:
|
||||
if other_node.settings()["vmname"] == self._virtualbox_vms[vm]["vmname"] and \
|
||||
(self._virtualbox_vms[vm]["server"] == "local" and other_node.server().isLocal() or self._virtualbox_vms[vm]["server"] == other_node.server().host):
|
||||
raise ModuleError("Sorry a VirtualBox VM can only be used once in your topology (this will change in future versions)")
|
||||
raise ModuleError("Sorry a VirtualBox VM without the linked base setting enabled can only be used once in your topology")
|
||||
elif node.project().temporary():
|
||||
raise ModuleError("Sorry, VirtualBox linked clones are not supported in temporary projects")
|
||||
|
||||
vm_settings = {}
|
||||
for setting_name, value in self._virtualbox_vms[vm].items():
|
||||
if setting_name in node.settings() and value != "" and value is not None:
|
||||
if setting_name != "name" and setting_name in node.settings() and value != "" and value is not None:
|
||||
vm_settings[setting_name] = value
|
||||
|
||||
name = self._virtualbox_vms[vm]["name"]
|
||||
vmname = self._virtualbox_vms[vm]["vmname"]
|
||||
port_name_format = self._virtualbox_vms[vm]["port_name_format"]
|
||||
port_segment_size = self._virtualbox_vms[vm]["port_segment_size"]
|
||||
first_port_name = self._virtualbox_vms[vm]["first_port_name"]
|
||||
|
||||
default_name_format = VBOX_VM_SETTINGS["default_name_format"]
|
||||
if self._virtualbox_vms[vm]["default_name_format"]:
|
||||
default_name_format = self._virtualbox_vms[vm]["default_name_format"]
|
||||
if linked_base:
|
||||
default_name_format = default_name_format.replace('{name}', name)
|
||||
name = None
|
||||
|
||||
node.setup(vmname,
|
||||
name=name,
|
||||
port_name_format=port_name_format,
|
||||
port_segment_size=port_segment_size,
|
||||
first_port_name=first_port_name,
|
||||
linked_clone=linked_base,
|
||||
additional_settings=vm_settings)
|
||||
additional_settings=vm_settings,
|
||||
default_name_format=default_name_format)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
@@ -315,7 +334,7 @@ class VirtualBox(Module):
|
||||
for vbox_vm in self._virtualbox_vms.values():
|
||||
nodes.append(
|
||||
{"class": VirtualBoxVM.__name__,
|
||||
"name": vbox_vm["vmname"],
|
||||
"name": vbox_vm["name"],
|
||||
"server": vbox_vm["server"],
|
||||
"symbol": vbox_vm["symbol"],
|
||||
"categories": [vbox_vm["category"]]}
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
Wizard for VirtualBox VMs.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from gns3.qt import QtGui, QtWidgets
|
||||
from gns3.servers import Servers
|
||||
from gns3.dialogs.vm_wizard import VMWizard
|
||||
@@ -107,6 +105,7 @@ class VirtualBoxVMWizard(VMWizard, Ui_VirtualBoxVMWizard):
|
||||
vminfo = self.uiVMListComboBox.itemData(index)
|
||||
|
||||
settings = {
|
||||
"name": vmname,
|
||||
"vmname": vmname,
|
||||
"server": server,
|
||||
"ram": vminfo["ram"],
|
||||
|
||||
@@ -106,6 +106,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
self.uiVMListComboBox.hide()
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# rename the label from "Name" to "Template name"
|
||||
self.uiNameLabel.setText("Template name:")
|
||||
|
||||
# load the default name format
|
||||
self.uiDefaultNameFormatLineEdit.setText(settings["default_name_format"])
|
||||
|
||||
# load the symbol
|
||||
self.uiSymbolLineEdit.setText(settings["symbol"])
|
||||
self.uiSymbolLineEdit.setToolTip('<img src="{}"/>'.format(settings["symbol"]))
|
||||
@@ -119,6 +127,8 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
self.uiPortSegmentSizeSpinBox.setValue(settings["port_segment_size"])
|
||||
self.uiFirstPortNameLineEdit.setText(settings["first_port_name"])
|
||||
else:
|
||||
self.uiDefaultNameFormatLabel.hide()
|
||||
self.uiDefaultNameFormatLineEdit.hide()
|
||||
self.uiSymbolLabel.hide()
|
||||
self.uiSymbolLineEdit.hide()
|
||||
self.uiSymbolToolButton.hide()
|
||||
@@ -176,6 +186,15 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
del settings["enable_remote_console"]
|
||||
|
||||
if not node:
|
||||
# these are template settings
|
||||
|
||||
# save the default name format
|
||||
default_name_format = self.uiDefaultNameFormatLineEdit.text().strip()
|
||||
if '{0}' not in default_name_format and '{id}' not in default_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Default name format", "The default name format must contain at least {0} or {id}")
|
||||
else:
|
||||
settings["default_name_format"] = default_name_format
|
||||
|
||||
symbol_path = self.uiSymbolLineEdit.text()
|
||||
pixmap = QtGui.QPixmap(symbol_path)
|
||||
if pixmap.isNull():
|
||||
@@ -185,14 +204,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
|
||||
|
||||
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
|
||||
port_name_format = self.uiPortNameFormatLineEdit.text()
|
||||
if '{0}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
|
||||
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
|
||||
else:
|
||||
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
|
||||
|
||||
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
|
||||
if port_segment_size and '{1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
|
||||
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
|
||||
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
|
||||
else:
|
||||
settings["port_segment_size"] = port_segment_size
|
||||
|
||||
|
||||
@@ -66,7 +66,10 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
|
||||
|
||||
# fill out the General section
|
||||
section_item = self._createSectionItem("General")
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["VM name:", vbox_vm["vmname"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Template name:", vbox_vm["name"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["VirtualBox name:", vbox_vm["vmname"]])
|
||||
if vbox_vm["linked_base"]:
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Default name format:", vbox_vm["default_name_format"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["RAM:", str(vbox_vm["ram"])])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Server:", vbox_vm["server"]])
|
||||
QtWidgets.QTreeWidgetItem(section_item, ["Remote console enabled:", "{}".format(vbox_vm["enable_remote_console"])])
|
||||
@@ -121,7 +124,7 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
|
||||
self._virtualbox_vms[key].update(new_vm_settings)
|
||||
|
||||
item = QtWidgets.QTreeWidgetItem(self.uiVirtualBoxVMsTreeWidget)
|
||||
item.setText(0, self._virtualbox_vms[key]["vmname"])
|
||||
item.setText(0, self._virtualbox_vms[key]["name"])
|
||||
item.setIcon(0, QtGui.QIcon(self._virtualbox_vms[key]["symbol"]))
|
||||
item.setData(0, QtCore.Qt.UserRole, key)
|
||||
self._items.append(item)
|
||||
@@ -141,17 +144,8 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
|
||||
if dialog.exec_():
|
||||
# update the icon
|
||||
item.setIcon(0, QtGui.QIcon(vbox_vm["symbol"]))
|
||||
if vbox_vm["vmname"] != item.text(0):
|
||||
new_key = "{server}:{vmname}".format(server=vbox_vm["server"], name=vbox_vm["vmname"])
|
||||
if new_key in self._virtualbox_vms:
|
||||
QtWidgets.QMessageBox.critical(self, "VirtualBox VM", "VirtualBox VM name {} already exists for server {}".format(vbox_vm["vmname"],
|
||||
vbox_vm["server"]))
|
||||
vbox_vm["vmname"] = item.text(0)
|
||||
return
|
||||
self._virtualbox_vms[new_key] = self._virtualbox_vms[key]
|
||||
del self._virtualbox_vms[key]
|
||||
item.setText(0, vbox_vm["vmname"])
|
||||
item.setData(0, QtCore.Qt.UserRole, new_key)
|
||||
if vbox_vm["name"] != item.text(0):
|
||||
item.setText(0, vbox_vm["name"])
|
||||
self._refreshInfo(vbox_vm)
|
||||
|
||||
def _vboxVMDeleteSlot(self):
|
||||
@@ -171,12 +165,12 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
|
||||
"""
|
||||
|
||||
vbox_module = VirtualBox.instance()
|
||||
self._virtualbox_vms = copy.deepcopy(vbox_module.virtualBoxVMs())
|
||||
self._virtualbox_vms = copy.deepcopy(vbox_module.VMs())
|
||||
self._items.clear()
|
||||
|
||||
for key, vbox_vm in self._virtualbox_vms.items():
|
||||
item = QtWidgets.QTreeWidgetItem(self.uiVirtualBoxVMsTreeWidget)
|
||||
item.setText(0, vbox_vm["vmname"])
|
||||
item.setText(0, vbox_vm["name"])
|
||||
item.setIcon(0, QtGui.QIcon(vbox_vm["symbol"]))
|
||||
item.setData(0, QtCore.Qt.UserRole, key)
|
||||
self._items.append(item)
|
||||
@@ -191,4 +185,4 @@ class VirtualBoxVMPreferencesPage(QtWidgets.QWidget, Ui_VirtualBoxVMPreferencesP
|
||||
"""
|
||||
|
||||
# self._vboxVMSaveSlot()
|
||||
VirtualBox.instance().setVirtualBoxVMs(self._virtualbox_vms)
|
||||
VirtualBox.instance().setVMs(self._virtualbox_vms)
|
||||
|
||||
@@ -27,7 +27,9 @@ VBOX_SETTINGS = {
|
||||
}
|
||||
|
||||
VBOX_VM_SETTINGS = {
|
||||
"name": "",
|
||||
"vmname": "",
|
||||
"default_name_format": "{name}-{0}",
|
||||
"symbol": ":/symbols/vbox_guest.svg",
|
||||
"category": Node.end_devices,
|
||||
"port_name_format": "Ethernet{0}",
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_preferences_page.ui'
|
||||
#
|
||||
# Created: Wed Jul 15 12:22:35 2015
|
||||
# by: PyQt5 UI code generator 5.4
|
||||
# Created by: PyQt5 UI code generator 5.4.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_VirtualBoxPreferencesPageWidget(object):
|
||||
|
||||
def setupUi(self, VirtualBoxPreferencesPageWidget):
|
||||
VirtualBoxPreferencesPageWidget.setObjectName("VirtualBoxPreferencesPageWidget")
|
||||
VirtualBoxPreferencesPageWidget.resize(450, 250)
|
||||
@@ -69,4 +70,3 @@ class Ui_VirtualBoxPreferencesPageWidget(object):
|
||||
self.uiVboxManagePathToolButton.setText(_translate("VirtualBoxPreferencesPageWidget", "&Browse..."))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("VirtualBoxPreferencesPageWidget", "General settings"))
|
||||
self.uiRestoreDefaultsPushButton.setText(_translate("VirtualBoxPreferencesPageWidget", "Restore defaults"))
|
||||
|
||||
|
||||
@@ -24,24 +24,27 @@
|
||||
<string>General settings</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="uiVMListComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiNameLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiVMRamLabel">
|
||||
<property name="text">
|
||||
<string>Symbol:</string>
|
||||
<string>RAM:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="uiSymbolLineEdit"/>
|
||||
@@ -58,41 +61,14 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNameLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiVMListLabel">
|
||||
<property name="text">
|
||||
<string>VM name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiVMListComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiVMRamLabel">
|
||||
<property name="text">
|
||||
<string>RAM:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiVMRamSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@@ -108,45 +84,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiSymbolLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
<string>Symbol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiEnableConsoleCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable remote console</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiACPIShutdownCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable ACPI shutdown</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiHeadlessModeCheckBox">
|
||||
<property name="text">
|
||||
<string>Start VM in headless mode</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiBaseVMCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
@@ -156,7 +108,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -169,6 +121,64 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiCategoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiCategoryComboBox"/>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiEnableConsoleCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable remote console</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiHeadlessModeCheckBox">
|
||||
<property name="text">
|
||||
<string>Start VM in headless mode</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiVMListLabel">
|
||||
<property name="text">
|
||||
<string>VM name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="uiConsolePortLabel">
|
||||
<property name="text">
|
||||
<string>Console port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QSpinBox" name="uiConsolePortSpinBox">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiDefaultNameFormatLabel">
|
||||
<property name="text">
|
||||
<string>Default name format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiDefaultNameFormatLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
@@ -256,6 +266,9 @@
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="uiPortNameFormatLabel">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name format:</string>
|
||||
</property>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user