Merge pull request #323 from coreemu/coretk
merging current state of coretk
35
.github/workflows/coretk-checks.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
name: CORE Tk Checks
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Set up Python 3.7
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.7
|
||||||
|
- name: Install pipenv
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pipenv
|
||||||
|
cd daemon
|
||||||
|
cp setup.py.in setup.py
|
||||||
|
cp core/constants.py.in core/constants.py
|
||||||
|
sed -i 's/True/False/g' core/constants.py
|
||||||
|
cd ../coretk
|
||||||
|
pipenv install --dev
|
||||||
|
- name: isort
|
||||||
|
run: |
|
||||||
|
cd coretk
|
||||||
|
pipenv run isort -c
|
||||||
|
- name: black
|
||||||
|
run: |
|
||||||
|
cd coretk
|
||||||
|
pipenv run black --check .
|
||||||
|
- name: flake8
|
||||||
|
run: |
|
||||||
|
cd coretk
|
||||||
|
pipenv run flake8
|
3
.gitignore
vendored
|
@ -58,3 +58,6 @@ ns3/setup.py
|
||||||
|
|
||||||
# ignore corefx build
|
# ignore corefx build
|
||||||
corefx/target
|
corefx/target
|
||||||
|
|
||||||
|
# python
|
||||||
|
__pycache__
|
||||||
|
|
19
coretk/Pipfile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[scripts]
|
||||||
|
coretk = "python coretk/app.py"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
flake8 = "*"
|
||||||
|
isort = "*"
|
||||||
|
black = "==19.3b0"
|
||||||
|
pre-commit = "*"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
coretk = {path = ".",editable = true}
|
||||||
|
core = {path = "./../daemon",editable = true}
|
||||||
|
pyyaml = "*"
|
||||||
|
netaddr = "*"
|
521
coretk/Pipfile.lock
generated
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "9024ff4821ee3ccffee21a83f5436953371ad7d64a81a22b6c3723002c92b2cd"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"bcrypt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89",
|
||||||
|
"sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42",
|
||||||
|
"sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294",
|
||||||
|
"sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161",
|
||||||
|
"sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752",
|
||||||
|
"sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31",
|
||||||
|
"sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5",
|
||||||
|
"sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c",
|
||||||
|
"sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0",
|
||||||
|
"sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de",
|
||||||
|
"sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e",
|
||||||
|
"sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052",
|
||||||
|
"sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09",
|
||||||
|
"sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105",
|
||||||
|
"sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133",
|
||||||
|
"sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1",
|
||||||
|
"sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7",
|
||||||
|
"sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"
|
||||||
|
],
|
||||||
|
"version": "==3.1.7"
|
||||||
|
},
|
||||||
|
"cffi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42",
|
||||||
|
"sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04",
|
||||||
|
"sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5",
|
||||||
|
"sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54",
|
||||||
|
"sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba",
|
||||||
|
"sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57",
|
||||||
|
"sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396",
|
||||||
|
"sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12",
|
||||||
|
"sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97",
|
||||||
|
"sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43",
|
||||||
|
"sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db",
|
||||||
|
"sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3",
|
||||||
|
"sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b",
|
||||||
|
"sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579",
|
||||||
|
"sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346",
|
||||||
|
"sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159",
|
||||||
|
"sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652",
|
||||||
|
"sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e",
|
||||||
|
"sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a",
|
||||||
|
"sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506",
|
||||||
|
"sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f",
|
||||||
|
"sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d",
|
||||||
|
"sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c",
|
||||||
|
"sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20",
|
||||||
|
"sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858",
|
||||||
|
"sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc",
|
||||||
|
"sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a",
|
||||||
|
"sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3",
|
||||||
|
"sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e",
|
||||||
|
"sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410",
|
||||||
|
"sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25",
|
||||||
|
"sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b",
|
||||||
|
"sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"
|
||||||
|
],
|
||||||
|
"version": "==1.13.2"
|
||||||
|
},
|
||||||
|
"core": {
|
||||||
|
"editable": true,
|
||||||
|
"path": "./../daemon"
|
||||||
|
},
|
||||||
|
"coretk": {
|
||||||
|
"editable": true,
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
"cryptography": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
|
||||||
|
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
|
||||||
|
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
|
||||||
|
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
|
||||||
|
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
|
||||||
|
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
|
||||||
|
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
|
||||||
|
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
|
||||||
|
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
|
||||||
|
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
|
||||||
|
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
|
||||||
|
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
|
||||||
|
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
|
||||||
|
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
|
||||||
|
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
|
||||||
|
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
|
||||||
|
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
|
||||||
|
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
|
||||||
|
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
|
||||||
|
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
|
||||||
|
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"fabric": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:160331934ea60036604928e792fa8e9f813266b098ef5562aa82b88527740389",
|
||||||
|
"sha256:24842d7d51556adcabd885ac3cf5e1df73fc622a1708bf3667bf5927576cdfa6"
|
||||||
|
],
|
||||||
|
"version": "==2.5.0"
|
||||||
|
},
|
||||||
|
"grpcio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0419ae5a45f49c7c40d9ae77ae4de9442431b7822851dfbbe56ee0eacb5e5654",
|
||||||
|
"sha256:1e8631eeee0fb0b4230aeb135e4890035f6ef9159c2a3555fa184468e325691a",
|
||||||
|
"sha256:24db2fa5438f3815a4edb7a189035051760ca6aa2b0b70a6a948b28bfc63c76b",
|
||||||
|
"sha256:2adb1cdb7d33e91069517b41249622710a94a1faece1fed31cd36904e4201cde",
|
||||||
|
"sha256:2cd51f35692b551aeb1fdeb7a256c7c558f6d78fcddff00640942d42f7aeba5f",
|
||||||
|
"sha256:3247834d24964589f8c2b121b40cd61319b3c2e8d744a6a82008643ef8a378b1",
|
||||||
|
"sha256:3433cb848b4209717722b62392e575a77a52a34d67c6730138102abc0a441685",
|
||||||
|
"sha256:39671b7ff77a962bd745746d9d2292c8ed227c5748f16598d16d8631d17dd7e5",
|
||||||
|
"sha256:40a0b8b2e6f6dd630f8b267eede2f40a848963d0f3c40b1b1f453a4a870f679e",
|
||||||
|
"sha256:40f9a74c7aa210b3e76eb1c9d56aa8d08722b73426a77626967019df9bbac287",
|
||||||
|
"sha256:423f76aa504c84cb94594fb88b8a24027c887f1c488cf58f2173f22f4fbd046c",
|
||||||
|
"sha256:43bd04cec72281a96eb361e1b0232f0f542b46da50bcfe72ef7e5a1b41d00cb3",
|
||||||
|
"sha256:43e38762635c09e24885d15e3a8e374b72d105d4178ee2cc9491855a8da9c380",
|
||||||
|
"sha256:4413b11c2385180d7de03add6c8845dd66692b148d36e27ec8c9ef537b2553a1",
|
||||||
|
"sha256:4450352a87094fd58daf468b04c65a9fa19ad11a0ac8ac7b7ff17d46f873cbc1",
|
||||||
|
"sha256:49ffda04a6e44de028b3b786278ac9a70043e7905c3eea29eed88b6524d53a29",
|
||||||
|
"sha256:4a38c4dde4c9120deef43aaabaa44f19186c98659ce554c29788c4071ab2f0a4",
|
||||||
|
"sha256:50b1febdfd21e2144b56a9aa226829e93a79c354ef22a4e5b013d9965e1ec0ed",
|
||||||
|
"sha256:559b1a3a8be7395ded2943ea6c2135d096f8cc7039d6d12127110b6496f251fe",
|
||||||
|
"sha256:5de86c182667ec68cf84019aa0d8ceccf01d352cdca19bf9e373725204bdbf50",
|
||||||
|
"sha256:5fc069bb481fe3fad0ba24d3baaf69e22dfa6cc1b63290e6dfeaf4ac1e996fb7",
|
||||||
|
"sha256:6a19d654da49516296515d6f65de4bbcbd734bc57913b21a610cfc45e6df3ff1",
|
||||||
|
"sha256:7535b3e52f498270e7877dde1c8944d6b7720e93e2e66b89c82a11447b5818f5",
|
||||||
|
"sha256:7c4e495bcabc308198b8962e60ca12f53b27eb8f03a21ac1d2d711d6dd9ecfca",
|
||||||
|
"sha256:8a8fc4a0220367cb8370cedac02272d574079ccc32bffbb34d53aaf9e38b5060",
|
||||||
|
"sha256:8b008515e067232838daca020d1af628bf6520c8cc338bf383284efe6d8bd083",
|
||||||
|
"sha256:8d1684258e1385e459418f3429e107eec5fb3d75e1f5a8c52e5946b3f329d6ea",
|
||||||
|
"sha256:8eb5d54b87fb561dc2e00a5c5226c33ffe8dbc13f2e4033a412bafb7b37b194d",
|
||||||
|
"sha256:94cdef0c61bd014bb7af495e21a1c3a369dd0399c3cd1965b1502043f5c88d94",
|
||||||
|
"sha256:9d9f3be69c7a5e84c3549a8c4403fa9ac7672da456863d21e390b2bbf45ccad1",
|
||||||
|
"sha256:9fb6fb5975a448169756da2d124a1beb38c0924ff6c0306d883b6848a9980f38",
|
||||||
|
"sha256:a5eaae8700b87144d7dfb475aa4675e500ff707292caba3deff41609ddc5b845",
|
||||||
|
"sha256:aaeac2d552772b76d24eaff67a5d2325bc5205c74c0d4f9fbe71685d4a971db2",
|
||||||
|
"sha256:bb611e447559b3b5665e12a7da5160c0de6876097f62bf1d23ba66911564868e",
|
||||||
|
"sha256:bc0d41f4eb07da8b8d3ea85e50b62f6491ab313834db86ae2345be07536a4e5a",
|
||||||
|
"sha256:bf51051c129b847d1bb63a9b0826346b5f52fb821b15fe5e0d5ef86f268510f5",
|
||||||
|
"sha256:c948c034d8997526011960db54f512756fb0b4be1b81140a15b4ef094c6594a4",
|
||||||
|
"sha256:d435a01334157c3b126b4ee5141401d44bdc8440993b18b05e2f267a6647f92d",
|
||||||
|
"sha256:d46c1f95672b73288e08cdca181e14e84c6229b5879561b7b8cfd48374e09287",
|
||||||
|
"sha256:d5d58309b42064228b16b0311ff715d6c6e20230e81b35e8d0c8cfa1bbdecad8",
|
||||||
|
"sha256:dc6e2e91365a1dd6314d615d80291159c7981928b88a4c65654e3fefac83a836",
|
||||||
|
"sha256:e0dfb5f7a39029a6cbec23affa923b22a2c02207960fd66f109e01d6f632c1eb",
|
||||||
|
"sha256:eb4bf58d381b1373bd21d50837a53953d625d1693f1b58fed12743c75d3dd321",
|
||||||
|
"sha256:ebb211a85248dbc396b29320273c1ffde484b898852432613e8df0164c091006",
|
||||||
|
"sha256:ec759ece4786ae993a5b7dc3b3dead6e9375d89a6c65dfd6860076d2eb2abe7b",
|
||||||
|
"sha256:f55108397a8fa164268238c3e69cc134e945d1f693572a2f05a028b8d0d2b837",
|
||||||
|
"sha256:f6c706866d424ff285b85a02de7bbe5ed0ace227766b2c42cbe12f3d9ea5a8aa",
|
||||||
|
"sha256:f8370ad332b36fbad117440faf0dd4b910e80b9c49db5648afd337abdde9a1b6"
|
||||||
|
],
|
||||||
|
"version": "==1.25.0"
|
||||||
|
},
|
||||||
|
"invoke": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c52274d2e8a6d64ef0d61093e1983268ea1fc0cd13facb9448c4ef0c9a7ac7da",
|
||||||
|
"sha256:f4ec8a134c0122ea042c8912529f87652445d9f4de590b353d23f95bfa1f0efd",
|
||||||
|
"sha256:fc803a5c9052f15e63310aa81a43498d7c55542beb18564db88a9d75a176fa44"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"lxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4",
|
||||||
|
"sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc",
|
||||||
|
"sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1",
|
||||||
|
"sha256:1409b14bf83a7d729f92e2a7fbfe7ec929d4883ca071b06e95c539ceedb6497c",
|
||||||
|
"sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046",
|
||||||
|
"sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36",
|
||||||
|
"sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5",
|
||||||
|
"sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d",
|
||||||
|
"sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916",
|
||||||
|
"sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0",
|
||||||
|
"sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27",
|
||||||
|
"sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc",
|
||||||
|
"sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7",
|
||||||
|
"sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38",
|
||||||
|
"sha256:9277562f175d2334744ad297568677056861070399cec56ff06abbe2564d1232",
|
||||||
|
"sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5",
|
||||||
|
"sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832",
|
||||||
|
"sha256:ae88588d687bd476be588010cbbe551e9c2872b816f2da8f01f6f1fda74e1ef0",
|
||||||
|
"sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a",
|
||||||
|
"sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f",
|
||||||
|
"sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9",
|
||||||
|
"sha256:c7fccd08b14aa437fe096c71c645c0f9be0655a9b1a4b7cffc77bcb23b3d61d2",
|
||||||
|
"sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692",
|
||||||
|
"sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84",
|
||||||
|
"sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79",
|
||||||
|
"sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681"
|
||||||
|
],
|
||||||
|
"version": "==4.4.1"
|
||||||
|
},
|
||||||
|
"netaddr": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd",
|
||||||
|
"sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.7.19"
|
||||||
|
},
|
||||||
|
"paramiko": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:99f0179bdc176281d21961a003ffdb2ec369daac1a1007241f53374e376576cf",
|
||||||
|
"sha256:f4b2edfa0d226b70bd4ca31ea7e389325990283da23465d572ed1f70a7583041"
|
||||||
|
],
|
||||||
|
"version": "==2.6.0"
|
||||||
|
},
|
||||||
|
"pillow": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031",
|
||||||
|
"sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71",
|
||||||
|
"sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c",
|
||||||
|
"sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340",
|
||||||
|
"sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa",
|
||||||
|
"sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b",
|
||||||
|
"sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573",
|
||||||
|
"sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e",
|
||||||
|
"sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab",
|
||||||
|
"sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9",
|
||||||
|
"sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e",
|
||||||
|
"sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291",
|
||||||
|
"sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12",
|
||||||
|
"sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871",
|
||||||
|
"sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281",
|
||||||
|
"sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08",
|
||||||
|
"sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41",
|
||||||
|
"sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2",
|
||||||
|
"sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5",
|
||||||
|
"sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb",
|
||||||
|
"sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547",
|
||||||
|
"sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75",
|
||||||
|
"sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9",
|
||||||
|
"sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1",
|
||||||
|
"sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a",
|
||||||
|
"sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96",
|
||||||
|
"sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132",
|
||||||
|
"sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a",
|
||||||
|
"sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5",
|
||||||
|
"sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"
|
||||||
|
],
|
||||||
|
"version": "==6.2.1"
|
||||||
|
},
|
||||||
|
"protobuf": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:125713564d8cfed7610e52444c9769b8dcb0b55e25cc7841f2290ee7bc86636f",
|
||||||
|
"sha256:1accdb7a47e51503be64d9a57543964ba674edac103215576399d2d0e34eac77",
|
||||||
|
"sha256:27003d12d4f68e3cbea9eb67427cab3bfddd47ff90670cb367fcd7a3a89b9657",
|
||||||
|
"sha256:3264f3c431a631b0b31e9db2ae8c927b79fc1a7b1b06b31e8e5bcf2af91fe896",
|
||||||
|
"sha256:3c5ab0f5c71ca5af27143e60613729e3488bb45f6d3f143dc918a20af8bab0bf",
|
||||||
|
"sha256:45dcf8758873e3f69feab075e5f3177270739f146255225474ee0b90429adef6",
|
||||||
|
"sha256:56a77d61a91186cc5676d8e11b36a5feb513873e4ae88d2ee5cf530d52bbcd3b",
|
||||||
|
"sha256:5984e4947bbcef5bd849d6244aec507d31786f2dd3344139adc1489fb403b300",
|
||||||
|
"sha256:6b0441da73796dd00821763bb4119674eaf252776beb50ae3883bed179a60b2a",
|
||||||
|
"sha256:6f6677c5ade94d4fe75a912926d6796d5c71a2a90c2aeefe0d6f211d75c74789",
|
||||||
|
"sha256:84a825a9418d7196e2acc48f8746cf1ee75877ed2f30433ab92a133f3eaf8fbe",
|
||||||
|
"sha256:b842c34fe043ccf78b4a6cf1019d7b80113707d68c88842d061fa2b8fb6ddedc",
|
||||||
|
"sha256:ca33d2f09dae149a1dcf942d2d825ebb06343b77b437198c9e2ef115cf5d5bc1",
|
||||||
|
"sha256:db83b5c12c0cd30150bb568e6feb2435c49ce4e68fe2d7b903113f0e221e58fe",
|
||||||
|
"sha256:f50f3b1c5c1c1334ca7ce9cad5992f098f460ffd6388a3cabad10b66c2006b09",
|
||||||
|
"sha256:f99f127909731cafb841c52f9216e447d3e4afb99b17bebfad327a75aee206de"
|
||||||
|
],
|
||||||
|
"version": "==3.10.0"
|
||||||
|
},
|
||||||
|
"pycparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
|
||||||
|
],
|
||||||
|
"version": "==2.19"
|
||||||
|
},
|
||||||
|
"pynacl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255",
|
||||||
|
"sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c",
|
||||||
|
"sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e",
|
||||||
|
"sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae",
|
||||||
|
"sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621",
|
||||||
|
"sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56",
|
||||||
|
"sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39",
|
||||||
|
"sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310",
|
||||||
|
"sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1",
|
||||||
|
"sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5",
|
||||||
|
"sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a",
|
||||||
|
"sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786",
|
||||||
|
"sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b",
|
||||||
|
"sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b",
|
||||||
|
"sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f",
|
||||||
|
"sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20",
|
||||||
|
"sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415",
|
||||||
|
"sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715",
|
||||||
|
"sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92",
|
||||||
|
"sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1",
|
||||||
|
"sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"pyyaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||||
|
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
||||||
|
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
||||||
|
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
||||||
|
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
||||||
|
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
||||||
|
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
||||||
|
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
||||||
|
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
||||||
|
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
||||||
|
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
||||||
|
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
||||||
|
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.1.2"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
|
||||||
|
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
|
||||||
|
],
|
||||||
|
"version": "==1.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {
|
||||||
|
"appdirs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||||
|
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||||
|
],
|
||||||
|
"version": "==1.4.3"
|
||||||
|
},
|
||||||
|
"aspy.yaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc",
|
||||||
|
"sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||||
|
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||||
|
],
|
||||||
|
"version": "==19.3.0"
|
||||||
|
},
|
||||||
|
"black": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||||
|
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==19.3b0"
|
||||||
|
},
|
||||||
|
"cfgv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144",
|
||||||
|
"sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"
|
||||||
|
],
|
||||||
|
"version": "==2.0.1"
|
||||||
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||||
|
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||||
|
],
|
||||||
|
"version": "==7.0"
|
||||||
|
},
|
||||||
|
"entrypoints": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||||
|
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||||
|
],
|
||||||
|
"version": "==0.3"
|
||||||
|
},
|
||||||
|
"flake8": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
|
||||||
|
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.7.9"
|
||||||
|
},
|
||||||
|
"identify": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017",
|
||||||
|
"sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"
|
||||||
|
],
|
||||||
|
"version": "==1.4.7"
|
||||||
|
},
|
||||||
|
"importlib-metadata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||||
|
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.8'",
|
||||||
|
"version": "==0.23"
|
||||||
|
},
|
||||||
|
"importlib-resources": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b",
|
||||||
|
"sha256:d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.7'",
|
||||||
|
"version": "==1.0.2"
|
||||||
|
},
|
||||||
|
"isort": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||||
|
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.3.21"
|
||||||
|
},
|
||||||
|
"mccabe": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||||
|
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||||
|
],
|
||||||
|
"version": "==0.6.1"
|
||||||
|
},
|
||||||
|
"more-itertools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832",
|
||||||
|
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"
|
||||||
|
],
|
||||||
|
"version": "==7.2.0"
|
||||||
|
},
|
||||||
|
"nodeenv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"
|
||||||
|
],
|
||||||
|
"version": "==1.3.3"
|
||||||
|
},
|
||||||
|
"pre-commit": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9f152687127ec90642a2cc3e4d9e1e6240c4eb153615cb02aa1ad41d331cbb6e",
|
||||||
|
"sha256:c2e4810d2d3102d354947907514a78c5d30424d299dc0fe48f5aa049826e9b50"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.20.0"
|
||||||
|
},
|
||||||
|
"pycodestyle": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||||
|
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||||
|
],
|
||||||
|
"version": "==2.5.0"
|
||||||
|
},
|
||||||
|
"pyflakes": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||||
|
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||||
|
],
|
||||||
|
"version": "==2.1.1"
|
||||||
|
},
|
||||||
|
"pyyaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
|
||||||
|
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
|
||||||
|
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
|
||||||
|
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
|
||||||
|
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
|
||||||
|
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
|
||||||
|
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
|
||||||
|
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
|
||||||
|
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
|
||||||
|
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
|
||||||
|
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
|
||||||
|
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
|
||||||
|
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.1.2"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
|
||||||
|
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
|
||||||
|
],
|
||||||
|
"version": "==1.13.0"
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
|
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||||
|
],
|
||||||
|
"version": "==0.10.0"
|
||||||
|
},
|
||||||
|
"virtualenv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589",
|
||||||
|
"sha256:d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136"
|
||||||
|
],
|
||||||
|
"version": "==16.7.7"
|
||||||
|
},
|
||||||
|
"zipp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
||||||
|
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
||||||
|
],
|
||||||
|
"version": "==0.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
coretk/coretk/__init__.py
Normal file
107
coretk/coretk/app.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk import appconfig, themes
|
||||||
|
from coretk.coreclient import CoreClient
|
||||||
|
from coretk.graph.graph import CanvasGraph
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.menuaction import MenuAction
|
||||||
|
from coretk.menubar import Menubar
|
||||||
|
from coretk.nodeutils import NodeUtils
|
||||||
|
from coretk.statusbar import StatusBar
|
||||||
|
from coretk.toolbar import Toolbar
|
||||||
|
from coretk.validation import InputValidation
|
||||||
|
|
||||||
|
WIDTH = 1000
|
||||||
|
HEIGHT = 800
|
||||||
|
|
||||||
|
|
||||||
|
class Application(tk.Frame):
|
||||||
|
def __init__(self, master=None):
|
||||||
|
super().__init__(master)
|
||||||
|
# load node icons
|
||||||
|
NodeUtils.setup()
|
||||||
|
|
||||||
|
# widgets
|
||||||
|
self.menubar = None
|
||||||
|
self.toolbar = None
|
||||||
|
self.canvas = None
|
||||||
|
self.statusbar = None
|
||||||
|
self.validation = None
|
||||||
|
|
||||||
|
# setup
|
||||||
|
self.guiconfig = appconfig.read()
|
||||||
|
self.style = ttk.Style()
|
||||||
|
self.setup_theme()
|
||||||
|
self.core = CoreClient(self)
|
||||||
|
self.setup_app()
|
||||||
|
self.draw()
|
||||||
|
self.core.set_up()
|
||||||
|
|
||||||
|
def setup_theme(self):
|
||||||
|
themes.load(self.style)
|
||||||
|
self.master.bind_class("Menu", "<<ThemeChanged>>", themes.theme_change_menu)
|
||||||
|
self.master.bind("<<ThemeChanged>>", themes.theme_change)
|
||||||
|
self.style.theme_use(self.guiconfig["preferences"]["theme"])
|
||||||
|
|
||||||
|
def setup_app(self):
|
||||||
|
self.master.title("CORE")
|
||||||
|
self.center()
|
||||||
|
self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||||
|
image = Images.get(ImageEnum.CORE, 16)
|
||||||
|
self.master.tk.call("wm", "iconphoto", self.master._w, image)
|
||||||
|
self.pack(fill=tk.BOTH, expand=True)
|
||||||
|
self.validation = InputValidation(self)
|
||||||
|
|
||||||
|
def center(self):
|
||||||
|
screen_width = self.master.winfo_screenwidth()
|
||||||
|
screen_height = self.master.winfo_screenheight()
|
||||||
|
x = int((screen_width / 2) - (WIDTH / 2))
|
||||||
|
y = int((screen_height / 2) - (HEIGHT / 2))
|
||||||
|
self.master.geometry(f"{WIDTH}x{HEIGHT}+{x}+{y}")
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.master.option_add("*tearOff", tk.FALSE)
|
||||||
|
self.menubar = Menubar(self.master, self)
|
||||||
|
self.toolbar = Toolbar(self, self)
|
||||||
|
self.toolbar.pack(side=tk.LEFT, fill=tk.Y, ipadx=2, ipady=2)
|
||||||
|
self.draw_canvas()
|
||||||
|
self.draw_status()
|
||||||
|
|
||||||
|
def draw_canvas(self):
|
||||||
|
width = self.guiconfig["preferences"]["width"]
|
||||||
|
height = self.guiconfig["preferences"]["height"]
|
||||||
|
self.canvas = CanvasGraph(self, self.core, width, height)
|
||||||
|
self.canvas.pack(fill=tk.BOTH, expand=True)
|
||||||
|
scroll_x = ttk.Scrollbar(
|
||||||
|
self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview
|
||||||
|
)
|
||||||
|
scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
scroll_y = ttk.Scrollbar(self.canvas, command=self.canvas.yview)
|
||||||
|
scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
self.canvas.configure(xscrollcommand=scroll_x.set)
|
||||||
|
self.canvas.configure(yscrollcommand=scroll_y.set)
|
||||||
|
|
||||||
|
def draw_status(self):
|
||||||
|
self.statusbar = StatusBar(master=self, app=self)
|
||||||
|
self.statusbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
|
||||||
|
def on_closing(self):
|
||||||
|
menu_action = MenuAction(self, self.master)
|
||||||
|
menu_action.on_quit()
|
||||||
|
|
||||||
|
def save_config(self):
|
||||||
|
appconfig.save(self.guiconfig)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.master.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s"
|
||||||
|
logging.basicConfig(level=logging.DEBUG, format=log_format)
|
||||||
|
Images.load_all()
|
||||||
|
appconfig.check_directory()
|
||||||
|
app = Application()
|
||||||
|
app.mainloop()
|
111
coretk/coretk/appconfig.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# gui home paths
|
||||||
|
from coretk import themes
|
||||||
|
|
||||||
|
HOME_PATH = Path.home().joinpath(".coretk")
|
||||||
|
BACKGROUNDS_PATH = HOME_PATH.joinpath("backgrounds")
|
||||||
|
CUSTOM_EMANE_PATH = HOME_PATH.joinpath("custom_emane")
|
||||||
|
CUSTOM_SERVICE_PATH = HOME_PATH.joinpath("custom_services")
|
||||||
|
ICONS_PATH = HOME_PATH.joinpath("icons")
|
||||||
|
MOBILITY_PATH = HOME_PATH.joinpath("mobility")
|
||||||
|
XMLS_PATH = HOME_PATH.joinpath("xmls")
|
||||||
|
CONFIG_PATH = HOME_PATH.joinpath("gui.yaml")
|
||||||
|
|
||||||
|
# local paths
|
||||||
|
DATA_PATH = Path(__file__).parent.joinpath("data")
|
||||||
|
LOCAL_ICONS_PATH = DATA_PATH.joinpath("icons").absolute()
|
||||||
|
LOCAL_BACKGROUND_PATH = DATA_PATH.joinpath("backgrounds").absolute()
|
||||||
|
LOCAL_XMLS_PATH = DATA_PATH.joinpath("xmls").absolute()
|
||||||
|
LOCAL_MOBILITY_PATH = DATA_PATH.joinpath("mobility").absolute()
|
||||||
|
|
||||||
|
# configuration data
|
||||||
|
TERMINALS = [
|
||||||
|
"$TERM",
|
||||||
|
"gnome-terminal --window --",
|
||||||
|
"lxterminal -e",
|
||||||
|
"konsole -e",
|
||||||
|
"xterm -e",
|
||||||
|
"aterm -e",
|
||||||
|
"eterm -e",
|
||||||
|
"rxvt -e",
|
||||||
|
"xfce4-terminal -x",
|
||||||
|
]
|
||||||
|
EDITORS = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"]
|
||||||
|
|
||||||
|
|
||||||
|
class IndentDumper(yaml.Dumper):
|
||||||
|
def increase_indent(self, flow=False, indentless=False):
|
||||||
|
return super().increase_indent(flow, False)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_files(current_path, new_path):
|
||||||
|
for current_file in current_path.glob("*"):
|
||||||
|
new_file = new_path.joinpath(current_file.name)
|
||||||
|
shutil.copy(current_file, new_file)
|
||||||
|
|
||||||
|
|
||||||
|
def check_directory():
|
||||||
|
if HOME_PATH.exists():
|
||||||
|
logging.info("~/.coretk exists")
|
||||||
|
return
|
||||||
|
logging.info("creating ~/.coretk")
|
||||||
|
HOME_PATH.mkdir()
|
||||||
|
BACKGROUNDS_PATH.mkdir()
|
||||||
|
CUSTOM_EMANE_PATH.mkdir()
|
||||||
|
CUSTOM_SERVICE_PATH.mkdir()
|
||||||
|
ICONS_PATH.mkdir()
|
||||||
|
MOBILITY_PATH.mkdir()
|
||||||
|
XMLS_PATH.mkdir()
|
||||||
|
|
||||||
|
copy_files(LOCAL_ICONS_PATH, ICONS_PATH)
|
||||||
|
copy_files(LOCAL_BACKGROUND_PATH, BACKGROUNDS_PATH)
|
||||||
|
copy_files(LOCAL_XMLS_PATH, XMLS_PATH)
|
||||||
|
copy_files(LOCAL_MOBILITY_PATH, MOBILITY_PATH)
|
||||||
|
|
||||||
|
if "TERM" in os.environ:
|
||||||
|
terminal = TERMINALS[0]
|
||||||
|
else:
|
||||||
|
terminal = TERMINALS[1]
|
||||||
|
if "EDITOR" in os.environ:
|
||||||
|
editor = EDITORS[0]
|
||||||
|
else:
|
||||||
|
editor = EDITORS[1]
|
||||||
|
config = {
|
||||||
|
"preferences": {
|
||||||
|
"theme": themes.THEME_DARK,
|
||||||
|
"editor": editor,
|
||||||
|
"terminal": terminal,
|
||||||
|
"gui3d": "/usr/local/bin/std3d.sh",
|
||||||
|
"width": 1000,
|
||||||
|
"height": 750,
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"lat": 47.5791667,
|
||||||
|
"lon": -122.132322,
|
||||||
|
"alt": 2.0,
|
||||||
|
"scale": 150.0,
|
||||||
|
},
|
||||||
|
"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}],
|
||||||
|
"nodes": [],
|
||||||
|
"observers": [{"name": "hello", "cmd": "echo hello"}],
|
||||||
|
}
|
||||||
|
save(config)
|
||||||
|
|
||||||
|
|
||||||
|
def read():
|
||||||
|
with CONFIG_PATH.open("r") as f:
|
||||||
|
return yaml.load(f, Loader=yaml.SafeLoader)
|
||||||
|
|
||||||
|
|
||||||
|
def save(config):
|
||||||
|
with CONFIG_PATH.open("w") as f:
|
||||||
|
yaml.dump(config, f, Dumper=IndentDumper, default_flow_style=False)
|
909
coretk/coretk/coreclient.py
Normal file
|
@ -0,0 +1,909 @@
|
||||||
|
"""
|
||||||
|
Incorporate grpc into python tkinter GUI
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc import client, core_pb2
|
||||||
|
from coretk import appconfig
|
||||||
|
from coretk.dialogs.mobilityplayer import MobilityPlayer
|
||||||
|
from coretk.dialogs.sessions import SessionsDialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.graph import tags
|
||||||
|
from coretk.graph.shape import AnnotationData, Shape
|
||||||
|
from coretk.graph.shapeutils import ShapeType
|
||||||
|
from coretk.interface import InterfaceManager
|
||||||
|
from coretk.nodeutils import NodeDraw, NodeUtils
|
||||||
|
|
||||||
|
GUI_SOURCE = "gui"
|
||||||
|
OBSERVERS = {
|
||||||
|
"processes": "ps",
|
||||||
|
"ifconfig": "ifconfig",
|
||||||
|
"IPV4 Routes": "ip -4 ro",
|
||||||
|
"IPV6 Routes": "ip -6 ro",
|
||||||
|
"Listening sockets": "netstat -tuwnl",
|
||||||
|
"IPv4 MFC entries": "ip -4 mroute show",
|
||||||
|
"IPv6 MFC entries": "ip -6 mroute show",
|
||||||
|
"firewall rules": "iptables -L",
|
||||||
|
"IPSec policies": "setkey -DP",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CoreServer:
|
||||||
|
def __init__(self, name, address, port):
|
||||||
|
self.name = name
|
||||||
|
self.address = address
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
|
||||||
|
class Observer:
|
||||||
|
def __init__(self, name, cmd):
|
||||||
|
self.name = name
|
||||||
|
self.cmd = cmd
|
||||||
|
|
||||||
|
|
||||||
|
class CoreClient:
|
||||||
|
def __init__(self, app):
|
||||||
|
"""
|
||||||
|
Create a CoreGrpc instance
|
||||||
|
"""
|
||||||
|
self.client = client.CoreGrpcClient()
|
||||||
|
self.session_id = None
|
||||||
|
self.node_ids = []
|
||||||
|
self.app = app
|
||||||
|
self.master = app.master
|
||||||
|
self.services = {}
|
||||||
|
self.default_services = {}
|
||||||
|
self.emane_models = []
|
||||||
|
self.observer = None
|
||||||
|
|
||||||
|
# loaded configuration data
|
||||||
|
self.servers = {}
|
||||||
|
self.custom_nodes = {}
|
||||||
|
self.custom_observers = {}
|
||||||
|
self.read_config()
|
||||||
|
|
||||||
|
# helpers
|
||||||
|
self.interface_to_edge = {}
|
||||||
|
self.interfaces_manager = InterfaceManager(self.app)
|
||||||
|
|
||||||
|
# session data
|
||||||
|
self.state = None
|
||||||
|
self.canvas_nodes = {}
|
||||||
|
self.location = None
|
||||||
|
self.links = {}
|
||||||
|
self.hooks = {}
|
||||||
|
self.wlan_configs = {}
|
||||||
|
self.mobility_configs = {}
|
||||||
|
self.emane_model_configs = {}
|
||||||
|
self.emane_config = None
|
||||||
|
self.service_configs = {}
|
||||||
|
self.file_configs = {}
|
||||||
|
self.mobility_players = {}
|
||||||
|
self.handling_throughputs = None
|
||||||
|
self.handling_events = None
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
# helpers
|
||||||
|
self.interfaces_manager.reset()
|
||||||
|
self.interface_to_edge.clear()
|
||||||
|
# session data
|
||||||
|
self.canvas_nodes.clear()
|
||||||
|
self.links.clear()
|
||||||
|
self.hooks.clear()
|
||||||
|
self.wlan_configs.clear()
|
||||||
|
self.mobility_configs.clear()
|
||||||
|
self.emane_model_configs.clear()
|
||||||
|
self.emane_config = None
|
||||||
|
self.service_configs.clear()
|
||||||
|
self.file_configs.clear()
|
||||||
|
self.mobility_players.clear()
|
||||||
|
# clear streams
|
||||||
|
if self.handling_throughputs:
|
||||||
|
self.handling_throughputs.cancel()
|
||||||
|
self.handling_throughputs = None
|
||||||
|
if self.handling_events:
|
||||||
|
self.handling_events.cancel()
|
||||||
|
self.handling_events = None
|
||||||
|
|
||||||
|
def set_observer(self, value):
|
||||||
|
self.observer = value
|
||||||
|
|
||||||
|
def read_config(self):
|
||||||
|
# read distributed server
|
||||||
|
for config in self.app.guiconfig.get("servers", []):
|
||||||
|
server = CoreServer(config["name"], config["address"], config["port"])
|
||||||
|
self.servers[server.name] = server
|
||||||
|
|
||||||
|
# read custom nodes
|
||||||
|
for config in self.app.guiconfig.get("nodes", []):
|
||||||
|
name = config["name"]
|
||||||
|
image_file = config["image"]
|
||||||
|
services = set(config["services"])
|
||||||
|
node_draw = NodeDraw.from_custom(name, image_file, services)
|
||||||
|
self.custom_nodes[name] = node_draw
|
||||||
|
|
||||||
|
# read observers
|
||||||
|
for config in self.app.guiconfig.get("observers", []):
|
||||||
|
observer = Observer(config["name"], config["cmd"])
|
||||||
|
self.custom_observers[observer.name] = observer
|
||||||
|
|
||||||
|
def handle_events(self, event):
|
||||||
|
if event.session_id != self.session_id:
|
||||||
|
logging.warn(
|
||||||
|
"ignoring event session(%s) current(%s)",
|
||||||
|
event.session_id,
|
||||||
|
self.session_id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if event.HasField("link_event"):
|
||||||
|
logging.info("link event: %s", event)
|
||||||
|
self.handle_link_event(event.link_event)
|
||||||
|
elif event.HasField("session_event"):
|
||||||
|
logging.info("session event: %s", event)
|
||||||
|
session_event = event.session_event
|
||||||
|
if session_event.event <= core_pb2.SessionState.SHUTDOWN:
|
||||||
|
self.state = event.session_event.event
|
||||||
|
elif session_event.event in {7, 8, 9}:
|
||||||
|
node_id = session_event.node_id
|
||||||
|
dialog = self.mobility_players.get(node_id)
|
||||||
|
if dialog:
|
||||||
|
if session_event.event == 7:
|
||||||
|
dialog.set_play()
|
||||||
|
elif session_event.event == 8:
|
||||||
|
dialog.set_stop()
|
||||||
|
else:
|
||||||
|
dialog.set_pause()
|
||||||
|
else:
|
||||||
|
logging.warning("unknown session event: %s", session_event)
|
||||||
|
elif event.HasField("node_event"):
|
||||||
|
self.handle_node_event(event.node_event)
|
||||||
|
elif event.HasField("config_event"):
|
||||||
|
logging.info("config event: %s", event)
|
||||||
|
elif event.HasField("exception_event"):
|
||||||
|
self.handle_exception_event(event.exception_event)
|
||||||
|
else:
|
||||||
|
logging.info("unhandled event: %s", event)
|
||||||
|
|
||||||
|
def handle_link_event(self, event):
|
||||||
|
node_one_id = event.link.node_one_id
|
||||||
|
node_two_id = event.link.node_two_id
|
||||||
|
canvas_node_one = self.canvas_nodes[node_one_id]
|
||||||
|
canvas_node_two = self.canvas_nodes[node_two_id]
|
||||||
|
|
||||||
|
if event.message_type == core_pb2.MessageType.ADD:
|
||||||
|
self.app.canvas.add_wireless_edge(canvas_node_one, canvas_node_two)
|
||||||
|
elif event.message_type == core_pb2.MessageType.DELETE:
|
||||||
|
self.app.canvas.delete_wireless_edge(canvas_node_one, canvas_node_two)
|
||||||
|
else:
|
||||||
|
logging.warning("unknown link event: %s", event.message_type)
|
||||||
|
|
||||||
|
def handle_node_event(self, event):
|
||||||
|
if event.source == GUI_SOURCE:
|
||||||
|
return
|
||||||
|
node_id = event.node.id
|
||||||
|
x = event.node.position.x
|
||||||
|
y = event.node.position.y
|
||||||
|
canvas_node = self.canvas_nodes[node_id]
|
||||||
|
canvas_node.move(x, y)
|
||||||
|
|
||||||
|
def enable_throughputs(self):
|
||||||
|
self.handling_throughputs = self.client.throughputs(
|
||||||
|
self.session_id, self.handle_throughputs
|
||||||
|
)
|
||||||
|
|
||||||
|
def cancel_throughputs(self):
|
||||||
|
self.handling_throughputs.cancel()
|
||||||
|
self.handling_throughputs = None
|
||||||
|
|
||||||
|
def handle_throughputs(self, event):
|
||||||
|
if event.session_id != self.session_id:
|
||||||
|
logging.warn(
|
||||||
|
"ignoring throughput event session(%s) current(%s)",
|
||||||
|
event.session_id,
|
||||||
|
self.session_id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
logging.info("handling throughputs event: %s", event)
|
||||||
|
self.app.canvas.throughput_draw.process_grpc_throughput_event(
|
||||||
|
event.interface_throughputs
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_exception_event(self, event):
|
||||||
|
logging.info("exception event: %s", event)
|
||||||
|
self.app.statusbar.core_alarms.append(event)
|
||||||
|
|
||||||
|
def join_session(self, session_id, query_location=True):
|
||||||
|
# update session and title
|
||||||
|
self.session_id = session_id
|
||||||
|
self.master.title(f"CORE Session({self.session_id})")
|
||||||
|
|
||||||
|
# clear session data
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# get session data
|
||||||
|
try:
|
||||||
|
response = self.client.get_session(self.session_id)
|
||||||
|
session = response.session
|
||||||
|
self.state = session.state
|
||||||
|
self.handling_events = self.client.events(
|
||||||
|
self.session_id, self.handle_events
|
||||||
|
)
|
||||||
|
|
||||||
|
# get location
|
||||||
|
if query_location:
|
||||||
|
response = self.client.get_session_location(self.session_id)
|
||||||
|
self.location = response.location
|
||||||
|
|
||||||
|
# get emane models
|
||||||
|
response = self.client.get_emane_models(self.session_id)
|
||||||
|
self.emane_models = response.models
|
||||||
|
|
||||||
|
# get hooks
|
||||||
|
response = self.client.get_hooks(self.session_id)
|
||||||
|
for hook in response.hooks:
|
||||||
|
self.hooks[hook.file] = hook
|
||||||
|
|
||||||
|
# get mobility configs
|
||||||
|
response = self.client.get_mobility_configs(self.session_id)
|
||||||
|
for node_id in response.configs:
|
||||||
|
node_config = response.configs[node_id].config
|
||||||
|
self.mobility_configs[node_id] = node_config
|
||||||
|
|
||||||
|
# get emane config
|
||||||
|
response = self.client.get_emane_config(self.session_id)
|
||||||
|
self.emane_config = response.config
|
||||||
|
|
||||||
|
# get emane model config
|
||||||
|
response = self.client.get_emane_model_configs(self.session_id)
|
||||||
|
for config in response.configs:
|
||||||
|
interface = None
|
||||||
|
if config.interface != -1:
|
||||||
|
interface = config.interface
|
||||||
|
self.set_emane_model_config(
|
||||||
|
config.node_id, config.model, config.config, interface
|
||||||
|
)
|
||||||
|
|
||||||
|
# get wlan configurations
|
||||||
|
response = self.client.get_wlan_configs(self.session_id)
|
||||||
|
for _id in response.configs:
|
||||||
|
mapped_config = response.configs[_id]
|
||||||
|
self.wlan_configs[_id] = mapped_config.config
|
||||||
|
|
||||||
|
# get service configurations
|
||||||
|
response = self.client.get_node_service_configs(self.session_id)
|
||||||
|
for config in response.configs:
|
||||||
|
service_configs = self.service_configs.setdefault(config.node_id, {})
|
||||||
|
service_configs[config.service] = config.data
|
||||||
|
logging.info("service file configs: %s", config.files)
|
||||||
|
for file_name in config.files:
|
||||||
|
file_configs = self.file_configs.setdefault(config.node_id, {})
|
||||||
|
files = file_configs.setdefault(config.service, {})
|
||||||
|
data = config.files[file_name]
|
||||||
|
files[file_name] = data
|
||||||
|
|
||||||
|
# draw session
|
||||||
|
self.app.canvas.reset_and_redraw(session)
|
||||||
|
|
||||||
|
# get metadata
|
||||||
|
response = self.client.get_session_metadata(self.session_id)
|
||||||
|
self.parse_metadata(response.config)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
# update ui to represent current state
|
||||||
|
if self.is_runtime():
|
||||||
|
self.app.toolbar.runtime_frame.tkraise()
|
||||||
|
self.app.toolbar.click_runtime_selection()
|
||||||
|
else:
|
||||||
|
self.app.toolbar.design_frame.tkraise()
|
||||||
|
self.app.toolbar.click_selection()
|
||||||
|
self.app.statusbar.progress_bar.stop()
|
||||||
|
|
||||||
|
def is_runtime(self):
|
||||||
|
return self.state == core_pb2.SessionState.RUNTIME
|
||||||
|
|
||||||
|
def parse_metadata(self, config):
|
||||||
|
# canvas setting
|
||||||
|
canvas_config = config.get("canvas")
|
||||||
|
logging.info("canvas metadata: %s", canvas_config)
|
||||||
|
if canvas_config:
|
||||||
|
canvas_config = json.loads(canvas_config)
|
||||||
|
|
||||||
|
gridlines = canvas_config.get("gridlines", True)
|
||||||
|
self.app.canvas.show_grid.set(gridlines)
|
||||||
|
|
||||||
|
fit_image = canvas_config.get("fit_image", False)
|
||||||
|
self.app.canvas.adjust_to_dim.set(fit_image)
|
||||||
|
|
||||||
|
wallpaper_style = canvas_config.get("wallpaper-style", 1)
|
||||||
|
self.app.canvas.scale_option.set(wallpaper_style)
|
||||||
|
|
||||||
|
width = self.app.guiconfig["preferences"]["width"]
|
||||||
|
height = self.app.guiconfig["preferences"]["height"]
|
||||||
|
dimensions = canvas_config.get("dimensions", [width, height])
|
||||||
|
self.app.canvas.redraw_canvas(dimensions)
|
||||||
|
|
||||||
|
wallpaper = canvas_config.get("wallpaper")
|
||||||
|
if wallpaper:
|
||||||
|
wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper))
|
||||||
|
self.app.canvas.set_wallpaper(wallpaper)
|
||||||
|
else:
|
||||||
|
self.app.canvas.redraw_canvas()
|
||||||
|
self.app.canvas.set_wallpaper(None)
|
||||||
|
|
||||||
|
# load saved shapes
|
||||||
|
shapes_config = config.get("shapes")
|
||||||
|
if shapes_config:
|
||||||
|
shapes_config = json.loads(shapes_config)
|
||||||
|
for shape_config in shapes_config:
|
||||||
|
logging.info("loading shape: %s", shape_config)
|
||||||
|
shape_type = shape_config["type"]
|
||||||
|
try:
|
||||||
|
shape_type = ShapeType(shape_type)
|
||||||
|
coords = shape_config["iconcoords"]
|
||||||
|
data = AnnotationData(
|
||||||
|
shape_config["label"],
|
||||||
|
shape_config["fontfamily"],
|
||||||
|
shape_config["fontsize"],
|
||||||
|
shape_config["labelcolor"],
|
||||||
|
shape_config["color"],
|
||||||
|
shape_config["border"],
|
||||||
|
shape_config["width"],
|
||||||
|
shape_config["bold"],
|
||||||
|
shape_config["italic"],
|
||||||
|
shape_config["underline"],
|
||||||
|
)
|
||||||
|
shape = Shape(
|
||||||
|
self.app, self.app.canvas, shape_type, *coords, data=data
|
||||||
|
)
|
||||||
|
self.app.canvas.shapes[shape.id] = shape
|
||||||
|
except ValueError:
|
||||||
|
logging.exception("unknown shape: %s", shape_type)
|
||||||
|
|
||||||
|
for tag in tags.ABOVE_WALLPAPER_TAGS:
|
||||||
|
self.app.canvas.tag_raise(tag)
|
||||||
|
|
||||||
|
def create_new_session(self):
|
||||||
|
"""
|
||||||
|
Create a new session
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self.client.create_session()
|
||||||
|
logging.info("created session: %s", response)
|
||||||
|
location_config = self.app.guiconfig["location"]
|
||||||
|
self.location = core_pb2.SessionLocation(
|
||||||
|
x=location_config["x"],
|
||||||
|
y=location_config["y"],
|
||||||
|
z=location_config["z"],
|
||||||
|
lat=location_config["lat"],
|
||||||
|
lon=location_config["lon"],
|
||||||
|
alt=location_config["alt"],
|
||||||
|
scale=location_config["scale"],
|
||||||
|
)
|
||||||
|
self.join_session(response.session_id, query_location=False)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def delete_session(self, session_id=None):
|
||||||
|
if session_id is None:
|
||||||
|
session_id = self.session_id
|
||||||
|
try:
|
||||||
|
response = self.client.delete_session(session_id)
|
||||||
|
logging.info("deleted session result: %s", response)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def set_up(self):
|
||||||
|
"""
|
||||||
|
Query sessions, if there exist any, prompt whether to join one
|
||||||
|
|
||||||
|
:return: existing sessions
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.client.connect()
|
||||||
|
|
||||||
|
# get service information
|
||||||
|
response = self.client.get_services()
|
||||||
|
for service in response.services:
|
||||||
|
group_services = self.services.setdefault(service.group, set())
|
||||||
|
group_services.add(service.name)
|
||||||
|
|
||||||
|
# if there are no sessions, create a new session, else join a session
|
||||||
|
response = self.client.get_sessions()
|
||||||
|
logging.info("current sessions: %s", response)
|
||||||
|
sessions = response.sessions
|
||||||
|
if len(sessions) == 0:
|
||||||
|
self.create_new_session()
|
||||||
|
else:
|
||||||
|
dialog = SessionsDialog(self.app, self.app)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
response = self.client.get_service_defaults(self.session_id)
|
||||||
|
self.default_services = {
|
||||||
|
x.node_type: set(x.services) for x in response.defaults
|
||||||
|
}
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.app.close()
|
||||||
|
|
||||||
|
def edit_node(self, core_node):
|
||||||
|
try:
|
||||||
|
self.client.edit_node(
|
||||||
|
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def start_session(self):
|
||||||
|
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
|
links = [x.link for x in self.links.values()]
|
||||||
|
wlan_configs = self.get_wlan_configs_proto()
|
||||||
|
mobility_configs = self.get_mobility_configs_proto()
|
||||||
|
emane_model_configs = self.get_emane_model_configs_proto()
|
||||||
|
hooks = list(self.hooks.values())
|
||||||
|
service_configs = self.get_service_configs_proto()
|
||||||
|
file_configs = self.get_service_file_configs_proto()
|
||||||
|
asymmetric_links = [
|
||||||
|
x.asymmetric_link for x in self.links.values() if x.asymmetric_link
|
||||||
|
]
|
||||||
|
if self.emane_config:
|
||||||
|
emane_config = {x: self.emane_config[x].value for x in self.emane_config}
|
||||||
|
else:
|
||||||
|
emane_config = None
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
try:
|
||||||
|
response = self.client.start_session(
|
||||||
|
self.session_id,
|
||||||
|
nodes,
|
||||||
|
links,
|
||||||
|
self.location,
|
||||||
|
hooks,
|
||||||
|
emane_config,
|
||||||
|
emane_model_configs,
|
||||||
|
wlan_configs,
|
||||||
|
mobility_configs,
|
||||||
|
service_configs,
|
||||||
|
file_configs,
|
||||||
|
asymmetric_links,
|
||||||
|
)
|
||||||
|
self.set_metadata()
|
||||||
|
process_time = time.perf_counter() - start
|
||||||
|
logging.debug(
|
||||||
|
"start session(%s), result: %s", self.session_id, response.result
|
||||||
|
)
|
||||||
|
self.app.statusbar.start_session_callback(process_time)
|
||||||
|
|
||||||
|
# display mobility players
|
||||||
|
for node_id, config in self.mobility_configs.items():
|
||||||
|
canvas_node = self.canvas_nodes[node_id]
|
||||||
|
mobility_player = MobilityPlayer(
|
||||||
|
self.app, self.app, canvas_node, config
|
||||||
|
)
|
||||||
|
mobility_player.show()
|
||||||
|
self.mobility_players[node_id] = mobility_player
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def stop_session(self, session_id=None):
|
||||||
|
if not session_id:
|
||||||
|
session_id = self.session_id
|
||||||
|
start = time.perf_counter()
|
||||||
|
try:
|
||||||
|
response = self.client.stop_session(session_id)
|
||||||
|
logging.debug(
|
||||||
|
"stopped session(%s), result: %s", session_id, response.result
|
||||||
|
)
|
||||||
|
process_time = time.perf_counter() - start
|
||||||
|
self.app.statusbar.stop_session_callback(process_time)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def set_metadata(self):
|
||||||
|
# create canvas data
|
||||||
|
wallpaper = None
|
||||||
|
if self.app.canvas.wallpaper_file:
|
||||||
|
wallpaper = Path(self.app.canvas.wallpaper_file).name
|
||||||
|
canvas_config = {
|
||||||
|
"wallpaper": wallpaper,
|
||||||
|
"wallpaper-style": self.app.canvas.scale_option.get(),
|
||||||
|
"gridlines": self.app.canvas.show_grid.get(),
|
||||||
|
"fit_image": self.app.canvas.adjust_to_dim.get(),
|
||||||
|
"dimensions": self.app.canvas.current_dimensions,
|
||||||
|
}
|
||||||
|
canvas_config = json.dumps(canvas_config)
|
||||||
|
|
||||||
|
# create shapes data
|
||||||
|
shapes = []
|
||||||
|
for shape in self.app.canvas.shapes.values():
|
||||||
|
shapes.append(shape.metadata())
|
||||||
|
shapes = json.dumps(shapes)
|
||||||
|
|
||||||
|
metadata = {"canvas": canvas_config, "shapes": shapes}
|
||||||
|
response = self.client.set_session_metadata(self.session_id, metadata)
|
||||||
|
logging.info("set session metadata: %s", response)
|
||||||
|
|
||||||
|
def launch_terminal(self, node_id):
|
||||||
|
try:
|
||||||
|
response = self.client.get_node_terminal(self.session_id, node_id)
|
||||||
|
logging.info("get terminal %s", response.terminal)
|
||||||
|
os.system(f"xterm -e {response.terminal} &")
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def save_xml(self, file_path):
|
||||||
|
"""
|
||||||
|
Save core session as to an xml file
|
||||||
|
|
||||||
|
:param str file_path: file path that user pick
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.state != core_pb2.SessionState.RUNTIME:
|
||||||
|
logging.debug(
|
||||||
|
"session state not runtime, send session data to the daemon..."
|
||||||
|
)
|
||||||
|
self.send_data()
|
||||||
|
response = self.client.save_xml(self.session_id, file_path)
|
||||||
|
logging.info("saved xml(%s): %s", file_path, response)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def open_xml(self, file_path):
|
||||||
|
"""
|
||||||
|
Open core xml
|
||||||
|
|
||||||
|
:param str file_path: file to open
|
||||||
|
:return: session id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self.client.open_xml(file_path)
|
||||||
|
logging.debug("open xml: %s", response)
|
||||||
|
self.join_session(response.session_id)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def get_node_service(self, node_id, service_name):
|
||||||
|
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
||||||
|
logging.debug("get node service %s", response)
|
||||||
|
return response.service
|
||||||
|
|
||||||
|
def set_node_service(self, node_id, service_name, startups, validations, shutdowns):
|
||||||
|
response = self.client.set_node_service(
|
||||||
|
self.session_id, node_id, service_name, startups, validations, shutdowns
|
||||||
|
)
|
||||||
|
logging.debug("set node service %s", response)
|
||||||
|
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
||||||
|
logging.debug("get node service : %s", response)
|
||||||
|
return response.service
|
||||||
|
|
||||||
|
def get_node_service_file(self, node_id, service_name, file_name):
|
||||||
|
response = self.client.get_node_service_file(
|
||||||
|
self.session_id, node_id, service_name, file_name
|
||||||
|
)
|
||||||
|
logging.debug("get service file %s", response)
|
||||||
|
return response.data
|
||||||
|
|
||||||
|
def set_node_service_file(self, node_id, service_name, file_name, data):
|
||||||
|
response = self.client.set_node_service_file(
|
||||||
|
self.session_id, node_id, service_name, file_name, data
|
||||||
|
)
|
||||||
|
logging.debug("set node service file %s", response)
|
||||||
|
|
||||||
|
def create_nodes_and_links(self):
|
||||||
|
"""
|
||||||
|
create nodes and links that have not been created yet
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
|
link_protos = [x.link for x in self.links.values()]
|
||||||
|
if self.state != core_pb2.SessionState.DEFINITION:
|
||||||
|
self.client.set_session_state(
|
||||||
|
self.session_id, core_pb2.SessionState.DEFINITION
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.set_session_state(self.session_id, core_pb2.SessionState.DEFINITION)
|
||||||
|
for node_proto in node_protos:
|
||||||
|
response = self.client.add_node(self.session_id, node_proto)
|
||||||
|
logging.debug("create node: %s", response)
|
||||||
|
for link_proto in link_protos:
|
||||||
|
response = self.client.add_link(
|
||||||
|
self.session_id,
|
||||||
|
link_proto.node_one_id,
|
||||||
|
link_proto.node_two_id,
|
||||||
|
link_proto.interface_one,
|
||||||
|
link_proto.interface_two,
|
||||||
|
link_proto.options,
|
||||||
|
)
|
||||||
|
logging.debug("create link: %s", response)
|
||||||
|
|
||||||
|
def send_data(self):
|
||||||
|
"""
|
||||||
|
send to daemon all session info, but don't start the session
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self.create_nodes_and_links()
|
||||||
|
for config_proto in self.get_wlan_configs_proto():
|
||||||
|
self.client.set_wlan_config(
|
||||||
|
self.session_id, config_proto.node_id, config_proto.config
|
||||||
|
)
|
||||||
|
for config_proto in self.get_mobility_configs_proto():
|
||||||
|
self.client.set_mobility_config(
|
||||||
|
self.session_id, config_proto.node_id, config_proto.config
|
||||||
|
)
|
||||||
|
for config_proto in self.get_service_configs_proto():
|
||||||
|
self.client.set_node_service(
|
||||||
|
self.session_id,
|
||||||
|
config_proto.node_id,
|
||||||
|
config_proto.service,
|
||||||
|
config_proto.startup,
|
||||||
|
config_proto.validate,
|
||||||
|
config_proto.shutdown,
|
||||||
|
)
|
||||||
|
for config_proto in self.get_service_file_configs_proto():
|
||||||
|
self.client.set_node_service_file(
|
||||||
|
self.session_id,
|
||||||
|
config_proto.node_id,
|
||||||
|
config_proto.service,
|
||||||
|
config_proto.file,
|
||||||
|
config_proto.data,
|
||||||
|
)
|
||||||
|
for hook in self.hooks.values():
|
||||||
|
self.client.add_hook(self.session_id, hook.state, hook.file, hook.data)
|
||||||
|
for config_proto in self.get_emane_model_configs_proto():
|
||||||
|
self.client.set_emane_model_config(
|
||||||
|
self.session_id,
|
||||||
|
config_proto.node_id,
|
||||||
|
config_proto.model,
|
||||||
|
config_proto.config,
|
||||||
|
config_proto.interface_id,
|
||||||
|
)
|
||||||
|
if self.emane_config:
|
||||||
|
config = {x: self.emane_config[x].value for x in self.emane_config}
|
||||||
|
self.client.set_emane_config(self.session_id, config)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Clean ups when done using grpc
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
logging.debug("close grpc")
|
||||||
|
self.client.close()
|
||||||
|
|
||||||
|
def next_node_id(self):
|
||||||
|
"""
|
||||||
|
Get the next usable node id.
|
||||||
|
|
||||||
|
:return: the next id to be used
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
if i not in self.canvas_nodes:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def create_node(self, x, y, node_type, model):
|
||||||
|
"""
|
||||||
|
Add node, with information filled in, to grpc manager
|
||||||
|
|
||||||
|
:param int x: x coord
|
||||||
|
:param int y: y coord
|
||||||
|
:param core_pb2.NodeType node_type: node type
|
||||||
|
:param str model: node model
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
node_id = self.next_node_id()
|
||||||
|
position = core_pb2.Position(x=x, y=y)
|
||||||
|
image = None
|
||||||
|
if NodeUtils.is_image_node(node_type):
|
||||||
|
image = "ubuntu:latest"
|
||||||
|
emane = None
|
||||||
|
if node_type == core_pb2.NodeType.EMANE:
|
||||||
|
emane = self.emane_models[0]
|
||||||
|
node = core_pb2.Node(
|
||||||
|
id=node_id,
|
||||||
|
type=node_type,
|
||||||
|
name=f"n{node_id}",
|
||||||
|
model=model,
|
||||||
|
position=position,
|
||||||
|
image=image,
|
||||||
|
emane=emane,
|
||||||
|
)
|
||||||
|
logging.debug(
|
||||||
|
"adding node to core session: %s, coords: (%s, %s), name: %s",
|
||||||
|
self.session_id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
node.name,
|
||||||
|
)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def delete_graph_nodes(self, canvas_nodes):
|
||||||
|
"""
|
||||||
|
remove the nodes selected by the user and anything related to that node
|
||||||
|
such as link, configurations, interfaces
|
||||||
|
|
||||||
|
:param list canvas_nodes: list of nodes to delete
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
edges = set()
|
||||||
|
for canvas_node in canvas_nodes:
|
||||||
|
node_id = canvas_node.core_node.id
|
||||||
|
if node_id not in self.canvas_nodes:
|
||||||
|
logging.error("unknown node: %s", node_id)
|
||||||
|
continue
|
||||||
|
del self.canvas_nodes[node_id]
|
||||||
|
if node_id in self.mobility_configs:
|
||||||
|
del self.mobility_configs[node_id]
|
||||||
|
if node_id in self.wlan_configs:
|
||||||
|
del self.wlan_configs[node_id]
|
||||||
|
for key in list(self.emane_model_configs):
|
||||||
|
node_id, _, _ = key
|
||||||
|
if node_id == node_id:
|
||||||
|
del self.emane_model_configs[key]
|
||||||
|
|
||||||
|
for edge in canvas_node.edges:
|
||||||
|
if edge in edges:
|
||||||
|
continue
|
||||||
|
edges.add(edge)
|
||||||
|
if edge.token not in self.links:
|
||||||
|
logging.error("unknown edge: %s", edge.token)
|
||||||
|
del self.links[edge.token]
|
||||||
|
|
||||||
|
def create_interface(self, canvas_node):
|
||||||
|
node = canvas_node.core_node
|
||||||
|
ip4, ip6, prefix = self.interfaces_manager.get_ips(node.id)
|
||||||
|
interface_id = len(canvas_node.interfaces)
|
||||||
|
name = f"eth{interface_id}"
|
||||||
|
interface = core_pb2.Interface(
|
||||||
|
id=interface_id, name=name, ip4=ip4, ip4mask=prefix, ip6=ip6, ip6mask=prefix
|
||||||
|
)
|
||||||
|
canvas_node.interfaces.append(interface)
|
||||||
|
logging.debug(
|
||||||
|
"create node(%s) interface IPv4: %s, name: %s",
|
||||||
|
node.name,
|
||||||
|
interface.ip4,
|
||||||
|
interface.name,
|
||||||
|
)
|
||||||
|
return interface
|
||||||
|
|
||||||
|
def create_link(self, edge, canvas_src_node, canvas_dst_node):
|
||||||
|
"""
|
||||||
|
Create core link for a pair of canvas nodes, with token referencing
|
||||||
|
the canvas edge.
|
||||||
|
|
||||||
|
:param edge: edge for link
|
||||||
|
:param canvas_src_node: canvas node one
|
||||||
|
:param canvas_dst_node: canvas node two
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
src_node = canvas_src_node.core_node
|
||||||
|
dst_node = canvas_dst_node.core_node
|
||||||
|
|
||||||
|
# determine subnet
|
||||||
|
self.interfaces_manager.determine_subnet(canvas_src_node, canvas_dst_node)
|
||||||
|
|
||||||
|
src_interface = None
|
||||||
|
if NodeUtils.is_container_node(src_node.type):
|
||||||
|
src_interface = self.create_interface(canvas_src_node)
|
||||||
|
edge.src_interface = src_interface
|
||||||
|
self.interface_to_edge[(src_node.id, src_interface.id)] = edge.token
|
||||||
|
|
||||||
|
dst_interface = None
|
||||||
|
if NodeUtils.is_container_node(dst_node.type):
|
||||||
|
dst_interface = self.create_interface(canvas_dst_node)
|
||||||
|
edge.dst_interface = dst_interface
|
||||||
|
self.interface_to_edge[(dst_node.id, dst_interface.id)] = edge.token
|
||||||
|
|
||||||
|
link = core_pb2.Link(
|
||||||
|
type=core_pb2.LinkType.WIRED,
|
||||||
|
node_one_id=src_node.id,
|
||||||
|
node_two_id=dst_node.id,
|
||||||
|
interface_one=src_interface,
|
||||||
|
interface_two=dst_interface,
|
||||||
|
)
|
||||||
|
edge.set_link(link)
|
||||||
|
self.links[edge.token] = edge
|
||||||
|
|
||||||
|
def get_wlan_configs_proto(self):
|
||||||
|
configs = []
|
||||||
|
for node_id, config in self.wlan_configs.items():
|
||||||
|
config = {x: config[x].value for x in config}
|
||||||
|
wlan_config = core_pb2.WlanConfig(node_id=node_id, config=config)
|
||||||
|
configs.append(wlan_config)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def get_mobility_configs_proto(self):
|
||||||
|
configs = []
|
||||||
|
for node_id, config in self.mobility_configs.items():
|
||||||
|
config = {x: config[x].value for x in config}
|
||||||
|
mobility_config = core_pb2.MobilityConfig(node_id=node_id, config=config)
|
||||||
|
configs.append(mobility_config)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def get_emane_model_configs_proto(self):
|
||||||
|
configs = []
|
||||||
|
for key, config in self.emane_model_configs.items():
|
||||||
|
node_id, model, interface = key
|
||||||
|
config = {x: config[x].value for x in config}
|
||||||
|
if interface is None:
|
||||||
|
interface = -1
|
||||||
|
config_proto = core_pb2.EmaneModelConfig(
|
||||||
|
node_id=node_id, interface_id=interface, model=model, config=config
|
||||||
|
)
|
||||||
|
configs.append(config_proto)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def get_service_configs_proto(self):
|
||||||
|
configs = []
|
||||||
|
for node_id, services in self.service_configs.items():
|
||||||
|
for name, config in services.items():
|
||||||
|
config_proto = core_pb2.ServiceConfig(
|
||||||
|
node_id=node_id,
|
||||||
|
service=name,
|
||||||
|
startup=config.startup,
|
||||||
|
validate=config.validate,
|
||||||
|
shutdown=config.shutdown,
|
||||||
|
)
|
||||||
|
configs.append(config_proto)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def get_service_file_configs_proto(self):
|
||||||
|
configs = []
|
||||||
|
for (node_id, file_configs) in self.file_configs.items():
|
||||||
|
for service, file_config in file_configs.items():
|
||||||
|
for file, data in file_config.items():
|
||||||
|
config_proto = core_pb2.ServiceFileConfig(
|
||||||
|
node_id=node_id, service=service, file=file, data=data
|
||||||
|
)
|
||||||
|
configs.append(config_proto)
|
||||||
|
return configs
|
||||||
|
|
||||||
|
def run(self, node_id):
|
||||||
|
logging.info("running node(%s) cmd: %s", node_id, self.observer)
|
||||||
|
return self.client.node_command(self.session_id, node_id, self.observer).output
|
||||||
|
|
||||||
|
def get_wlan_config(self, node_id):
|
||||||
|
config = self.wlan_configs.get(node_id)
|
||||||
|
if not config:
|
||||||
|
response = self.client.get_wlan_config(self.session_id, node_id)
|
||||||
|
config = response.config
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_mobility_config(self, node_id):
|
||||||
|
config = self.mobility_configs.get(node_id)
|
||||||
|
if not config:
|
||||||
|
response = self.client.get_mobility_config(self.session_id, node_id)
|
||||||
|
config = response.config
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_emane_model_config(self, node_id, model, interface=None):
|
||||||
|
logging.info("getting emane model config: %s %s %s", node_id, model, interface)
|
||||||
|
config = self.emane_model_configs.get((node_id, model, interface))
|
||||||
|
if not config:
|
||||||
|
if interface is None:
|
||||||
|
interface = -1
|
||||||
|
response = self.client.get_emane_model_config(
|
||||||
|
self.session_id, node_id, model, interface
|
||||||
|
)
|
||||||
|
config = response.config
|
||||||
|
return config
|
||||||
|
|
||||||
|
def set_emane_model_config(self, node_id, model, config, interface=None):
|
||||||
|
logging.info("setting emane model config: %s %s %s", node_id, model, interface)
|
||||||
|
self.emane_model_configs[(node_id, model, interface)] = config
|
BIN
coretk/coretk/data/backgrounds/sample1-bg.gif
Normal file
After Width: | Height: | Size: 312 KiB |
BIN
coretk/coretk/data/backgrounds/sample4-bg.jpg
Normal file
After Width: | Height: | Size: 196 KiB |
BIN
coretk/coretk/data/icons/OVS.gif
Executable file
After Width: | Height: | Size: 744 B |
BIN
coretk/coretk/data/icons/alert.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
coretk/coretk/data/icons/antenna.gif
Normal file
After Width: | Height: | Size: 230 B |
BIN
coretk/coretk/data/icons/core-icon.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
coretk/coretk/data/icons/docker.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
coretk/coretk/data/icons/document-new.gif
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
coretk/coretk/data/icons/document-properties.gif
Normal file
After Width: | Height: | Size: 635 B |
BIN
coretk/coretk/data/icons/document-save.gif
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
coretk/coretk/data/icons/edit-delete.gif
Normal file
After Width: | Height: | Size: 1,006 B |
BIN
coretk/coretk/data/icons/edit-node.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
coretk/coretk/data/icons/emane.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
coretk/coretk/data/icons/fileopen.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/icons/host.png
Normal file
After Width: | Height: | Size: 642 B |
BIN
coretk/coretk/data/icons/hub.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
coretk/coretk/data/icons/lanswitch.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/icons/link.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
coretk/coretk/data/icons/lxc.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
coretk/coretk/data/icons/marker.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/data/icons/markerclear.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
coretk/coretk/data/icons/mdr.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
coretk/coretk/data/icons/observe.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/icons/oval.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
coretk/coretk/data/icons/pause.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
coretk/coretk/data/icons/pc.png
Normal file
After Width: | Height: | Size: 828 B |
BIN
coretk/coretk/data/icons/plot.gif
Normal file
After Width: | Height: | Size: 265 B |
BIN
coretk/coretk/data/icons/prouter.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
coretk/coretk/data/icons/rectangle.png
Normal file
After Width: | Height: | Size: 259 B |
BIN
coretk/coretk/data/icons/rj45.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/icons/router.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
coretk/coretk/data/icons/run.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
coretk/coretk/data/icons/select.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
coretk/coretk/data/icons/start.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
coretk/coretk/data/icons/stop.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
coretk/coretk/data/icons/text.png
Normal file
After Width: | Height: | Size: 314 B |
BIN
coretk/coretk/data/icons/tunnel.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
coretk/coretk/data/icons/twonode.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
coretk/coretk/data/icons/wlan.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
28
coretk/coretk/data/mobility/sample1.scen
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#
|
||||||
|
# nodes: 4, max time: 27.000000, max x: 600.00, max y: 600.00
|
||||||
|
# nominal range: 300.00 link bw: 54000000.00
|
||||||
|
# pause: 30.00, min speed 1.50 max speed: 4.50
|
||||||
|
|
||||||
|
$node_(6) set X_ 780.0
|
||||||
|
$node_(6) set Y_ 228.0
|
||||||
|
$node_(6) set Z_ 0.00
|
||||||
|
$node_(7) set X_ 816.0
|
||||||
|
$node_(7) set Y_ 348.0
|
||||||
|
$node_(7) set Z_ 0.00
|
||||||
|
$node_(8) set X_ 672.0
|
||||||
|
$node_(8) set Y_ 420.0
|
||||||
|
$node_(8) set Z_ 0.00
|
||||||
|
$node_(9) set X_ 672.0
|
||||||
|
$node_(9) set Y_ 96.0
|
||||||
|
$node_(9) set Z_ 0.00
|
||||||
|
$ns_ at 1.00 "$node_(6) setdest 500.0 178.0 25.0"
|
||||||
|
$ns_ at 2.00 "$node_(7) setdest 400.0 288.0 15.0"
|
||||||
|
$ns_ at 1.00 "$node_(8) setdest 590.0 520.0 17.0"
|
||||||
|
$ns_ at 3.00 "$node_(9) setdest 720.0 300.0 20.0"
|
||||||
|
$ns_ at 8.00 "$node_(7) setdest 600.0 350.0 10.0"
|
||||||
|
$ns_ at 9.00 "$node_(8) setdest 730.0 300.0 15.0"
|
||||||
|
$ns_ at 10.00 "$node_(6) setdest 600.0 108.0 10.0"
|
||||||
|
$ns_ at 16.00 "$node_(9) setdest 672.0 96.0 20.0"
|
||||||
|
$ns_ at 17.00 "$node_(7) setdest 816.0 348.0 20.0"
|
||||||
|
$ns_ at 18.00 "$node_(6) setdest 780.0 228.0 25.0"
|
||||||
|
$ns_ at 22.00 "$node_(8) setdest 672.0 420.0 20.0"
|
BIN
coretk/coretk/data/oldicons/docker.gif
Normal file
After Width: | Height: | Size: 719 B |
BIN
coretk/coretk/data/oldicons/emane.gif
Normal file
After Width: | Height: | Size: 337 B |
BIN
coretk/coretk/data/oldicons/host.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/data/oldicons/hub.gif
Normal file
After Width: | Height: | Size: 719 B |
BIN
coretk/coretk/data/oldicons/lanswitch.gif
Normal file
After Width: | Height: | Size: 744 B |
BIN
coretk/coretk/data/oldicons/link.gif
Normal file
After Width: | Height: | Size: 86 B |
BIN
coretk/coretk/data/oldicons/lxc.gif
Normal file
After Width: | Height: | Size: 724 B |
BIN
coretk/coretk/data/oldicons/marker.gif
Normal file
After Width: | Height: | Size: 375 B |
BIN
coretk/coretk/data/oldicons/mdr.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/data/oldicons/oval.gif
Normal file
After Width: | Height: | Size: 174 B |
BIN
coretk/coretk/data/oldicons/pc.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
coretk/coretk/data/oldicons/rectangle.gif
Normal file
After Width: | Height: | Size: 160 B |
BIN
coretk/coretk/data/oldicons/rj45.gif
Normal file
After Width: | Height: | Size: 755 B |
BIN
coretk/coretk/data/oldicons/router.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/oldicons/router_green.gif
Normal file
After Width: | Height: | Size: 753 B |
BIN
coretk/coretk/data/oldicons/run.gif
Normal file
After Width: | Height: | Size: 324 B |
BIN
coretk/coretk/data/oldicons/select.gif
Normal file
After Width: | Height: | Size: 925 B |
BIN
coretk/coretk/data/oldicons/start.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
coretk/coretk/data/oldicons/stop.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
coretk/coretk/data/oldicons/text.gif
Normal file
After Width: | Height: | Size: 127 B |
BIN
coretk/coretk/data/oldicons/tunnel.gif
Normal file
After Width: | Height: | Size: 799 B |
BIN
coretk/coretk/data/oldicons/twonode.gif
Normal file
After Width: | Height: | Size: 220 B |
BIN
coretk/coretk/data/oldicons/wlan.gif
Normal file
After Width: | Height: | Size: 173 B |
1869
coretk/coretk/data/xmls/sample1.xml
Normal file
0
coretk/coretk/dialogs/__init__.py
Normal file
44
coretk/coretk/dialogs/about.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.widgets import CodeText
|
||||||
|
|
||||||
|
LICENSE = """\
|
||||||
|
Copyright (c) 2005-2020, the Boeing Company.
|
||||||
|
|
||||||
|
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 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.\
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class AboutDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "About CORE", modal=True)
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
codetext = CodeText(self.top)
|
||||||
|
codetext.text.insert("1.0", LICENSE)
|
||||||
|
codetext.text.config(state=tk.DISABLED)
|
||||||
|
codetext.grid(sticky="nsew")
|
171
coretk/coretk/dialogs/alerts.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
"""
|
||||||
|
check engine light
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from grpc import RpcError
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import CodeText
|
||||||
|
|
||||||
|
|
||||||
|
class AlertsDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Alerts", modal=True)
|
||||||
|
self.app = app
|
||||||
|
self.tree = None
|
||||||
|
self.codetext = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.tree = ttk.Treeview(
|
||||||
|
frame,
|
||||||
|
columns=("time", "level", "session_id", "node", "source"),
|
||||||
|
show="headings",
|
||||||
|
)
|
||||||
|
self.tree.grid(row=0, column=0, sticky="nsew")
|
||||||
|
self.tree.column("time", stretch=tk.YES)
|
||||||
|
self.tree.heading("time", text="Time")
|
||||||
|
self.tree.column("level", stretch=tk.YES)
|
||||||
|
self.tree.heading("level", text="Level")
|
||||||
|
self.tree.column("session_id", stretch=tk.YES)
|
||||||
|
self.tree.heading("session_id", text="Session ID")
|
||||||
|
self.tree.column("node", stretch=tk.YES)
|
||||||
|
self.tree.heading("node", text="Node")
|
||||||
|
self.tree.column("source", stretch=tk.YES)
|
||||||
|
self.tree.heading("source", text="Source")
|
||||||
|
self.tree.bind("<<TreeviewSelect>>", self.click_select)
|
||||||
|
|
||||||
|
for alarm in self.app.statusbar.core_alarms:
|
||||||
|
level = self.get_level(alarm.level)
|
||||||
|
self.tree.insert(
|
||||||
|
"",
|
||||||
|
tk.END,
|
||||||
|
text=str(alarm.date),
|
||||||
|
values=(
|
||||||
|
alarm.date,
|
||||||
|
level + " (%s)" % alarm.level,
|
||||||
|
alarm.session_id,
|
||||||
|
alarm.node_id,
|
||||||
|
alarm.source,
|
||||||
|
),
|
||||||
|
tags=(level,),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.tree.tag_configure("ERROR", background="#ff6666")
|
||||||
|
self.tree.tag_configure("FATAL", background="#d9d9d9")
|
||||||
|
self.tree.tag_configure("WARNING", background="#ffff99")
|
||||||
|
self.tree.tag_configure("NOTICE", background="#85e085")
|
||||||
|
|
||||||
|
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
|
||||||
|
yscrollbar.grid(row=0, column=1, sticky="ns")
|
||||||
|
self.tree.configure(yscrollcommand=yscrollbar.set)
|
||||||
|
|
||||||
|
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
|
||||||
|
xscrollbar.grid(row=1, sticky="ew")
|
||||||
|
self.tree.configure(xscrollcommand=xscrollbar.set)
|
||||||
|
|
||||||
|
self.codetext = CodeText(self.top)
|
||||||
|
self.codetext.text.config(state=tk.DISABLED)
|
||||||
|
self.codetext.grid(sticky="nsew", pady=PADY)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Reset", command=self.reset_alerts)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Daemon Log", command=self.daemon_log)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Node Log")
|
||||||
|
button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Close", command=self.destroy)
|
||||||
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
|
def reset_alerts(self):
|
||||||
|
self.codetext.text.delete("1.0", tk.END)
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
self.app.statusbar.core_alarms.clear()
|
||||||
|
|
||||||
|
def daemon_log(self):
|
||||||
|
dialog = DaemonLog(self, self.app)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def get_level(self, level):
|
||||||
|
if level == core_pb2.ExceptionLevel.ERROR:
|
||||||
|
return "ERROR"
|
||||||
|
if level == core_pb2.ExceptionLevel.FATAL:
|
||||||
|
return "FATAL"
|
||||||
|
if level == core_pb2.ExceptionLevel.WARNING:
|
||||||
|
return "WARNING"
|
||||||
|
if level == core_pb2.ExceptionLevel.NOTICE:
|
||||||
|
return "NOTICE"
|
||||||
|
|
||||||
|
def click_select(self, event):
|
||||||
|
current = self.tree.selection()
|
||||||
|
values = self.tree.item(current)["values"]
|
||||||
|
time = values[0]
|
||||||
|
level = values[1]
|
||||||
|
session_id = values[2]
|
||||||
|
node_id = values[3]
|
||||||
|
source = values[4]
|
||||||
|
text = "DATE: %s\nLEVEL: %s\nNODE: %s (%s)\nSESSION: %s\nSOURCE: %s\n\n" % (
|
||||||
|
time,
|
||||||
|
level,
|
||||||
|
node_id,
|
||||||
|
self.app.core.canvas_nodes[node_id].core_node.name,
|
||||||
|
session_id,
|
||||||
|
source,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
sid = self.app.core.session_id
|
||||||
|
self.app.core.client.get_node(sid, node_id)
|
||||||
|
text = text + "node created"
|
||||||
|
except RpcError:
|
||||||
|
text = text + "node not created"
|
||||||
|
self.codetext.text.delete("1.0", "end")
|
||||||
|
self.codetext.text.insert("1.0", text)
|
||||||
|
|
||||||
|
|
||||||
|
class DaemonLog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "core-daemon log", modal=True)
|
||||||
|
self.columnconfigure(0, weight=1)
|
||||||
|
self.path = tk.StringVar(value="/var/log/core-daemon.log")
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(1, weight=1)
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(row=0, column=0, sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=9)
|
||||||
|
label = ttk.Label(frame, text="File", anchor="w")
|
||||||
|
label.grid(row=0, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.path, state="disabled")
|
||||||
|
entry.grid(row=0, column=1, sticky="ew")
|
||||||
|
try:
|
||||||
|
file = open("/var/log/core-daemon.log", "r")
|
||||||
|
log = file.readlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
log = "Log file not found"
|
||||||
|
codetext = CodeText(self.top)
|
||||||
|
codetext.text.insert("1.0", log)
|
||||||
|
codetext.text.see("end")
|
||||||
|
codetext.text.config(state=tk.DISABLED)
|
||||||
|
codetext.grid(row=1, column=0, sticky="nsew")
|
254
coretk/coretk/dialogs/canvassizeandscale.py
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
"""
|
||||||
|
size and scale
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import font, ttk
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
PIXEL_SCALE = 100
|
||||||
|
|
||||||
|
|
||||||
|
class SizeAndScaleDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
"""
|
||||||
|
create an instance for size and scale object
|
||||||
|
|
||||||
|
:param app: main application
|
||||||
|
"""
|
||||||
|
super().__init__(master, app, "Canvas Size and Scale", modal=True)
|
||||||
|
self.canvas = self.app.canvas
|
||||||
|
self.validation = app.validation
|
||||||
|
self.section_font = font.Font(weight="bold")
|
||||||
|
width, height = self.canvas.current_dimensions
|
||||||
|
self.pixel_width = tk.IntVar(value=width)
|
||||||
|
self.pixel_height = tk.IntVar(value=height)
|
||||||
|
location = self.app.core.location
|
||||||
|
self.x = tk.DoubleVar(value=location.x)
|
||||||
|
self.y = tk.DoubleVar(value=location.y)
|
||||||
|
self.lat = tk.DoubleVar(value=location.lat)
|
||||||
|
self.lon = tk.DoubleVar(value=location.lon)
|
||||||
|
self.alt = tk.DoubleVar(value=location.alt)
|
||||||
|
self.scale = tk.DoubleVar(value=location.scale)
|
||||||
|
self.meters_width = tk.IntVar(value=width / PIXEL_SCALE * location.scale)
|
||||||
|
self.meters_height = tk.IntVar(value=height / PIXEL_SCALE * location.scale)
|
||||||
|
self.save_default = tk.BooleanVar(value=False)
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.draw_size()
|
||||||
|
self.draw_scale()
|
||||||
|
self.draw_reference_point()
|
||||||
|
self.draw_save_as_default()
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_size(self):
|
||||||
|
label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="ew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# draw size row 1
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Width")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.pixel_width,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
label = ttk.Label(frame, text="x Height")
|
||||||
|
label.grid(row=0, column=2, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.pixel_height,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
|
||||||
|
label = ttk.Label(frame, text="Pixels")
|
||||||
|
label.grid(row=0, column=4, sticky="w")
|
||||||
|
|
||||||
|
# draw size row 2
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Width")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.meters_width,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
label = ttk.Label(frame, text="x Height")
|
||||||
|
label.grid(row=0, column=2, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.meters_height,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
|
||||||
|
label = ttk.Label(frame, text="Meters")
|
||||||
|
label.grid(row=0, column=4, sticky="w")
|
||||||
|
|
||||||
|
def draw_scale(self):
|
||||||
|
label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="ew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
label = ttk.Label(frame, text=f"{PIXEL_SCALE} Pixels =")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.scale,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
label = ttk.Label(frame, text="Meters")
|
||||||
|
label.grid(row=0, column=2, sticky="w")
|
||||||
|
|
||||||
|
def draw_reference_point(self):
|
||||||
|
label_frame = ttk.Labelframe(
|
||||||
|
self.top, text="Reference Point", padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
label_frame.grid(sticky="ew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(
|
||||||
|
label_frame, text="Default is (0, 0), the upper left corner of the canvas"
|
||||||
|
)
|
||||||
|
label.grid()
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="X")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.x,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Y")
|
||||||
|
label.grid(row=0, column=2, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.y,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
label = ttk.Label(label_frame, text="Translates To")
|
||||||
|
label.grid()
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
frame.columnconfigure(5, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Lat")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.lat,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Lon")
|
||||||
|
label.grid(row=0, column=2, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.lon,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Alt")
|
||||||
|
label.grid(row=0, column=4, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.alt,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
|
||||||
|
entry.grid(row=0, column=5, sticky="ew")
|
||||||
|
|
||||||
|
def draw_save_as_default(self):
|
||||||
|
button = ttk.Checkbutton(
|
||||||
|
self.top, text="Save as default?", variable=self.save_default
|
||||||
|
)
|
||||||
|
button.grid(sticky="w", pady=PADY)
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
width, height = self.pixel_width.get(), self.pixel_height.get()
|
||||||
|
self.canvas.redraw_canvas((width, height))
|
||||||
|
if self.canvas.wallpaper:
|
||||||
|
self.canvas.redraw_wallpaper()
|
||||||
|
location = self.app.core.location
|
||||||
|
location.x = self.x.get()
|
||||||
|
location.y = self.y.get()
|
||||||
|
location.lat = self.lat.get()
|
||||||
|
location.lon = self.lon.get()
|
||||||
|
location.alt = self.alt.get()
|
||||||
|
location.scale = self.scale.get()
|
||||||
|
if self.save_default.get():
|
||||||
|
location_config = self.app.guiconfig["location"]
|
||||||
|
location_config["x"] = location.x
|
||||||
|
location_config["y"] = location.y
|
||||||
|
location_config["z"] = location.z
|
||||||
|
location_config["lat"] = location.lat
|
||||||
|
location_config["lon"] = location.lon
|
||||||
|
location_config["alt"] = location.alt
|
||||||
|
location_config["scale"] = location.scale
|
||||||
|
preferences = self.app.guiconfig["preferences"]
|
||||||
|
preferences["width"] = width
|
||||||
|
preferences["height"] = height
|
||||||
|
self.app.save_config()
|
||||||
|
self.destroy()
|
179
coretk/coretk/dialogs/canvaswallpaper.py
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
"""
|
||||||
|
set wallpaper
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.appconfig import BACKGROUNDS_PATH
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.images import Images
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import image_chooser
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasBackgroundDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
"""
|
||||||
|
create an instance of CanvasWallpaper object
|
||||||
|
|
||||||
|
:param coretk.app.Application app: root application
|
||||||
|
"""
|
||||||
|
super().__init__(master, app, "Canvas Background", modal=True)
|
||||||
|
self.canvas = self.app.canvas
|
||||||
|
self.scale_option = tk.IntVar(value=self.canvas.scale_option.get())
|
||||||
|
self.show_grid = tk.BooleanVar(value=self.canvas.show_grid.get())
|
||||||
|
self.adjust_to_dim = tk.BooleanVar(value=self.canvas.adjust_to_dim.get())
|
||||||
|
self.filename = tk.StringVar(value=self.canvas.wallpaper_file)
|
||||||
|
self.image_label = None
|
||||||
|
self.options = []
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.draw_image()
|
||||||
|
self.draw_image_label()
|
||||||
|
self.draw_image_selection()
|
||||||
|
self.draw_options()
|
||||||
|
self.draw_additional_options()
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_image(self):
|
||||||
|
self.image_label = ttk.Label(
|
||||||
|
self.top, text="(image preview)", width=32, anchor=tk.CENTER
|
||||||
|
)
|
||||||
|
self.image_label.grid(pady=PADY)
|
||||||
|
|
||||||
|
def draw_image_label(self):
|
||||||
|
label = ttk.Label(self.top, text="Image filename: ")
|
||||||
|
label.grid(sticky="ew")
|
||||||
|
if self.filename.get():
|
||||||
|
self.draw_preview()
|
||||||
|
|
||||||
|
def draw_image_selection(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=2)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=1)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.filename)
|
||||||
|
entry.focus()
|
||||||
|
entry.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="...", command=self.click_open_image)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Clear", command=self.click_clear)
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
def draw_options(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame, text="upper-left", value=1, variable=self.scale_option
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=0, sticky="ew")
|
||||||
|
self.options.append(button)
|
||||||
|
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame, text="centered", value=2, variable=self.scale_option
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
self.options.append(button)
|
||||||
|
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame, text="scaled", value=3, variable=self.scale_option
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
self.options.append(button)
|
||||||
|
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame, text="titled", value=4, variable=self.scale_option
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
self.options.append(button)
|
||||||
|
|
||||||
|
def draw_additional_options(self):
|
||||||
|
checkbutton = ttk.Checkbutton(
|
||||||
|
self.top, text="Show grid", variable=self.show_grid
|
||||||
|
)
|
||||||
|
checkbutton.grid(sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
checkbutton = ttk.Checkbutton(
|
||||||
|
self.top,
|
||||||
|
text="Adjust canvas size to image dimensions",
|
||||||
|
variable=self.adjust_to_dim,
|
||||||
|
command=self.click_adjust_canvas,
|
||||||
|
)
|
||||||
|
checkbutton.grid(sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(pady=PADY, sticky="ew")
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_open_image(self):
|
||||||
|
filename = image_chooser(self, BACKGROUNDS_PATH)
|
||||||
|
if filename:
|
||||||
|
self.filename.set(filename)
|
||||||
|
self.draw_preview()
|
||||||
|
|
||||||
|
def draw_preview(self):
|
||||||
|
image = Images.create(self.filename.get(), 250, 135)
|
||||||
|
self.image_label.config(image=image)
|
||||||
|
self.image_label.image = image
|
||||||
|
|
||||||
|
def click_clear(self):
|
||||||
|
"""
|
||||||
|
delete like shown in image link entry if there is any
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
# delete entry
|
||||||
|
self.filename.set("")
|
||||||
|
# delete display image
|
||||||
|
self.image_label.config(image="", width=32)
|
||||||
|
self.image_label.image = None
|
||||||
|
|
||||||
|
def click_adjust_canvas(self):
|
||||||
|
# deselect all radio buttons and grey them out
|
||||||
|
if self.adjust_to_dim.get():
|
||||||
|
self.scale_option.set(0)
|
||||||
|
for option in self.options:
|
||||||
|
option.config(state=tk.DISABLED)
|
||||||
|
# turn back the radio button to active state so that user can choose again
|
||||||
|
else:
|
||||||
|
self.scale_option.set(1)
|
||||||
|
for option in self.options:
|
||||||
|
option.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
self.canvas.scale_option.set(self.scale_option.get())
|
||||||
|
self.canvas.show_grid.set(self.show_grid.get())
|
||||||
|
self.canvas.adjust_to_dim.set(self.adjust_to_dim.get())
|
||||||
|
self.canvas.update_grid()
|
||||||
|
|
||||||
|
filename = self.filename.get()
|
||||||
|
if not filename:
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.canvas.set_wallpaper(filename)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error("invalid background: %s", filename)
|
||||||
|
|
||||||
|
self.destroy()
|
251
coretk/coretk/dialogs/colorpicker.py
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
"""
|
||||||
|
custom color picker
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
|
||||||
|
class ColorPicker(Dialog):
|
||||||
|
def __init__(self, master, app, initcolor="#000000"):
|
||||||
|
super().__init__(master, app, "color picker", modal=True)
|
||||||
|
self.red_entry = None
|
||||||
|
self.blue_entry = None
|
||||||
|
self.green_entry = None
|
||||||
|
self.hex_entry = None
|
||||||
|
self.red_label = None
|
||||||
|
self.green_label = None
|
||||||
|
self.blue_label = None
|
||||||
|
self.display = None
|
||||||
|
self.color = initcolor
|
||||||
|
red, green, blue = self.get_rgb(initcolor)
|
||||||
|
self.red = tk.IntVar(value=red)
|
||||||
|
self.blue = tk.IntVar(value=blue)
|
||||||
|
self.green = tk.IntVar(value=green)
|
||||||
|
self.hex = tk.StringVar(value=initcolor)
|
||||||
|
self.red_scale = tk.IntVar(value=red)
|
||||||
|
self.green_scale = tk.IntVar(value=green)
|
||||||
|
self.blue_scale = tk.IntVar(value=blue)
|
||||||
|
self.draw()
|
||||||
|
self.set_bindings()
|
||||||
|
|
||||||
|
def askcolor(self):
|
||||||
|
self.show()
|
||||||
|
return self.color
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
# rgb frames
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=6)
|
||||||
|
frame.columnconfigure(3, weight=2)
|
||||||
|
label = ttk.Label(frame, text="R: ")
|
||||||
|
label.grid(row=0, column=0)
|
||||||
|
self.red_entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
width=4,
|
||||||
|
textvariable=self.red,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.rgb, "%P"),
|
||||||
|
)
|
||||||
|
self.red_entry.grid(row=0, column=1, sticky="nsew")
|
||||||
|
scale = ttk.Scale(
|
||||||
|
frame,
|
||||||
|
from_=0,
|
||||||
|
to=255,
|
||||||
|
value=0,
|
||||||
|
# length=200,
|
||||||
|
orient=tk.HORIZONTAL,
|
||||||
|
variable=self.red_scale,
|
||||||
|
command=lambda x: self.scale_callback(self.red_scale, self.red),
|
||||||
|
)
|
||||||
|
scale.grid(row=0, column=2, sticky="nsew")
|
||||||
|
self.red_label = ttk.Label(
|
||||||
|
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
|
||||||
|
)
|
||||||
|
self.red_label.grid(row=0, column=3, sticky="nsew")
|
||||||
|
frame.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=6)
|
||||||
|
frame.columnconfigure(3, weight=2)
|
||||||
|
label = ttk.Label(frame, text="G: ")
|
||||||
|
label.grid(row=0, column=0)
|
||||||
|
self.green_entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
width=4,
|
||||||
|
textvariable=self.green,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.rgb, "%P"),
|
||||||
|
)
|
||||||
|
self.green_entry.grid(row=0, column=1, sticky="nsew")
|
||||||
|
scale = ttk.Scale(
|
||||||
|
frame,
|
||||||
|
from_=0,
|
||||||
|
to=255,
|
||||||
|
value=0,
|
||||||
|
# length=200,
|
||||||
|
orient=tk.HORIZONTAL,
|
||||||
|
variable=self.green_scale,
|
||||||
|
command=lambda x: self.scale_callback(self.green_scale, self.green),
|
||||||
|
)
|
||||||
|
scale.grid(row=0, column=2, sticky="nsew")
|
||||||
|
self.green_label = ttk.Label(
|
||||||
|
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
|
||||||
|
)
|
||||||
|
self.green_label.grid(row=0, column=3, sticky="nsew")
|
||||||
|
frame.grid(row=1, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=6)
|
||||||
|
frame.columnconfigure(3, weight=2)
|
||||||
|
label = ttk.Label(frame, text="B: ")
|
||||||
|
label.grid(row=0, column=0)
|
||||||
|
self.blue_entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
width=4,
|
||||||
|
textvariable=self.blue,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.rgb, "%P"),
|
||||||
|
)
|
||||||
|
self.blue_entry.grid(row=0, column=1, sticky="nsew")
|
||||||
|
scale = ttk.Scale(
|
||||||
|
frame,
|
||||||
|
from_=0,
|
||||||
|
to=255,
|
||||||
|
value=0,
|
||||||
|
# length=200,
|
||||||
|
orient=tk.HORIZONTAL,
|
||||||
|
variable=self.blue_scale,
|
||||||
|
command=lambda x: self.scale_callback(self.blue_scale, self.blue),
|
||||||
|
)
|
||||||
|
scale.grid(row=0, column=2, sticky="nsew")
|
||||||
|
self.blue_label = ttk.Label(
|
||||||
|
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
|
||||||
|
)
|
||||||
|
self.blue_label.grid(row=0, column=3, sticky="nsew")
|
||||||
|
frame.grid(row=2, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
# hex code and color display
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Selection: ")
|
||||||
|
label.grid(row=0, column=0, sticky="nsew")
|
||||||
|
self.hex_entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.hex,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.hex, "%P"),
|
||||||
|
)
|
||||||
|
self.hex_entry.grid(row=1, column=0, sticky="nsew")
|
||||||
|
self.display = tk.Frame(frame, background=self.color, width=100, height=100)
|
||||||
|
self.display.grid(row=2, column=0)
|
||||||
|
frame.grid(row=3, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
# button frame
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
button = ttk.Button(frame, text="OK", command=self.button_ok)
|
||||||
|
button.grid(row=0, column=0, sticky="nsew")
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="nsew")
|
||||||
|
frame.grid(row=4, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
def set_bindings(self):
|
||||||
|
self.red_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
|
||||||
|
self.green_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
|
||||||
|
self.blue_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
|
||||||
|
self.hex_entry.bind("<FocusIn>", lambda x: self.current_focus("hex"))
|
||||||
|
self.red.trace_add("write", self.update_color)
|
||||||
|
self.green.trace_add("write", self.update_color)
|
||||||
|
self.blue.trace_add("write", self.update_color)
|
||||||
|
self.hex.trace_add("write", self.update_color)
|
||||||
|
|
||||||
|
def button_ok(self):
|
||||||
|
logging.debug("not implemented")
|
||||||
|
self.color = self.hex.get()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def get_hex(self):
|
||||||
|
"""
|
||||||
|
convert current RGB values into hex color
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:return: hex color
|
||||||
|
"""
|
||||||
|
red = self.red_entry.get()
|
||||||
|
blue = self.blue_entry.get()
|
||||||
|
green = self.green_entry.get()
|
||||||
|
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
|
|
||||||
|
def current_focus(self, focus):
|
||||||
|
self.focus = focus
|
||||||
|
|
||||||
|
def update_color(self, arg1=None, arg2=None, arg3=None):
|
||||||
|
if self.focus == "rgb":
|
||||||
|
red = self.red_entry.get()
|
||||||
|
blue = self.blue_entry.get()
|
||||||
|
green = self.green_entry.get()
|
||||||
|
self.set_scale(red, green, blue)
|
||||||
|
if red and blue and green:
|
||||||
|
hex_code = "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
|
self.hex.set(hex_code)
|
||||||
|
self.display.config(background=hex_code)
|
||||||
|
self.set_label(red, green, blue)
|
||||||
|
elif self.focus == "hex":
|
||||||
|
hex_code = self.hex.get()
|
||||||
|
if len(hex_code) == 4 or len(hex_code) == 7:
|
||||||
|
red, green, blue = self.get_rgb(hex_code)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
self.set_entry(red, green, blue)
|
||||||
|
self.set_scale(red, green, blue)
|
||||||
|
self.display.config(background=hex_code)
|
||||||
|
self.set_label(red, green, blue)
|
||||||
|
|
||||||
|
def scale_callback(self, var, color_var):
|
||||||
|
color_var.set(var.get())
|
||||||
|
self.focus = "rgb"
|
||||||
|
self.update_color()
|
||||||
|
|
||||||
|
def set_scale(self, red, green, blue):
|
||||||
|
self.red_scale.set(red)
|
||||||
|
self.green_scale.set(green)
|
||||||
|
self.blue_scale.set(blue)
|
||||||
|
|
||||||
|
def set_entry(self, red, green, blue):
|
||||||
|
self.red.set(red)
|
||||||
|
self.green.set(green)
|
||||||
|
self.blue.set(blue)
|
||||||
|
|
||||||
|
def set_label(self, red, green, blue):
|
||||||
|
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
||||||
|
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
||||||
|
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
||||||
|
|
||||||
|
def get_rgb(self, hex_code):
|
||||||
|
"""
|
||||||
|
convert a valid hex code to RGB values
|
||||||
|
|
||||||
|
:param string hex_code: color in hex
|
||||||
|
:rtype: tuple(int, int, int)
|
||||||
|
:return: the RGB values
|
||||||
|
"""
|
||||||
|
if len(hex_code) == 4:
|
||||||
|
red = hex_code[1]
|
||||||
|
green = hex_code[2]
|
||||||
|
blue = hex_code[3]
|
||||||
|
else:
|
||||||
|
red = hex_code[1:3]
|
||||||
|
green = hex_code[3:5]
|
||||||
|
blue = hex_code[5:]
|
||||||
|
return int(red, 16), int(green, 16), int(blue, 16)
|
261
coretk/coretk/dialogs/customnodes.py
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from pathlib import Path
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk import nodeutils
|
||||||
|
from coretk.appconfig import ICONS_PATH
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.images import Images
|
||||||
|
from coretk.nodeutils import NodeDraw
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
from coretk.widgets import CheckboxList, ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesSelectDialog(Dialog):
|
||||||
|
def __init__(self, master, app, current_services):
|
||||||
|
super().__init__(master, app, "Node Services", modal=True)
|
||||||
|
self.groups = None
|
||||||
|
self.services = None
|
||||||
|
self.current = None
|
||||||
|
self.current_services = set(current_services)
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
frame = ttk.LabelFrame(self.top)
|
||||||
|
frame.grid(stick="nsew", pady=PADY)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(row=0, column=0, sticky="nsew")
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
self.groups = ListboxScroll(label_frame)
|
||||||
|
self.groups.grid(sticky="nsew")
|
||||||
|
for group in sorted(self.app.core.services):
|
||||||
|
self.groups.listbox.insert(tk.END, group)
|
||||||
|
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
||||||
|
self.groups.listbox.selection_set(0)
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Services")
|
||||||
|
label_frame.grid(row=0, column=1, sticky="nsew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
self.services = CheckboxList(
|
||||||
|
label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
self.services.grid(sticky="nsew")
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(row=0, column=2, sticky="nsew")
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
self.current = ListboxScroll(label_frame)
|
||||||
|
self.current.grid(sticky="nsew")
|
||||||
|
for service in sorted(self.current_services):
|
||||||
|
self.current.listbox.insert(tk.END, service)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(stick="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.destroy)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
# trigger group change
|
||||||
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
|
def handle_group_change(self, event):
|
||||||
|
selection = self.groups.listbox.curselection()
|
||||||
|
if selection:
|
||||||
|
index = selection[0]
|
||||||
|
group = self.groups.listbox.get(index)
|
||||||
|
self.services.clear()
|
||||||
|
for name in sorted(self.app.core.services[group]):
|
||||||
|
checked = name in self.current_services
|
||||||
|
self.services.add(name, checked)
|
||||||
|
|
||||||
|
def service_clicked(self, name, var):
|
||||||
|
if var.get() and name not in self.current_services:
|
||||||
|
self.current_services.add(name)
|
||||||
|
elif not var.get() and name in self.current_services:
|
||||||
|
self.current_services.remove(name)
|
||||||
|
self.current.listbox.delete(0, tk.END)
|
||||||
|
for name in sorted(self.current_services):
|
||||||
|
self.current.listbox.insert(tk.END, name)
|
||||||
|
|
||||||
|
def click_cancel(self):
|
||||||
|
self.current_services = None
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomNodesDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Custom Nodes", modal=True)
|
||||||
|
self.edit_button = None
|
||||||
|
self.delete_button = None
|
||||||
|
self.nodes_list = None
|
||||||
|
self.name = tk.StringVar()
|
||||||
|
self.image_button = None
|
||||||
|
self.image = None
|
||||||
|
self.image_file = None
|
||||||
|
self.services = set()
|
||||||
|
self.selected = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.draw_node_config()
|
||||||
|
self.draw_node_buttons()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_node_config(self):
|
||||||
|
frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD)
|
||||||
|
frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.nodes_list = ListboxScroll(frame)
|
||||||
|
self.nodes_list.grid(row=0, column=0, sticky="nsew", padx=PADX)
|
||||||
|
self.nodes_list.listbox.bind("<<ListboxSelect>>", self.handle_node_select)
|
||||||
|
for name in sorted(self.app.core.custom_nodes):
|
||||||
|
self.nodes_list.listbox.insert(tk.END, name)
|
||||||
|
|
||||||
|
frame = ttk.Frame(frame)
|
||||||
|
frame.grid(row=0, column=2, sticky="nsew")
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.name)
|
||||||
|
entry.grid(sticky="ew")
|
||||||
|
self.image_button = ttk.Button(
|
||||||
|
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
|
||||||
|
)
|
||||||
|
self.image_button.grid(sticky="ew")
|
||||||
|
button = ttk.Button(frame, text="Services", command=self.click_services)
|
||||||
|
button.grid(sticky="ew")
|
||||||
|
|
||||||
|
def draw_node_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Create", command=self.click_create)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.edit_button = ttk.Button(
|
||||||
|
frame, text="Edit", state=tk.DISABLED, command=self.click_edit
|
||||||
|
)
|
||||||
|
self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.delete_button = ttk.Button(
|
||||||
|
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
|
||||||
|
)
|
||||||
|
self.delete_button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.click_save)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def reset_values(self):
|
||||||
|
self.name.set("")
|
||||||
|
self.image = None
|
||||||
|
self.image_file = None
|
||||||
|
self.services = set()
|
||||||
|
self.image_button.config(image="")
|
||||||
|
|
||||||
|
def click_icon(self):
|
||||||
|
file_path = image_chooser(self, ICONS_PATH)
|
||||||
|
if file_path:
|
||||||
|
image = Images.create(file_path, nodeutils.ICON_SIZE)
|
||||||
|
self.image = image
|
||||||
|
self.image_file = file_path
|
||||||
|
self.image_button.config(image=self.image)
|
||||||
|
|
||||||
|
def click_services(self):
|
||||||
|
dialog = ServicesSelectDialog(self, self.app, self.services)
|
||||||
|
dialog.show()
|
||||||
|
if dialog.current_services is not None:
|
||||||
|
self.services.clear()
|
||||||
|
self.services.update(dialog.current_services)
|
||||||
|
|
||||||
|
def click_save(self):
|
||||||
|
self.app.guiconfig["nodes"].clear()
|
||||||
|
for name in sorted(self.app.core.custom_nodes):
|
||||||
|
node_draw = self.app.core.custom_nodes[name]
|
||||||
|
self.app.guiconfig["nodes"].append(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"image": node_draw.image_file,
|
||||||
|
"services": list(node_draw.services),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
logging.info("saving custom nodes: %s", self.app.guiconfig["nodes"])
|
||||||
|
self.app.save_config()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_create(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if name not in self.app.core.custom_nodes:
|
||||||
|
image_file = Path(self.image_file).stem
|
||||||
|
node_draw = NodeDraw.from_custom(name, image_file, set(self.services))
|
||||||
|
self.app.core.custom_nodes[name] = node_draw
|
||||||
|
self.nodes_list.listbox.insert(tk.END, name)
|
||||||
|
self.reset_values()
|
||||||
|
|
||||||
|
def click_edit(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if self.selected:
|
||||||
|
previous_name = self.selected
|
||||||
|
self.selected = name
|
||||||
|
node_draw = self.app.core.custom_nodes.pop(previous_name)
|
||||||
|
node_draw.model = name
|
||||||
|
node_draw.image_file = Path(self.image_file).stem
|
||||||
|
node_draw.image = self.image
|
||||||
|
node_draw.services = self.services
|
||||||
|
self.app.core.custom_nodes[name] = node_draw
|
||||||
|
self.nodes_list.listbox.delete(self.selected_index)
|
||||||
|
self.nodes_list.listbox.insert(self.selected_index, name)
|
||||||
|
self.nodes_list.listbox.selection_set(self.selected_index)
|
||||||
|
|
||||||
|
def click_delete(self):
|
||||||
|
if self.selected and self.selected in self.app.core.custom_nodes:
|
||||||
|
self.nodes_list.listbox.delete(self.selected_index)
|
||||||
|
del self.app.core.custom_nodes[self.selected]
|
||||||
|
self.reset_values()
|
||||||
|
self.nodes_list.listbox.selection_clear(0, tk.END)
|
||||||
|
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
|
def handle_node_select(self, event):
|
||||||
|
selection = self.nodes_list.listbox.curselection()
|
||||||
|
if selection:
|
||||||
|
self.selected_index = selection[0]
|
||||||
|
self.selected = self.nodes_list.listbox.get(self.selected_index)
|
||||||
|
node_draw = self.app.core.custom_nodes[self.selected]
|
||||||
|
self.name.set(node_draw.model)
|
||||||
|
self.services = node_draw.services
|
||||||
|
self.image = node_draw.image
|
||||||
|
self.image_file = node_draw.image_file
|
||||||
|
self.image_button.config(image=self.image)
|
||||||
|
self.edit_button.config(state=tk.NORMAL)
|
||||||
|
self.delete_button.config(state=tk.NORMAL)
|
||||||
|
else:
|
||||||
|
self.selected = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.edit_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
37
coretk/coretk/dialogs/dialog.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.themes import DIALOG_PAD
|
||||||
|
|
||||||
|
|
||||||
|
class Dialog(tk.Toplevel):
|
||||||
|
def __init__(self, master, app, title, modal=False):
|
||||||
|
super().__init__(master)
|
||||||
|
self.withdraw()
|
||||||
|
self.app = app
|
||||||
|
self.modal = modal
|
||||||
|
self.title(title)
|
||||||
|
self.protocol("WM_DELETE_WINDOW", self.destroy)
|
||||||
|
image = Images.get(ImageEnum.CORE, 16)
|
||||||
|
self.tk.call("wm", "iconphoto", self._w, image)
|
||||||
|
self.columnconfigure(0, weight=1)
|
||||||
|
self.rowconfigure(0, weight=1)
|
||||||
|
self.top = ttk.Frame(self, padding=DIALOG_PAD)
|
||||||
|
self.top.grid(sticky="nsew")
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.transient(self.master)
|
||||||
|
self.focus_force()
|
||||||
|
self.update()
|
||||||
|
self.deiconify()
|
||||||
|
if self.modal:
|
||||||
|
self.wait_visibility()
|
||||||
|
self.grab_set()
|
||||||
|
self.wait_window()
|
||||||
|
|
||||||
|
def draw_spacer(self, row=None):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(row=row, sticky="nsew")
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(frame.grid_info()["row"], weight=1)
|
235
coretk/coretk/dialogs/emaneconfig.py
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
"""
|
||||||
|
emane configuration
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
import webbrowser
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import ConfigFrame
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalEmaneDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "EMANE Configuration", modal=True)
|
||||||
|
self.config_frame = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, self.app.core.emane_config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
self.config_frame.parse_config()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class EmaneModelDialog(Dialog):
|
||||||
|
def __init__(self, master, app, node, model, interface=None):
|
||||||
|
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
|
||||||
|
self.node = node
|
||||||
|
self.model = f"emane_{model}"
|
||||||
|
self.interface = interface
|
||||||
|
self.config_frame = None
|
||||||
|
try:
|
||||||
|
self.config = self.app.core.get_emane_model_config(
|
||||||
|
self.node.id, self.model, self.interface
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, self.config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
self.config_frame.parse_config()
|
||||||
|
self.app.core.set_emane_model_config(
|
||||||
|
self.node.id, self.model, self.config, self.interface
|
||||||
|
)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class EmaneConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node):
|
||||||
|
super().__init__(
|
||||||
|
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
|
||||||
|
)
|
||||||
|
self.app = app
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node = canvas_node.core_node
|
||||||
|
self.radiovar = tk.IntVar()
|
||||||
|
self.radiovar.set(1)
|
||||||
|
self.emane_models = [x.split("_")[1] for x in self.app.core.emane_models]
|
||||||
|
self.emane_model = tk.StringVar(value=self.node.emane.split("_")[1])
|
||||||
|
self.emane_model_button = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.draw_emane_configuration()
|
||||||
|
self.draw_emane_models()
|
||||||
|
self.draw_emane_buttons()
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_apply_and_cancel()
|
||||||
|
|
||||||
|
def draw_emane_configuration(self):
|
||||||
|
"""
|
||||||
|
draw the main frame for emane configuration
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
label = ttk.Label(
|
||||||
|
self.top,
|
||||||
|
text="The EMANE emulation system provides more complex wireless radio emulation "
|
||||||
|
"\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details",
|
||||||
|
justify=tk.CENTER,
|
||||||
|
)
|
||||||
|
label.grid(pady=PADY)
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.EDITNODE, 16)
|
||||||
|
button = ttk.Button(
|
||||||
|
self.top,
|
||||||
|
image=image,
|
||||||
|
text="EMANE Wiki",
|
||||||
|
compound=tk.RIGHT,
|
||||||
|
command=lambda: webbrowser.open_new(
|
||||||
|
"https://github.com/adjacentlink/emane/wiki"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
button.image = image
|
||||||
|
button.grid(sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
def draw_emane_models(self):
|
||||||
|
"""
|
||||||
|
create a combobox that has all the known emane models
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Model")
|
||||||
|
label.grid(row=0, column=0, sticky="w")
|
||||||
|
|
||||||
|
# create combo box and its binding
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame,
|
||||||
|
textvariable=self.emane_model,
|
||||||
|
values=self.emane_models,
|
||||||
|
state="readonly",
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=1, sticky="ew")
|
||||||
|
combobox.bind("<<ComboboxSelected>>", self.emane_model_change)
|
||||||
|
|
||||||
|
def draw_emane_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.EDITNODE, 16)
|
||||||
|
self.emane_model_button = ttk.Button(
|
||||||
|
frame,
|
||||||
|
text=f"{self.emane_model.get()} options",
|
||||||
|
image=image,
|
||||||
|
compound=tk.RIGHT,
|
||||||
|
command=self.click_model_config,
|
||||||
|
)
|
||||||
|
self.emane_model_button.image = image
|
||||||
|
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.EDITNODE, 16)
|
||||||
|
button = ttk.Button(
|
||||||
|
frame,
|
||||||
|
text="EMANE options",
|
||||||
|
image=image,
|
||||||
|
compound=tk.RIGHT,
|
||||||
|
command=self.click_emane_config,
|
||||||
|
)
|
||||||
|
button.image = image
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def draw_apply_and_cancel(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_emane_config(self):
|
||||||
|
dialog = GlobalEmaneDialog(self, self.app)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def click_model_config(self):
|
||||||
|
"""
|
||||||
|
draw emane model configuration
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
model_name = self.emane_model.get()
|
||||||
|
logging.info("configuring emane model: %s", model_name)
|
||||||
|
dialog = EmaneModelDialog(
|
||||||
|
self, self.app, self.canvas_node.core_node, model_name
|
||||||
|
)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def emane_model_change(self, event):
|
||||||
|
"""
|
||||||
|
update emane model options button
|
||||||
|
|
||||||
|
:param event:
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
model_name = self.emane_model.get()
|
||||||
|
self.emane_model_button.config(text=f"{model_name} options")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
self.node.emane = f"emane_{self.emane_model.get()}"
|
||||||
|
self.destroy()
|
152
coretk/coretk/dialogs/hooks.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
|
||||||
|
class HookDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Hook", modal=True)
|
||||||
|
self.name = tk.StringVar()
|
||||||
|
self.codetext = None
|
||||||
|
self.hook = core_pb2.Hook()
|
||||||
|
self.state = tk.StringVar()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# name and states
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=2)
|
||||||
|
frame.columnconfigure(1, weight=7)
|
||||||
|
frame.columnconfigure(2, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Name")
|
||||||
|
label.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.name)
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE")
|
||||||
|
initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME)
|
||||||
|
self.state.set(initial_state)
|
||||||
|
self.name.set(f"{initial_state.lower()}_hook.sh")
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.state, values=values, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=2, sticky="ew")
|
||||||
|
combobox.bind("<<ComboboxSelected>>", self.state_change)
|
||||||
|
|
||||||
|
# data
|
||||||
|
self.codetext = CodeText(self.top)
|
||||||
|
self.codetext.text.insert(
|
||||||
|
1.0,
|
||||||
|
(
|
||||||
|
"#!/bin/sh\n"
|
||||||
|
"# session hook script; write commands here to execute on the host at the\n"
|
||||||
|
"# specified state\n"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.codetext.grid(sticky="nsew", pady=PADY)
|
||||||
|
|
||||||
|
# button row
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Save", command=lambda: self.save())
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def state_change(self, event):
|
||||||
|
state_name = self.state.get()
|
||||||
|
self.name.set(f"{state_name.lower()}_hook.sh")
|
||||||
|
|
||||||
|
def set(self, hook):
|
||||||
|
self.hook = hook
|
||||||
|
self.name.set(hook.file)
|
||||||
|
self.codetext.text.delete(1.0, tk.END)
|
||||||
|
self.codetext.text.insert(tk.END, hook.data)
|
||||||
|
state_name = core_pb2.SessionState.Enum.Name(hook.state)
|
||||||
|
self.state.set(state_name)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
data = self.codetext.text.get("1.0", tk.END).strip()
|
||||||
|
state_value = core_pb2.SessionState.Enum.Value(self.state.get())
|
||||||
|
self.hook.file = self.name.get()
|
||||||
|
self.hook.data = data
|
||||||
|
self.hook.state = state_value
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class HooksDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Hooks", modal=True)
|
||||||
|
self.listbox = None
|
||||||
|
self.edit_button = None
|
||||||
|
self.delete_button = None
|
||||||
|
self.selected = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
listbox_scroll = ListboxScroll(self.top)
|
||||||
|
listbox_scroll.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.listbox = listbox_scroll.listbox
|
||||||
|
self.listbox.bind("<<ListboxSelect>>", self.select)
|
||||||
|
for hook_file in self.app.core.hooks:
|
||||||
|
self.listbox.insert(tk.END, hook_file)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(4):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Create", command=self.click_create)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
self.edit_button = ttk.Button(
|
||||||
|
frame, text="Edit", state=tk.DISABLED, command=self.click_edit
|
||||||
|
)
|
||||||
|
self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
self.delete_button = ttk.Button(
|
||||||
|
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
|
||||||
|
)
|
||||||
|
self.delete_button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
||||||
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
|
def click_create(self):
|
||||||
|
dialog = HookDialog(self, self.app)
|
||||||
|
dialog.show()
|
||||||
|
hook = dialog.hook
|
||||||
|
if hook:
|
||||||
|
self.app.core.hooks[hook.file] = hook
|
||||||
|
self.listbox.insert(tk.END, hook.file)
|
||||||
|
|
||||||
|
def click_edit(self):
|
||||||
|
hook = self.app.core.hooks[self.selected]
|
||||||
|
dialog = HookDialog(self, self.app)
|
||||||
|
dialog.set(hook)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def click_delete(self):
|
||||||
|
del self.app.core.hooks[self.selected]
|
||||||
|
self.listbox.delete(tk.ANCHOR)
|
||||||
|
self.edit_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def select(self, event):
|
||||||
|
if self.listbox.curselection():
|
||||||
|
index = self.listbox.curselection()[0]
|
||||||
|
self.selected = self.listbox.get(index)
|
||||||
|
self.edit_button.config(state=tk.NORMAL)
|
||||||
|
self.delete_button.config(state=tk.NORMAL)
|
||||||
|
else:
|
||||||
|
self.selected = None
|
||||||
|
self.edit_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
362
coretk/coretk/dialogs/linkconfig.py
Normal file
|
@ -0,0 +1,362 @@
|
||||||
|
"""
|
||||||
|
link configuration
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from coretk.dialogs.colorpicker import ColorPicker
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
|
||||||
|
|
||||||
|
def get_int(var):
|
||||||
|
value = var.get()
|
||||||
|
if value != "":
|
||||||
|
return int(value)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_float(var):
|
||||||
|
value = var.get()
|
||||||
|
if value != "":
|
||||||
|
return float(value)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class LinkConfiguration(Dialog):
|
||||||
|
def __init__(self, master, app, edge):
|
||||||
|
super().__init__(master, app, "Link Configuration", modal=True)
|
||||||
|
self.app = app
|
||||||
|
self.edge = edge
|
||||||
|
self.is_symmetric = edge.link.options.unidirectional is False
|
||||||
|
if self.is_symmetric:
|
||||||
|
self.symmetry_var = tk.StringVar(value=">>")
|
||||||
|
else:
|
||||||
|
self.symmetry_var = tk.StringVar(value="<<")
|
||||||
|
|
||||||
|
self.bandwidth = tk.StringVar()
|
||||||
|
self.delay = tk.StringVar()
|
||||||
|
self.jitter = tk.StringVar()
|
||||||
|
self.loss = tk.StringVar()
|
||||||
|
self.duplicate = tk.StringVar()
|
||||||
|
|
||||||
|
self.down_bandwidth = tk.StringVar()
|
||||||
|
self.down_delay = tk.StringVar()
|
||||||
|
self.down_jitter = tk.StringVar()
|
||||||
|
self.down_loss = tk.StringVar()
|
||||||
|
self.down_duplicate = tk.StringVar()
|
||||||
|
|
||||||
|
self.color = tk.StringVar(value="#000000")
|
||||||
|
self.color_button = None
|
||||||
|
self.width = tk.DoubleVar()
|
||||||
|
|
||||||
|
self.load_link_config()
|
||||||
|
self.symmetric_frame = None
|
||||||
|
self.asymmetric_frame = None
|
||||||
|
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
source_name = self.app.canvas.nodes[self.edge.src].core_node.name
|
||||||
|
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
|
||||||
|
label = ttk.Label(
|
||||||
|
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
|
||||||
|
)
|
||||||
|
label.grid(row=0, column=0, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.grid(row=1, column=0, sticky="ew", pady=PADY)
|
||||||
|
button = ttk.Button(frame, text="Unlimited")
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
if self.is_symmetric:
|
||||||
|
button = ttk.Button(
|
||||||
|
frame, textvariable=self.symmetry_var, command=self.change_symmetry
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
button = ttk.Button(
|
||||||
|
frame, textvariable=self.symmetry_var, command=self.change_symmetry
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
if self.is_symmetric:
|
||||||
|
self.symmetric_frame = self.get_frame()
|
||||||
|
self.symmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
|
||||||
|
else:
|
||||||
|
self.asymmetric_frame = self.get_frame()
|
||||||
|
self.asymmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
self.draw_spacer(row=3)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.grid(row=4, column=0, sticky="ew")
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def get_frame(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
if self.is_symmetric:
|
||||||
|
label_name = "Symmetric Link Effects"
|
||||||
|
else:
|
||||||
|
label_name = "Asymmetric Effects: Downstream / Upstream "
|
||||||
|
row = 0
|
||||||
|
label = ttk.Label(frame, text=label_name, anchor=tk.CENTER)
|
||||||
|
label.grid(row=row, column=0, columnspan=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Bandwidth (bps)")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.bandwidth,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
if not self.is_symmetric:
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.down_bandwidth,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Delay (us)")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.delay,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
if not self.is_symmetric:
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.down_delay,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Jitter (us)")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.jitter,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
if not self.is_symmetric:
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.down_jitter,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Loss (%)")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.loss,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
if not self.is_symmetric:
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.down_loss,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Duplicate (%)")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.duplicate,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
if not self.is_symmetric:
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.down_duplicate,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Color")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
self.color_button = tk.Button(
|
||||||
|
frame,
|
||||||
|
textvariable=self.color,
|
||||||
|
background=self.color.get(),
|
||||||
|
bd=0,
|
||||||
|
relief=tk.FLAT,
|
||||||
|
highlightthickness=0,
|
||||||
|
command=self.click_color,
|
||||||
|
)
|
||||||
|
self.color_button.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Width")
|
||||||
|
label.grid(row=row, column=0, sticky="ew")
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.width,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_float, "%P"),
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def click_color(self):
|
||||||
|
dialog = ColorPicker(self, self.app, self.color.get())
|
||||||
|
color = dialog.askcolor()
|
||||||
|
self.color.set(color)
|
||||||
|
self.color_button.config(background=color)
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
logging.debug("click apply")
|
||||||
|
self.app.canvas.itemconfigure(self.edge.id, width=self.width.get())
|
||||||
|
self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get())
|
||||||
|
link = self.edge.link
|
||||||
|
bandwidth = get_int(self.bandwidth)
|
||||||
|
jitter = get_int(self.jitter)
|
||||||
|
delay = get_int(self.delay)
|
||||||
|
duplicate = get_int(self.duplicate)
|
||||||
|
loss = get_float(self.loss)
|
||||||
|
options = core_pb2.LinkOptions(
|
||||||
|
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, per=loss
|
||||||
|
)
|
||||||
|
link.options.CopyFrom(options)
|
||||||
|
|
||||||
|
interface_one = None
|
||||||
|
if link.HasField("interface_one"):
|
||||||
|
interface_one = link.interface_one.id
|
||||||
|
interface_two = None
|
||||||
|
if link.HasField("interface_two"):
|
||||||
|
interface_two = link.interface_two.id
|
||||||
|
|
||||||
|
if not self.is_symmetric:
|
||||||
|
link.options.unidirectional = True
|
||||||
|
asym_interface_one = None
|
||||||
|
if interface_one:
|
||||||
|
asym_interface_one = core_pb2.Interface(id=interface_one)
|
||||||
|
asym_interface_two = None
|
||||||
|
if interface_two:
|
||||||
|
asym_interface_two = core_pb2.Interface(id=interface_two)
|
||||||
|
down_bandwidth = get_int(self.down_bandwidth)
|
||||||
|
down_jitter = get_int(self.down_jitter)
|
||||||
|
down_delay = get_int(self.down_delay)
|
||||||
|
down_duplicate = get_int(self.down_duplicate)
|
||||||
|
down_loss = get_float(self.down_loss)
|
||||||
|
options = core_pb2.LinkOptions(
|
||||||
|
bandwidth=down_bandwidth,
|
||||||
|
jitter=down_jitter,
|
||||||
|
delay=down_delay,
|
||||||
|
dup=down_duplicate,
|
||||||
|
per=down_loss,
|
||||||
|
unidirectional=True,
|
||||||
|
)
|
||||||
|
self.edge.asymmetric_link = core_pb2.Link(
|
||||||
|
node_one_id=link.node_two_id,
|
||||||
|
node_two_id=link.node_one_id,
|
||||||
|
interface_one=asym_interface_one,
|
||||||
|
interface_two=asym_interface_two,
|
||||||
|
options=options,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
link.options.unidirectional = False
|
||||||
|
self.edge.asymmetric_link = None
|
||||||
|
|
||||||
|
if self.app.core.is_runtime() and link.HasField("options"):
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
self.app.core.client.edit_link(
|
||||||
|
session_id,
|
||||||
|
link.node_one_id,
|
||||||
|
link.node_two_id,
|
||||||
|
link.options,
|
||||||
|
interface_one,
|
||||||
|
interface_two,
|
||||||
|
)
|
||||||
|
if self.edge.asymmetric_link:
|
||||||
|
self.app.core.client.edit_link(
|
||||||
|
session_id,
|
||||||
|
link.node_two_id,
|
||||||
|
link.node_one_id,
|
||||||
|
self.edge.asymmetric_link.options,
|
||||||
|
interface_one,
|
||||||
|
interface_two,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def change_symmetry(self):
|
||||||
|
logging.debug("change symmetry")
|
||||||
|
|
||||||
|
if self.is_symmetric:
|
||||||
|
self.is_symmetric = False
|
||||||
|
self.symmetry_var.set("<<")
|
||||||
|
if not self.asymmetric_frame:
|
||||||
|
self.asymmetric_frame = self.get_frame()
|
||||||
|
self.symmetric_frame.grid_forget()
|
||||||
|
self.asymmetric_frame.grid(row=2, column=0)
|
||||||
|
else:
|
||||||
|
self.is_symmetric = True
|
||||||
|
self.symmetry_var.set(">>")
|
||||||
|
if not self.symmetric_frame:
|
||||||
|
self.symmetric_frame = self.get_frame()
|
||||||
|
self.asymmetric_frame.grid_forget()
|
||||||
|
self.symmetric_frame.grid(row=2, column=0)
|
||||||
|
|
||||||
|
def load_link_config(self):
|
||||||
|
"""
|
||||||
|
populate link config to the table
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
width = self.app.canvas.itemcget(self.edge.id, "width")
|
||||||
|
self.width.set(width)
|
||||||
|
color = self.app.canvas.itemcget(self.edge.id, "fill")
|
||||||
|
self.color.set(color)
|
||||||
|
link = self.edge.link
|
||||||
|
if link.HasField("options"):
|
||||||
|
self.bandwidth.set(str(link.options.bandwidth))
|
||||||
|
self.jitter.set(str(link.options.jitter))
|
||||||
|
self.duplicate.set(str(link.options.dup))
|
||||||
|
self.loss.set(str(link.options.per))
|
||||||
|
self.delay.set(str(link.options.delay))
|
||||||
|
if not self.is_symmetric:
|
||||||
|
asym_link = self.edge.asymmetric_link
|
||||||
|
self.down_bandwidth.set(str(asym_link.options.bandwidth))
|
||||||
|
self.down_jitter.set(str(asym_link.options.jitter))
|
||||||
|
self.down_duplicate.set(str(asym_link.options.dup))
|
||||||
|
self.down_loss.set(str(asym_link.options.per))
|
||||||
|
self.down_delay.set(str(asym_link.options.delay))
|
73
coretk/coretk/dialogs/marker.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
"""
|
||||||
|
marker dialog
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.dialogs.colorpicker import ColorPicker
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
MARKER_THICKNESS = [3, 5, 8, 10]
|
||||||
|
|
||||||
|
|
||||||
|
class Marker(Dialog):
|
||||||
|
def __init__(self, master, app, initcolor="#000000"):
|
||||||
|
super().__init__(master, app, "marker tool", modal=False)
|
||||||
|
self.app = app
|
||||||
|
self.color = initcolor
|
||||||
|
self.radius = MARKER_THICKNESS[0]
|
||||||
|
self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0])
|
||||||
|
self.draw()
|
||||||
|
self.top.bind("<Destroy>", self.close_marker)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
button = ttk.Button(self.top, text="clear", command=self.clear_marker)
|
||||||
|
button.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=4)
|
||||||
|
frame.grid(row=1, column=0, sticky="nsew")
|
||||||
|
label = ttk.Label(frame, text="Thickness: ")
|
||||||
|
label.grid(row=0, column=0, sticky="nsew")
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame,
|
||||||
|
textvariable=self.marker_thickness,
|
||||||
|
values=MARKER_THICKNESS,
|
||||||
|
state="readonly",
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=1, sticky="nsew")
|
||||||
|
combobox.bind("<<ComboboxSelected>>", self.change_thickness)
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=4)
|
||||||
|
frame.grid(row=2, column=0, sticky="nsew")
|
||||||
|
label = ttk.Label(frame, text="Color: ")
|
||||||
|
label.grid(row=0, column=0, sticky="nsew")
|
||||||
|
label = ttk.Label(frame, background=self.color)
|
||||||
|
label.grid(row=0, column=1, sticky="nsew")
|
||||||
|
label.bind("<Button-1>", self.change_color)
|
||||||
|
|
||||||
|
def clear_marker(self):
|
||||||
|
canvas = self.app.canvas
|
||||||
|
for i in canvas.find_withtag("marker"):
|
||||||
|
canvas.delete(i)
|
||||||
|
|
||||||
|
def change_color(self, event):
|
||||||
|
color_picker = ColorPicker(self, self.app, self.color)
|
||||||
|
color = color_picker.askcolor()
|
||||||
|
event.widget.configure(background=color)
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
def change_thickness(self, event):
|
||||||
|
self.radius = self.marker_thickness.get()
|
||||||
|
|
||||||
|
def close_marker(self, event):
|
||||||
|
logging.debug("destroy marker dialog")
|
||||||
|
self.app.toolbar.marker_tool = None
|
||||||
|
|
||||||
|
def position(self):
|
||||||
|
print(self.winfo_width(), self.winfo_height())
|
||||||
|
self.geometry("+{}+{}".format(self.app.master.winfo_x, self.app.master.winfo_y))
|
55
coretk/coretk/dialogs/mobilityconfig.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""
|
||||||
|
mobility configuration
|
||||||
|
"""
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import ConfigFrame
|
||||||
|
|
||||||
|
|
||||||
|
class MobilityConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node):
|
||||||
|
super().__init__(
|
||||||
|
master,
|
||||||
|
app,
|
||||||
|
f"{canvas_node.core_node.name} Mobility Configuration",
|
||||||
|
modal=True,
|
||||||
|
)
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node = canvas_node.core_node
|
||||||
|
self.config_frame = None
|
||||||
|
try:
|
||||||
|
self.config = self.app.core.get_mobility_config(self.node.id)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, self.config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.draw_apply_buttons()
|
||||||
|
|
||||||
|
def draw_apply_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
self.config_frame.parse_config()
|
||||||
|
self.app.core.mobility_configs[self.node.id] = self.config
|
||||||
|
self.destroy()
|
163
coretk/coretk/dialogs/mobilityplayer.py
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc.core_pb2 import MobilityAction
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
|
||||||
|
ICON_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
|
class MobilityPlayer:
|
||||||
|
def __init__(self, master, app, canvas_node, config):
|
||||||
|
self.master = master
|
||||||
|
self.app = app
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.config = config
|
||||||
|
self.dialog = None
|
||||||
|
self.state = None
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
if self.dialog:
|
||||||
|
self.dialog.destroy()
|
||||||
|
self.dialog = MobilityPlayerDialog(
|
||||||
|
self.master, self.app, self.canvas_node, self.config
|
||||||
|
)
|
||||||
|
self.dialog.protocol("WM_DELETE_WINDOW", self.handle_close)
|
||||||
|
if self.state == MobilityAction.START:
|
||||||
|
self.set_play()
|
||||||
|
elif self.state == MobilityAction.PAUSE:
|
||||||
|
self.set_pause()
|
||||||
|
else:
|
||||||
|
self.set_stop()
|
||||||
|
self.dialog.show()
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.dialog.destroy()
|
||||||
|
self.dialog = None
|
||||||
|
|
||||||
|
def set_play(self):
|
||||||
|
self.state = MobilityAction.START
|
||||||
|
if self.dialog:
|
||||||
|
self.dialog.set_play()
|
||||||
|
|
||||||
|
def set_pause(self):
|
||||||
|
self.state = MobilityAction.PAUSE
|
||||||
|
if self.dialog:
|
||||||
|
self.dialog.set_pause()
|
||||||
|
|
||||||
|
def set_stop(self):
|
||||||
|
self.state = MobilityAction.STOP
|
||||||
|
if self.dialog:
|
||||||
|
self.dialog.set_stop()
|
||||||
|
|
||||||
|
|
||||||
|
class MobilityPlayerDialog(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node, config):
|
||||||
|
super().__init__(
|
||||||
|
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
|
||||||
|
)
|
||||||
|
self.resizable(False, False)
|
||||||
|
self.geometry("")
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node = canvas_node.core_node
|
||||||
|
self.config = config
|
||||||
|
self.play_button = None
|
||||||
|
self.pause_button = None
|
||||||
|
self.stop_button = None
|
||||||
|
self.progressbar = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
file_name = self.config["file"].value
|
||||||
|
label = ttk.Label(self.top, text=file_name)
|
||||||
|
label.grid(sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
self.progressbar = ttk.Progressbar(self.top, mode="indeterminate")
|
||||||
|
self.progressbar.grid(sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.START, width=ICON_SIZE)
|
||||||
|
self.play_button = ttk.Button(frame, image=image, command=self.click_play)
|
||||||
|
self.play_button.image = image
|
||||||
|
self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.PAUSE, width=ICON_SIZE)
|
||||||
|
self.pause_button = ttk.Button(frame, image=image, command=self.click_pause)
|
||||||
|
self.pause_button.image = image
|
||||||
|
self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.STOP, width=ICON_SIZE)
|
||||||
|
self.stop_button = ttk.Button(frame, image=image, command=self.click_stop)
|
||||||
|
self.stop_button.image = image
|
||||||
|
self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
loop = tk.IntVar(value=int(self.config["loop"].value == "1"))
|
||||||
|
checkbutton = ttk.Checkbutton(
|
||||||
|
frame, text="Loop?", variable=loop, state=tk.DISABLED
|
||||||
|
)
|
||||||
|
checkbutton.grid(row=0, column=3, padx=PADX)
|
||||||
|
|
||||||
|
rate = self.config["refresh_ms"].value
|
||||||
|
label = ttk.Label(frame, text=f"rate {rate} ms")
|
||||||
|
label.grid(row=0, column=4)
|
||||||
|
|
||||||
|
def clear_buttons(self):
|
||||||
|
self.play_button.state(["!pressed"])
|
||||||
|
self.pause_button.state(["!pressed"])
|
||||||
|
self.stop_button.state(["!pressed"])
|
||||||
|
|
||||||
|
def set_play(self):
|
||||||
|
self.clear_buttons()
|
||||||
|
self.play_button.state(["pressed"])
|
||||||
|
self.progressbar.start()
|
||||||
|
|
||||||
|
def set_pause(self):
|
||||||
|
self.clear_buttons()
|
||||||
|
self.pause_button.state(["pressed"])
|
||||||
|
self.progressbar.stop()
|
||||||
|
|
||||||
|
def set_stop(self):
|
||||||
|
self.clear_buttons()
|
||||||
|
self.stop_button.state(["pressed"])
|
||||||
|
self.progressbar.stop()
|
||||||
|
|
||||||
|
def click_play(self):
|
||||||
|
self.set_play()
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
try:
|
||||||
|
self.app.core.client.mobility_action(
|
||||||
|
session_id, self.node.id, MobilityAction.START
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def click_pause(self):
|
||||||
|
self.set_pause()
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
try:
|
||||||
|
self.app.core.client.mobility_action(
|
||||||
|
session_id, self.node.id, MobilityAction.PAUSE
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def click_stop(self):
|
||||||
|
self.set_stop()
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
try:
|
||||||
|
self.app.core.client.mobility_action(
|
||||||
|
session_id, self.node.id, MobilityAction.STOP
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
237
coretk/coretk/dialogs/nodeconfig.py
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from functools import partial
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk import nodeutils
|
||||||
|
from coretk.appconfig import ICONS_PATH
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.dialogs.emaneconfig import EmaneModelDialog
|
||||||
|
from coretk.images import Images
|
||||||
|
from coretk.nodeutils import NodeUtils
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
from coretk.widgets import image_chooser
|
||||||
|
|
||||||
|
|
||||||
|
def mac_auto(is_auto, entry):
|
||||||
|
logging.info("mac auto clicked")
|
||||||
|
if is_auto.get():
|
||||||
|
logging.info("disabling mac")
|
||||||
|
entry.var.set("")
|
||||||
|
entry.config(state=tk.DISABLED)
|
||||||
|
else:
|
||||||
|
entry.var.set("00:00:00:00:00:00")
|
||||||
|
entry.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceData:
|
||||||
|
def __init__(self, is_auto, mac, ip4, ip6):
|
||||||
|
self.is_auto = is_auto
|
||||||
|
self.mac = mac
|
||||||
|
self.ip4 = ip4
|
||||||
|
self.ip6 = ip6
|
||||||
|
|
||||||
|
|
||||||
|
class NodeConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node):
|
||||||
|
"""
|
||||||
|
create an instance of node configuration
|
||||||
|
|
||||||
|
:param master: dialog master
|
||||||
|
:param coretk.app.Application: main app
|
||||||
|
:param coretk.graph.CanvasNode canvas_node: canvas node object
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
|
||||||
|
)
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node = canvas_node.core_node
|
||||||
|
self.image = canvas_node.image
|
||||||
|
self.image_file = None
|
||||||
|
self.image_button = None
|
||||||
|
self.name = tk.StringVar(value=self.node.name)
|
||||||
|
self.type = tk.StringVar(value=self.node.model)
|
||||||
|
self.container_image = tk.StringVar(value=self.node.image)
|
||||||
|
server = "localhost"
|
||||||
|
if self.node.server:
|
||||||
|
server = self.node.server
|
||||||
|
self.server = tk.StringVar(value=server)
|
||||||
|
self.interfaces = {}
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
# field frame
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# icon field
|
||||||
|
label = ttk.Label(frame, text="Icon")
|
||||||
|
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
|
||||||
|
self.image_button = ttk.Button(
|
||||||
|
frame,
|
||||||
|
text="Icon",
|
||||||
|
image=self.image,
|
||||||
|
compound=tk.NONE,
|
||||||
|
command=self.click_icon,
|
||||||
|
)
|
||||||
|
self.image_button.grid(row=row, column=1, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# name field
|
||||||
|
label = ttk.Label(frame, text="Name")
|
||||||
|
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.name,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.name, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind(
|
||||||
|
"<FocusOut>", lambda event: self.app.validation.focus_out(event, "noname")
|
||||||
|
)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# node type field
|
||||||
|
if NodeUtils.is_model_node(self.node.type):
|
||||||
|
label = ttk.Label(frame, text="Type")
|
||||||
|
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame,
|
||||||
|
textvariable=self.type,
|
||||||
|
values=list(NodeUtils.NODE_MODELS),
|
||||||
|
state="readonly",
|
||||||
|
)
|
||||||
|
combobox.grid(row=row, column=1, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# container image field
|
||||||
|
if NodeUtils.is_image_node(self.node.type):
|
||||||
|
label = ttk.Label(frame, text="Image")
|
||||||
|
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.container_image)
|
||||||
|
entry.grid(row=row, column=1, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
if NodeUtils.is_container_node(self.node.type):
|
||||||
|
# server
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Server")
|
||||||
|
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
|
||||||
|
servers = ["localhost"]
|
||||||
|
servers.extend(list(sorted(self.app.core.servers.keys())))
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.server, values=servers, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.grid(row=row, column=1, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# interfaces
|
||||||
|
if self.canvas_node.interfaces:
|
||||||
|
self.draw_interfaces()
|
||||||
|
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_interfaces(self):
|
||||||
|
notebook = ttk.Notebook(self.top)
|
||||||
|
notebook.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.top.rowconfigure(notebook.grid_info()["row"], weight=1)
|
||||||
|
|
||||||
|
for interface in self.canvas_node.interfaces:
|
||||||
|
logging.info("interface: %s", interface)
|
||||||
|
tab = ttk.Frame(notebook, padding=FRAME_PAD)
|
||||||
|
tab.grid(sticky="nsew", pady=PADY)
|
||||||
|
tab.columnconfigure(1, weight=1)
|
||||||
|
tab.columnconfigure(2, weight=1)
|
||||||
|
notebook.add(tab, text=interface.name)
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
emane_node = self.canvas_node.has_emane_link(interface.id)
|
||||||
|
if emane_node:
|
||||||
|
emane_model = emane_node.emane.split("_")[1]
|
||||||
|
button = ttk.Button(
|
||||||
|
tab,
|
||||||
|
text=f"Configure EMANE {emane_model}",
|
||||||
|
command=lambda: self.click_emane_config(emane_model, interface.id),
|
||||||
|
)
|
||||||
|
button.grid(row=row, sticky="ew", columnspan=3, pady=PADY)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
label = ttk.Label(tab, text="MAC")
|
||||||
|
label.grid(row=row, column=0, padx=PADX, pady=PADY)
|
||||||
|
is_auto = tk.BooleanVar(value=True)
|
||||||
|
checkbutton = ttk.Checkbutton(tab, text="Auto?", variable=is_auto)
|
||||||
|
checkbutton.var = is_auto
|
||||||
|
checkbutton.grid(row=row, column=1, padx=PADX)
|
||||||
|
mac = tk.StringVar(value=interface.mac)
|
||||||
|
entry = ttk.Entry(tab, textvariable=mac, state=tk.DISABLED)
|
||||||
|
entry.grid(row=row, column=2, sticky="ew")
|
||||||
|
func = partial(mac_auto, is_auto, entry)
|
||||||
|
checkbutton.config(command=func)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
label = ttk.Label(tab, text="IPv4")
|
||||||
|
label.grid(row=row, column=0, padx=PADX, pady=PADY)
|
||||||
|
ip4 = tk.StringVar(value=f"{interface.ip4}/{interface.ip4mask}")
|
||||||
|
entry = ttk.Entry(tab, textvariable=ip4)
|
||||||
|
entry.bind("<FocusOut>", self.app.validation.ip_focus_out)
|
||||||
|
entry.grid(row=row, column=1, columnspan=2, sticky="ew")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
label = ttk.Label(tab, text="IPv6")
|
||||||
|
label.grid(row=row, column=0, padx=PADX, pady=PADY)
|
||||||
|
ip6 = tk.StringVar(value=f"{interface.ip6}/{interface.ip6mask}")
|
||||||
|
entry = ttk.Entry(tab, textvariable=ip6)
|
||||||
|
entry.bind("<FocusOut>", self.app.validation.ip_focus_out)
|
||||||
|
entry.grid(row=row, column=1, columnspan=2, sticky="ew")
|
||||||
|
|
||||||
|
self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6)
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.config_apply)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_emane_config(self, emane_model, interface_id):
|
||||||
|
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
def click_icon(self):
|
||||||
|
file_path = image_chooser(self, ICONS_PATH)
|
||||||
|
if file_path:
|
||||||
|
self.image = Images.create(file_path, nodeutils.ICON_SIZE)
|
||||||
|
self.image_button.config(image=self.image)
|
||||||
|
self.image_file = file_path
|
||||||
|
|
||||||
|
def config_apply(self):
|
||||||
|
# update core node
|
||||||
|
self.node.name = self.name.get()
|
||||||
|
if NodeUtils.is_image_node(self.node.type):
|
||||||
|
self.node.image = self.container_image.get()
|
||||||
|
server = self.server.get()
|
||||||
|
if NodeUtils.is_container_node(self.node.type) and server != "localhost":
|
||||||
|
self.node.server = server
|
||||||
|
|
||||||
|
# set custom icon
|
||||||
|
if self.image_file:
|
||||||
|
self.node.icon = self.image_file
|
||||||
|
|
||||||
|
# update canvas node
|
||||||
|
self.canvas_node.image = self.image
|
||||||
|
|
||||||
|
# redraw
|
||||||
|
self.canvas_node.redraw()
|
||||||
|
self.destroy()
|
134
coretk/coretk/dialogs/nodeservice.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
"""
|
||||||
|
core node services
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import messagebox, ttk
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.dialogs.serviceconfiguration import ServiceConfiguration
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
from coretk.widgets import CheckboxList, ListboxScroll
|
||||||
|
|
||||||
|
|
||||||
|
class NodeService(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node, services=None):
|
||||||
|
title = f"{canvas_node.core_node.name} Services"
|
||||||
|
super().__init__(master, app, title, modal=True)
|
||||||
|
self.app = app
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node_id = canvas_node.core_node.id
|
||||||
|
self.groups = None
|
||||||
|
self.services = None
|
||||||
|
self.current = None
|
||||||
|
if services is None:
|
||||||
|
services = canvas_node.core_node.services
|
||||||
|
model = canvas_node.core_node.model
|
||||||
|
if len(services) == 0:
|
||||||
|
services = set(self.app.core.default_services[model])
|
||||||
|
else:
|
||||||
|
services = set(services)
|
||||||
|
|
||||||
|
self.current_services = services
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(stick="nsew", pady=PADY)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(row=0, column=0, sticky="nsew")
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
self.groups = ListboxScroll(label_frame)
|
||||||
|
self.groups.grid(sticky="nsew")
|
||||||
|
for group in sorted(self.app.core.services):
|
||||||
|
self.groups.listbox.insert(tk.END, group)
|
||||||
|
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
||||||
|
self.groups.listbox.selection_set(0)
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Services")
|
||||||
|
label_frame.grid(row=0, column=1, sticky="nsew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
self.services = CheckboxList(
|
||||||
|
label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
self.services.grid(sticky="nsew")
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(row=0, column=2, sticky="nsew")
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
self.current = ListboxScroll(label_frame)
|
||||||
|
self.current.grid(sticky="nsew")
|
||||||
|
for service in sorted(self.current_services):
|
||||||
|
self.current.listbox.insert(tk.END, service)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(stick="ew")
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Configure", command=self.click_configure)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.click_save)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
# trigger group change
|
||||||
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
|
def handle_group_change(self, event):
|
||||||
|
selection = self.groups.listbox.curselection()
|
||||||
|
if selection:
|
||||||
|
index = selection[0]
|
||||||
|
group = self.groups.listbox.get(index)
|
||||||
|
self.services.clear()
|
||||||
|
for name in sorted(self.app.core.services[group]):
|
||||||
|
checked = name in self.current_services
|
||||||
|
self.services.add(name, checked)
|
||||||
|
|
||||||
|
def service_clicked(self, name, var):
|
||||||
|
if var.get() and name not in self.current_services:
|
||||||
|
self.current_services.add(name)
|
||||||
|
elif not var.get() and name in self.current_services:
|
||||||
|
self.current_services.remove(name)
|
||||||
|
self.current.listbox.delete(0, tk.END)
|
||||||
|
for name in sorted(self.current_services):
|
||||||
|
self.current.listbox.insert(tk.END, name)
|
||||||
|
self.canvas_node.core_node.services[:] = self.current_services
|
||||||
|
|
||||||
|
def click_configure(self):
|
||||||
|
current_selection = self.current.listbox.curselection()
|
||||||
|
if len(current_selection):
|
||||||
|
dialog = ServiceConfiguration(
|
||||||
|
master=self,
|
||||||
|
app=self.app,
|
||||||
|
service_name=self.current.listbox.get(current_selection[0]),
|
||||||
|
node_id=self.node_id,
|
||||||
|
)
|
||||||
|
dialog.show()
|
||||||
|
else:
|
||||||
|
messagebox.showinfo(
|
||||||
|
"Node service configuration", "Select a service to configure"
|
||||||
|
)
|
||||||
|
|
||||||
|
def click_save(self):
|
||||||
|
if (
|
||||||
|
self.current_services
|
||||||
|
!= self.app.core.default_services[self.canvas_node.core_node.model]
|
||||||
|
):
|
||||||
|
self.canvas_node.core_node.services[:] = self.current_services
|
||||||
|
else:
|
||||||
|
if len(self.canvas_node.core_node.services) > 0:
|
||||||
|
self.canvas_node.core_node.services[:] = []
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_cancel(self):
|
||||||
|
self.current_services = None
|
||||||
|
self.destroy()
|
143
coretk/coretk/dialogs/observers.py
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.coreclient import Observer
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import ListboxScroll
|
||||||
|
|
||||||
|
|
||||||
|
class ObserverDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Observer Widgets", modal=True)
|
||||||
|
self.observers = None
|
||||||
|
self.save_button = None
|
||||||
|
self.delete_button = None
|
||||||
|
self.selected = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.name = tk.StringVar()
|
||||||
|
self.cmd = tk.StringVar()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.draw_listbox()
|
||||||
|
self.draw_form_fields()
|
||||||
|
self.draw_config_buttons()
|
||||||
|
self.draw_apply_buttons()
|
||||||
|
|
||||||
|
def draw_listbox(self):
|
||||||
|
listbox_scroll = ListboxScroll(self.top)
|
||||||
|
listbox_scroll.grid(sticky="nsew", pady=PADY)
|
||||||
|
listbox_scroll.columnconfigure(0, weight=1)
|
||||||
|
listbox_scroll.rowconfigure(0, weight=1)
|
||||||
|
self.observers = listbox_scroll.listbox
|
||||||
|
self.observers.grid(row=0, column=0, sticky="nsew")
|
||||||
|
self.observers.bind("<<ListboxSelect>>", self.handle_observer_change)
|
||||||
|
for name in sorted(self.app.core.custom_observers):
|
||||||
|
self.observers.insert(tk.END, name)
|
||||||
|
|
||||||
|
def draw_form_fields(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Name")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.name)
|
||||||
|
entry.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Command")
|
||||||
|
label.grid(row=1, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.cmd)
|
||||||
|
entry.grid(row=1, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def draw_config_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Create", command=self.click_create)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.save_button = ttk.Button(
|
||||||
|
frame, text="Save", state=tk.DISABLED, command=self.click_save
|
||||||
|
)
|
||||||
|
self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.delete_button = ttk.Button(
|
||||||
|
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
|
||||||
|
)
|
||||||
|
self.delete_button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
def draw_apply_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.click_save_config)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_save_config(self):
|
||||||
|
observers = []
|
||||||
|
for name in sorted(self.app.core.custom_observers):
|
||||||
|
observer = self.app.core.custom_observers[name]
|
||||||
|
observers.append({"name": observer.name, "cmd": observer.cmd})
|
||||||
|
self.app.guiconfig["observers"] = observers
|
||||||
|
self.app.save_config()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_create(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if name not in self.app.core.custom_observers:
|
||||||
|
cmd = self.cmd.get()
|
||||||
|
observer = Observer(name, cmd)
|
||||||
|
self.app.core.custom_observers[name] = observer
|
||||||
|
self.observers.insert(tk.END, name)
|
||||||
|
|
||||||
|
def click_save(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if self.selected:
|
||||||
|
previous_name = self.selected
|
||||||
|
self.selected = name
|
||||||
|
observer = self.app.core.custom_observers.pop(previous_name)
|
||||||
|
observer.name = name
|
||||||
|
observer.cmd = self.cmd.get()
|
||||||
|
self.app.core.custom_observers[name] = observer
|
||||||
|
self.observers.delete(self.selected_index)
|
||||||
|
self.observers.insert(self.selected_index, name)
|
||||||
|
self.observers.selection_set(self.selected_index)
|
||||||
|
|
||||||
|
def click_delete(self):
|
||||||
|
if self.selected:
|
||||||
|
self.observers.delete(self.selected_index)
|
||||||
|
del self.app.core.custom_observers[self.selected]
|
||||||
|
self.selected = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.name.set("")
|
||||||
|
self.cmd.set("")
|
||||||
|
self.observers.selection_clear(0, tk.END)
|
||||||
|
self.save_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def handle_observer_change(self, event):
|
||||||
|
selection = self.observers.curselection()
|
||||||
|
if selection:
|
||||||
|
self.selected_index = selection[0]
|
||||||
|
self.selected = self.observers.get(self.selected_index)
|
||||||
|
observer = self.app.core.custom_observers[self.selected]
|
||||||
|
self.name.set(observer.name)
|
||||||
|
self.cmd.set(observer.cmd)
|
||||||
|
self.save_button.config(state=tk.NORMAL)
|
||||||
|
self.delete_button.config(state=tk.NORMAL)
|
||||||
|
else:
|
||||||
|
self.selected_index = None
|
||||||
|
self.selected = None
|
||||||
|
self.save_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
87
coretk/coretk/dialogs/preferences.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk import appconfig
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
|
||||||
|
class PreferencesDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Preferences", modal=True)
|
||||||
|
preferences = self.app.guiconfig["preferences"]
|
||||||
|
self.editor = tk.StringVar(value=preferences["editor"])
|
||||||
|
self.theme = tk.StringVar(value=preferences["theme"])
|
||||||
|
self.terminal = tk.StringVar(value=preferences["terminal"])
|
||||||
|
self.gui3d = tk.StringVar(value=preferences["gui3d"])
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.draw_preferences()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_preferences(self):
|
||||||
|
frame = ttk.LabelFrame(self.top, text="Preferences", padding=FRAME_PAD)
|
||||||
|
frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Theme")
|
||||||
|
label.grid(row=0, column=0, pady=PADY, padx=PADX, sticky="w")
|
||||||
|
themes = self.app.style.theme_names()
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.theme, values=themes, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.set(self.theme.get())
|
||||||
|
combobox.grid(row=0, column=1, sticky="ew")
|
||||||
|
combobox.bind("<<ComboboxSelected>>", self.theme_change)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Editor")
|
||||||
|
label.grid(row=1, column=0, pady=PADY, padx=PADX, sticky="w")
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.editor, values=appconfig.EDITORS, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.grid(row=1, column=1, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Terminal")
|
||||||
|
label.grid(row=2, column=0, pady=PADY, padx=PADX, sticky="w")
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame,
|
||||||
|
textvariable=self.terminal,
|
||||||
|
values=appconfig.TERMINALS,
|
||||||
|
state="readonly",
|
||||||
|
)
|
||||||
|
combobox.grid(row=2, column=1, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="3D GUI")
|
||||||
|
label.grid(row=3, column=0, pady=PADY, padx=PADX, sticky="w")
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.gui3d)
|
||||||
|
entry.grid(row=3, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.click_save)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def theme_change(self, event):
|
||||||
|
theme = self.theme.get()
|
||||||
|
logging.info("changing theme: %s", theme)
|
||||||
|
self.app.style.theme_use(theme)
|
||||||
|
|
||||||
|
def click_save(self):
|
||||||
|
preferences = self.app.guiconfig["preferences"]
|
||||||
|
preferences["terminal"] = self.terminal.get()
|
||||||
|
preferences["editor"] = self.editor.get()
|
||||||
|
preferences["gui3d"] = self.gui3d.get()
|
||||||
|
preferences["theme"] = self.theme.get()
|
||||||
|
self.app.save_config()
|
||||||
|
self.destroy()
|
173
coretk/coretk/dialogs/servers.py
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from coretk.coreclient import CoreServer
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
from coretk.widgets import ListboxScroll
|
||||||
|
|
||||||
|
DEFAULT_NAME = "example"
|
||||||
|
DEFAULT_ADDRESS = "127.0.0.1"
|
||||||
|
DEFAULT_PORT = 50051
|
||||||
|
|
||||||
|
|
||||||
|
class ServersDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "CORE Servers", modal=True)
|
||||||
|
self.name = tk.StringVar(value=DEFAULT_NAME)
|
||||||
|
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
|
||||||
|
self.port = tk.IntVar(value=DEFAULT_PORT)
|
||||||
|
self.servers = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.selected = None
|
||||||
|
self.save_button = None
|
||||||
|
self.delete_button = None
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.draw_servers()
|
||||||
|
self.draw_servers_buttons()
|
||||||
|
self.draw_server_configuration()
|
||||||
|
self.draw_apply_buttons()
|
||||||
|
|
||||||
|
def draw_servers(self):
|
||||||
|
listbox_scroll = ListboxScroll(self.top)
|
||||||
|
listbox_scroll.grid(pady=PADY, sticky="nsew")
|
||||||
|
listbox_scroll.columnconfigure(0, weight=1)
|
||||||
|
listbox_scroll.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.servers = listbox_scroll.listbox
|
||||||
|
self.servers.grid(row=0, column=0, sticky="nsew")
|
||||||
|
self.servers.bind("<<ListboxSelect>>", self.handle_server_change)
|
||||||
|
|
||||||
|
for server in self.app.core.servers:
|
||||||
|
self.servers.insert(tk.END, server)
|
||||||
|
|
||||||
|
def draw_server_configuration(self):
|
||||||
|
frame = ttk.LabelFrame(self.top, text="Server Configuration", padding=FRAME_PAD)
|
||||||
|
frame.grid(pady=PADY, sticky="ew")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(3, weight=1)
|
||||||
|
frame.columnconfigure(5, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Name")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX, pady=PADY)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.name)
|
||||||
|
entry.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Address")
|
||||||
|
label.grid(row=0, column=2, sticky="w", padx=PADX, pady=PADY)
|
||||||
|
entry = ttk.Entry(frame, textvariable=self.address)
|
||||||
|
entry.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Port")
|
||||||
|
label.grid(row=0, column=4, sticky="w", padx=PADX, pady=PADY)
|
||||||
|
entry = ttk.Entry(
|
||||||
|
frame,
|
||||||
|
textvariable=self.port,
|
||||||
|
validate="key",
|
||||||
|
validatecommand=(self.app.validation.positive_int, "%P"),
|
||||||
|
)
|
||||||
|
entry.bind(
|
||||||
|
"<FocusOut>", lambda event: self.app.validation.focus_out(event, "50051")
|
||||||
|
)
|
||||||
|
entry.grid(row=0, column=5, sticky="ew")
|
||||||
|
|
||||||
|
def draw_servers_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(pady=PADY, sticky="ew")
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Create", command=self.click_create)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.save_button = ttk.Button(
|
||||||
|
frame, text="Save", state=tk.DISABLED, command=self.click_save
|
||||||
|
)
|
||||||
|
self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
self.delete_button = ttk.Button(
|
||||||
|
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
|
||||||
|
)
|
||||||
|
self.delete_button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
def draw_apply_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(
|
||||||
|
frame, text="Save Configuration", command=self.click_save_configuration
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_save_configuration(self):
|
||||||
|
servers = []
|
||||||
|
for name in sorted(self.app.core.servers):
|
||||||
|
server = self.app.core.servers[name]
|
||||||
|
servers.append(
|
||||||
|
{"name": server.name, "address": server.address, "port": server.port}
|
||||||
|
)
|
||||||
|
self.app.guiconfig["servers"] = servers
|
||||||
|
self.app.save_config()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_create(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if name not in self.app.core.servers:
|
||||||
|
address = self.address.get()
|
||||||
|
port = self.port.get()
|
||||||
|
server = CoreServer(name, address, port)
|
||||||
|
self.app.core.servers[name] = server
|
||||||
|
self.servers.insert(tk.END, name)
|
||||||
|
|
||||||
|
def click_save(self):
|
||||||
|
name = self.name.get()
|
||||||
|
if self.selected:
|
||||||
|
previous_name = self.selected
|
||||||
|
self.selected = name
|
||||||
|
server = self.app.core.servers.pop(previous_name)
|
||||||
|
server.name = name
|
||||||
|
server.address = self.address.get()
|
||||||
|
server.port = self.port.get()
|
||||||
|
self.app.core.servers[name] = server
|
||||||
|
self.servers.delete(self.selected_index)
|
||||||
|
self.servers.insert(self.selected_index, name)
|
||||||
|
self.servers.selection_set(self.selected_index)
|
||||||
|
|
||||||
|
def click_delete(self):
|
||||||
|
if self.selected:
|
||||||
|
self.servers.delete(self.selected_index)
|
||||||
|
del self.app.core.servers[self.selected]
|
||||||
|
self.selected = None
|
||||||
|
self.selected_index = None
|
||||||
|
self.name.set(DEFAULT_NAME)
|
||||||
|
self.address.set(DEFAULT_ADDRESS)
|
||||||
|
self.port.set(DEFAULT_PORT)
|
||||||
|
self.servers.selection_clear(0, tk.END)
|
||||||
|
self.save_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def handle_server_change(self, event):
|
||||||
|
selection = self.servers.curselection()
|
||||||
|
if selection:
|
||||||
|
self.selected_index = selection[0]
|
||||||
|
self.selected = self.servers.get(self.selected_index)
|
||||||
|
server = self.app.core.servers[self.selected]
|
||||||
|
self.name.set(server.name)
|
||||||
|
self.address.set(server.address)
|
||||||
|
self.port.set(server.port)
|
||||||
|
self.save_button.config(state=tk.NORMAL)
|
||||||
|
self.delete_button.config(state=tk.NORMAL)
|
||||||
|
else:
|
||||||
|
self.selected_index = None
|
||||||
|
self.selected = None
|
||||||
|
self.save_button.config(state=tk.DISABLED)
|
||||||
|
self.delete_button.config(state=tk.DISABLED)
|
453
coretk/coretk/dialogs/serviceconfiguration.py
Normal file
|
@ -0,0 +1,453 @@
|
||||||
|
"Service configuration dialog"
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
from coretk.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceConfiguration(Dialog):
|
||||||
|
def __init__(self, master, app, service_name, node_id):
|
||||||
|
title = f"{service_name} Service"
|
||||||
|
super().__init__(master, app, title, modal=True)
|
||||||
|
self.app = app
|
||||||
|
self.core = app.core
|
||||||
|
self.node_id = node_id
|
||||||
|
self.service_name = service_name
|
||||||
|
self.radiovar = tk.IntVar()
|
||||||
|
self.radiovar.set(2)
|
||||||
|
self.metadata = ""
|
||||||
|
self.filenames = []
|
||||||
|
self.dependencies = []
|
||||||
|
self.executables = []
|
||||||
|
self.startup_commands = []
|
||||||
|
self.validation_commands = []
|
||||||
|
self.shutdown_commands = []
|
||||||
|
self.validation_mode = None
|
||||||
|
self.validation_time = None
|
||||||
|
self.validation_period = None
|
||||||
|
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
|
||||||
|
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
|
||||||
|
|
||||||
|
self.notebook = None
|
||||||
|
self.metadata_entry = None
|
||||||
|
self.filename_combobox = None
|
||||||
|
self.startup_commands_listbox = None
|
||||||
|
self.shutdown_commands_listbox = None
|
||||||
|
self.validate_commands_listbox = None
|
||||||
|
self.validation_time_entry = None
|
||||||
|
self.validation_mode_entry = None
|
||||||
|
self.service_file_data = None
|
||||||
|
self.validation_period_entry = None
|
||||||
|
self.original_service_files = {}
|
||||||
|
self.temp_service_files = {}
|
||||||
|
self.modified_files = set()
|
||||||
|
self.load()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
try:
|
||||||
|
self.app.core.create_nodes_and_links()
|
||||||
|
service_configs = self.app.core.service_configs
|
||||||
|
if (
|
||||||
|
self.node_id in service_configs
|
||||||
|
and self.service_name in service_configs[self.node_id]
|
||||||
|
):
|
||||||
|
service_config = self.app.core.service_configs[self.node_id][
|
||||||
|
self.service_name
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
service_config = self.app.core.get_node_service(
|
||||||
|
self.node_id, self.service_name
|
||||||
|
)
|
||||||
|
self.dependencies = [x for x in service_config.dependencies]
|
||||||
|
self.executables = [x for x in service_config.executables]
|
||||||
|
self.metadata = service_config.meta
|
||||||
|
self.filenames = [x for x in service_config.configs]
|
||||||
|
self.startup_commands = [x for x in service_config.startup]
|
||||||
|
self.validation_commands = [x for x in service_config.validate]
|
||||||
|
self.shutdown_commands = [x for x in service_config.shutdown]
|
||||||
|
self.validation_mode = service_config.validation_mode
|
||||||
|
self.validation_time = service_config.validation_timer
|
||||||
|
self.original_service_files = {
|
||||||
|
x: self.app.core.get_node_service_file(
|
||||||
|
self.node_id, self.service_name, x
|
||||||
|
)
|
||||||
|
for x in self.filenames
|
||||||
|
}
|
||||||
|
self.temp_service_files = {
|
||||||
|
x: self.original_service_files[x] for x in self.original_service_files
|
||||||
|
}
|
||||||
|
file_configs = self.app.core.file_configs
|
||||||
|
if (
|
||||||
|
self.node_id in file_configs
|
||||||
|
and self.service_name in file_configs[self.node_id]
|
||||||
|
):
|
||||||
|
for file, data in file_configs[self.node_id][self.service_name].items():
|
||||||
|
self.temp_service_files[file] = data
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# draw metadata
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Meta-data")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
self.metadata_entry = ttk.Entry(frame, textvariable=self.metadata)
|
||||||
|
self.metadata_entry.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
# draw notebook
|
||||||
|
self.notebook = ttk.Notebook(self.top)
|
||||||
|
self.notebook.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.draw_tab_files()
|
||||||
|
self.draw_tab_directories()
|
||||||
|
self.draw_tab_startstop()
|
||||||
|
self.draw_tab_configuration()
|
||||||
|
|
||||||
|
button = ttk.Button(self.top, text="Only Save Changes")
|
||||||
|
button.grid(sticky="ew", pady=PADY)
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_tab_files(self):
|
||||||
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
|
tab.grid(sticky="nsew")
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
self.notebook.add(tab, text="Files")
|
||||||
|
|
||||||
|
label = ttk.Label(
|
||||||
|
tab, text="Config files and scripts that are generated for this service."
|
||||||
|
)
|
||||||
|
label.grid()
|
||||||
|
|
||||||
|
frame = ttk.Frame(tab)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
label = ttk.Label(frame, text="File Name")
|
||||||
|
label.grid(row=0, column=0, padx=PADX, sticky="w")
|
||||||
|
self.filename_combobox = ttk.Combobox(
|
||||||
|
frame, values=self.filenames, state="readonly"
|
||||||
|
)
|
||||||
|
self.filename_combobox.bind(
|
||||||
|
"<<ComboboxSelected>>", self.display_service_file_data
|
||||||
|
)
|
||||||
|
self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, image=self.documentnew_img, state="disabled")
|
||||||
|
button.bind("<Button-1>", self.add_filename)
|
||||||
|
button.grid(row=0, column=2, padx=PADX)
|
||||||
|
button = ttk.Button(frame, image=self.editdelete_img, state="disabled")
|
||||||
|
button.bind("<Button-1>", self.delete_filename)
|
||||||
|
button.grid(row=0, column=3)
|
||||||
|
|
||||||
|
frame = ttk.Frame(tab)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame,
|
||||||
|
variable=self.radiovar,
|
||||||
|
text="Copy Source File",
|
||||||
|
value=1,
|
||||||
|
state=tk.DISABLED,
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
entry = ttk.Entry(frame, state=tk.DISABLED)
|
||||||
|
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
image = Images.get(ImageEnum.FILEOPEN, 16)
|
||||||
|
button = ttk.Button(frame, image=image)
|
||||||
|
button.image = image
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
|
||||||
|
frame = ttk.Frame(tab)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
button = ttk.Radiobutton(
|
||||||
|
frame,
|
||||||
|
variable=self.radiovar,
|
||||||
|
text="Use text below for file contents",
|
||||||
|
value=2,
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=0, sticky="ew")
|
||||||
|
image = Images.get(ImageEnum.FILEOPEN, 16)
|
||||||
|
button = ttk.Button(frame, image=image)
|
||||||
|
button.image = image
|
||||||
|
button.grid(row=0, column=1)
|
||||||
|
image = Images.get(ImageEnum.DOCUMENTSAVE, 16)
|
||||||
|
button = ttk.Button(frame, image=image)
|
||||||
|
button.image = image
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
|
||||||
|
self.service_file_data = CodeText(tab)
|
||||||
|
self.service_file_data.grid(sticky="nsew")
|
||||||
|
tab.rowconfigure(self.service_file_data.grid_info()["row"], weight=1)
|
||||||
|
if len(self.filenames) > 0:
|
||||||
|
self.filename_combobox.current(0)
|
||||||
|
self.service_file_data.text.delete(1.0, "end")
|
||||||
|
self.service_file_data.text.insert(
|
||||||
|
"end", self.temp_service_files[self.filenames[0]]
|
||||||
|
)
|
||||||
|
self.service_file_data.text.bind(
|
||||||
|
"<FocusOut>", self.update_temp_service_file_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw_tab_directories(self):
|
||||||
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
|
tab.grid(sticky="nsew")
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
self.notebook.add(tab, text="Directories")
|
||||||
|
|
||||||
|
label = ttk.Label(
|
||||||
|
tab,
|
||||||
|
text="Directories required by this service that are unique for each node.",
|
||||||
|
)
|
||||||
|
label.grid()
|
||||||
|
|
||||||
|
def draw_tab_startstop(self):
|
||||||
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
|
tab.grid(sticky="nsew")
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
for i in range(3):
|
||||||
|
tab.rowconfigure(i, weight=1)
|
||||||
|
self.notebook.add(tab, text="Startup/Shutdown")
|
||||||
|
|
||||||
|
# tab 3
|
||||||
|
for i in range(3):
|
||||||
|
label_frame = None
|
||||||
|
if i == 0:
|
||||||
|
label_frame = ttk.LabelFrame(
|
||||||
|
tab, text="Startup Commands", padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
commands = self.startup_commands
|
||||||
|
elif i == 1:
|
||||||
|
label_frame = ttk.LabelFrame(
|
||||||
|
tab, text="Shutdown Commands", padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
commands = self.shutdown_commands
|
||||||
|
elif i == 2:
|
||||||
|
label_frame = ttk.LabelFrame(
|
||||||
|
tab, text="Validation Commands", padding=FRAME_PAD
|
||||||
|
)
|
||||||
|
commands = self.validation_commands
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
label_frame.rowconfigure(1, weight=1)
|
||||||
|
label_frame.grid(row=i, column=0, sticky="nsew", pady=PADY)
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(row=0, column=0, sticky="nsew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
entry = ttk.Entry(frame, textvariable=tk.StringVar())
|
||||||
|
entry.grid(row=0, column=0, stick="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, image=self.documentnew_img)
|
||||||
|
button.bind("<Button-1>", self.add_command)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, image=self.editdelete_img)
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
button.bind("<Button-1>", self.delete_command)
|
||||||
|
listbox_scroll = ListboxScroll(label_frame)
|
||||||
|
listbox_scroll.listbox.bind("<<ListboxSelect>>", self.update_entry)
|
||||||
|
for command in commands:
|
||||||
|
listbox_scroll.listbox.insert("end", command)
|
||||||
|
listbox_scroll.listbox.config(height=4)
|
||||||
|
listbox_scroll.grid(row=1, column=0, sticky="nsew")
|
||||||
|
if i == 0:
|
||||||
|
self.startup_commands_listbox = listbox_scroll.listbox
|
||||||
|
elif i == 1:
|
||||||
|
self.shutdown_commands_listbox = listbox_scroll.listbox
|
||||||
|
elif i == 2:
|
||||||
|
self.validate_commands_listbox = listbox_scroll.listbox
|
||||||
|
|
||||||
|
def draw_tab_configuration(self):
|
||||||
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
|
tab.grid(sticky="nsew")
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
self.notebook.add(tab, text="Configuration", sticky="nsew")
|
||||||
|
|
||||||
|
frame = ttk.Frame(tab)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Validation Time")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
self.validation_time_entry = ttk.Entry(frame)
|
||||||
|
self.validation_time_entry.insert("end", self.validation_time)
|
||||||
|
self.validation_time_entry.config(state=tk.DISABLED)
|
||||||
|
self.validation_time_entry.grid(row=0, column=1, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Validation Mode")
|
||||||
|
label.grid(row=1, column=0, sticky="w", padx=PADX)
|
||||||
|
if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING:
|
||||||
|
mode = "BLOCKING"
|
||||||
|
elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING:
|
||||||
|
mode = "NON_BLOCKING"
|
||||||
|
else:
|
||||||
|
mode = "TIMER"
|
||||||
|
self.validation_mode_entry = ttk.Entry(
|
||||||
|
frame, textvariable=tk.StringVar(value=mode)
|
||||||
|
)
|
||||||
|
self.validation_mode_entry.insert("end", mode)
|
||||||
|
self.validation_mode_entry.config(state=tk.DISABLED)
|
||||||
|
self.validation_mode_entry.grid(row=1, column=1, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Validation Period")
|
||||||
|
label.grid(row=2, column=0, sticky="w", padx=PADX)
|
||||||
|
self.validation_period_entry = ttk.Entry(
|
||||||
|
frame, state=tk.DISABLED, textvariable=tk.StringVar()
|
||||||
|
)
|
||||||
|
self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
listbox_scroll = ListboxScroll(label_frame)
|
||||||
|
listbox_scroll.grid(sticky="nsew")
|
||||||
|
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
|
||||||
|
for executable in self.executables:
|
||||||
|
listbox_scroll.listbox.insert("end", executable)
|
||||||
|
|
||||||
|
label_frame = ttk.LabelFrame(tab, text="Dependencies", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
label_frame.rowconfigure(0, weight=1)
|
||||||
|
listbox_scroll = ListboxScroll(label_frame)
|
||||||
|
listbox_scroll.grid(sticky="nsew")
|
||||||
|
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
|
||||||
|
for dependency in self.dependencies:
|
||||||
|
listbox_scroll.listbox.insert("end", dependency)
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(4):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(
|
||||||
|
frame, text="Defaults", command=self.click_defaults, state="disabled"
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(
|
||||||
|
frame, text="Copy...", command=self.click_copy, state="disabled"
|
||||||
|
)
|
||||||
|
button.grid(row=0, column=2, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
|
def add_filename(self, event):
|
||||||
|
# not worry about it for now
|
||||||
|
return
|
||||||
|
frame_contains_button = event.widget.master
|
||||||
|
combobox = frame_contains_button.grid_slaves(row=0, column=1)[0]
|
||||||
|
filename = combobox.get()
|
||||||
|
if filename not in combobox["values"]:
|
||||||
|
combobox["values"] += (filename,)
|
||||||
|
|
||||||
|
def delete_filename(self, event):
|
||||||
|
# not worry about it for now
|
||||||
|
return
|
||||||
|
frame_comntains_button = event.widget.master
|
||||||
|
combobox = frame_comntains_button.grid_slaves(row=0, column=1)[0]
|
||||||
|
filename = combobox.get()
|
||||||
|
if filename in combobox["values"]:
|
||||||
|
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
|
||||||
|
combobox.set("")
|
||||||
|
|
||||||
|
def add_command(self, event):
|
||||||
|
frame_contains_button = event.widget.master
|
||||||
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
|
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
|
||||||
|
if command_to_add == "":
|
||||||
|
return
|
||||||
|
for cmd in listbox.get(0, tk.END):
|
||||||
|
if cmd == command_to_add:
|
||||||
|
return
|
||||||
|
listbox.insert(tk.END, command_to_add)
|
||||||
|
|
||||||
|
def update_entry(self, event):
|
||||||
|
listbox = event.widget
|
||||||
|
current_selection = listbox.curselection()
|
||||||
|
if len(current_selection) > 0:
|
||||||
|
cmd = listbox.get(current_selection[0])
|
||||||
|
entry = listbox.master.master.grid_slaves(row=0, column=0)[0].grid_slaves(
|
||||||
|
row=0, column=0
|
||||||
|
)[0]
|
||||||
|
entry.delete(0, "end")
|
||||||
|
entry.insert(0, cmd)
|
||||||
|
|
||||||
|
def delete_command(self, event):
|
||||||
|
button = event.widget
|
||||||
|
frame_contains_button = button.master
|
||||||
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
|
current_selection = listbox.curselection()
|
||||||
|
if len(current_selection) > 0:
|
||||||
|
listbox.delete(current_selection[0])
|
||||||
|
entry = frame_contains_button.grid_slaves(row=0, column=0)[0]
|
||||||
|
entry.delete(0, tk.END)
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
service_configs = self.app.core.service_configs
|
||||||
|
startup_commands = self.startup_commands_listbox.get(0, "end")
|
||||||
|
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
|
||||||
|
validate_commands = self.validate_commands_listbox.get(0, "end")
|
||||||
|
try:
|
||||||
|
config = self.core.set_node_service(
|
||||||
|
self.node_id,
|
||||||
|
self.service_name,
|
||||||
|
startup_commands,
|
||||||
|
validate_commands,
|
||||||
|
shutdown_commands,
|
||||||
|
)
|
||||||
|
if self.node_id not in service_configs:
|
||||||
|
service_configs[self.node_id] = {}
|
||||||
|
if self.service_name not in service_configs[self.node_id]:
|
||||||
|
self.app.core.service_configs[self.node_id][self.service_name] = config
|
||||||
|
for file in self.modified_files:
|
||||||
|
file_configs = self.app.core.file_configs
|
||||||
|
if self.node_id not in file_configs:
|
||||||
|
file_configs[self.node_id] = {}
|
||||||
|
if self.service_name not in file_configs[self.node_id]:
|
||||||
|
file_configs[self.node_id][self.service_name] = {}
|
||||||
|
file_configs[self.node_id][self.service_name][
|
||||||
|
file
|
||||||
|
] = self.temp_service_files[file]
|
||||||
|
|
||||||
|
self.app.core.set_node_service_file(
|
||||||
|
self.node_id, self.service_name, file, self.temp_service_files[file]
|
||||||
|
)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def display_service_file_data(self, event):
|
||||||
|
combobox = event.widget
|
||||||
|
filename = combobox.get()
|
||||||
|
self.service_file_data.text.delete(1.0, "end")
|
||||||
|
self.service_file_data.text.insert("end", self.temp_service_files[filename])
|
||||||
|
|
||||||
|
def update_temp_service_file_data(self, event):
|
||||||
|
scrolledtext = event.widget
|
||||||
|
filename = self.filename_combobox.get()
|
||||||
|
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
|
||||||
|
if self.temp_service_files[filename] != self.original_service_files[filename]:
|
||||||
|
self.modified_files.add(filename)
|
||||||
|
else:
|
||||||
|
self.modified_files.discard(filename)
|
||||||
|
|
||||||
|
def click_defaults(self):
|
||||||
|
logging.info("not implemented")
|
||||||
|
|
||||||
|
def click_copy(self):
|
||||||
|
logging.info("not implemented")
|
||||||
|
|
||||||
|
def click_cancel(self):
|
||||||
|
logging.info("not implemented")
|
53
coretk/coretk/dialogs/sessionoptions.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import logging
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import ConfigFrame
|
||||||
|
|
||||||
|
|
||||||
|
class SessionOptionsDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Session Options", modal=True)
|
||||||
|
self.config_frame = None
|
||||||
|
self.config = self.get_config()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
try:
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
response = self.app.core.client.get_session_options(session_id)
|
||||||
|
return response.config
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, config=self.config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Save", command=self.save)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
config = self.config_frame.parse_config()
|
||||||
|
try:
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
response = self.app.core.client.set_session_options(session_id, config)
|
||||||
|
logging.info("saved session config: %s", response)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
181
coretk/coretk/dialogs/sessions.py
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.images import ImageEnum, Images
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
|
||||||
|
|
||||||
|
class SessionsDialog(Dialog):
|
||||||
|
def __init__(self, master, app):
|
||||||
|
super().__init__(master, app, "Sessions", modal=True)
|
||||||
|
self.selected = False
|
||||||
|
self.selected_id = None
|
||||||
|
self.tree = None
|
||||||
|
self.sessions = self.get_sessions()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def get_sessions(self):
|
||||||
|
try:
|
||||||
|
response = self.app.core.client.get_sessions()
|
||||||
|
logging.info("sessions: %s", response)
|
||||||
|
return response.sessions
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(1, weight=1)
|
||||||
|
self.draw_description()
|
||||||
|
self.draw_tree()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_description(self):
|
||||||
|
"""
|
||||||
|
write a short description
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
label = ttk.Label(
|
||||||
|
self.top,
|
||||||
|
text="Below is a list of active CORE sessions. Double-click to \n"
|
||||||
|
"connect to an existing session. Usually, only sessions in \n"
|
||||||
|
"the RUNTIME state persist in the daemon, except for the \n"
|
||||||
|
"one you might be concurrently editting.",
|
||||||
|
justify=tk.CENTER,
|
||||||
|
)
|
||||||
|
label.grid(pady=PADY)
|
||||||
|
|
||||||
|
def draw_tree(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.rowconfigure(0, weight=1)
|
||||||
|
frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.tree = ttk.Treeview(
|
||||||
|
frame, columns=("id", "state", "nodes"), show="headings"
|
||||||
|
)
|
||||||
|
self.tree.grid(sticky="nsew")
|
||||||
|
self.tree.column("id", stretch=tk.YES)
|
||||||
|
self.tree.heading("id", text="ID")
|
||||||
|
self.tree.column("state", stretch=tk.YES)
|
||||||
|
self.tree.heading("state", text="State")
|
||||||
|
self.tree.column("nodes", stretch=tk.YES)
|
||||||
|
self.tree.heading("nodes", text="Node Count")
|
||||||
|
|
||||||
|
for index, session in enumerate(self.sessions):
|
||||||
|
state_name = core_pb2.SessionState.Enum.Name(session.state)
|
||||||
|
self.tree.insert(
|
||||||
|
"",
|
||||||
|
tk.END,
|
||||||
|
text=str(session.id),
|
||||||
|
values=(session.id, state_name, session.nodes),
|
||||||
|
)
|
||||||
|
self.tree.bind("<Double-1>", self.on_selected)
|
||||||
|
self.tree.bind("<<TreeviewSelect>>", self.click_select)
|
||||||
|
|
||||||
|
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
|
||||||
|
yscrollbar.grid(row=0, column=1, sticky="ns")
|
||||||
|
self.tree.configure(yscrollcommand=yscrollbar.set)
|
||||||
|
|
||||||
|
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
|
||||||
|
xscrollbar.grid(row=1, sticky="ew")
|
||||||
|
self.tree.configure(xscrollcommand=xscrollbar.set)
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
for i in range(4):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.DOCUMENTNEW, 16)
|
||||||
|
b = ttk.Button(
|
||||||
|
frame, image=image, text="New", compound=tk.LEFT, command=self.click_new
|
||||||
|
)
|
||||||
|
b.image = image
|
||||||
|
b.grid(row=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.FILEOPEN, 16)
|
||||||
|
b = ttk.Button(
|
||||||
|
frame,
|
||||||
|
image=image,
|
||||||
|
text="Connect",
|
||||||
|
compound=tk.LEFT,
|
||||||
|
command=self.click_connect,
|
||||||
|
)
|
||||||
|
b.image = image
|
||||||
|
b.grid(row=0, column=1, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
image = Images.get(ImageEnum.EDITDELETE, 16)
|
||||||
|
b = ttk.Button(
|
||||||
|
frame,
|
||||||
|
image=image,
|
||||||
|
text="Shutdown",
|
||||||
|
compound=tk.LEFT,
|
||||||
|
command=self.click_shutdown,
|
||||||
|
)
|
||||||
|
b.image = image
|
||||||
|
b.grid(row=0, column=2, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
b = ttk.Button(frame, text="Cancel", command=self.click_new)
|
||||||
|
b.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
|
def click_new(self):
|
||||||
|
self.app.core.create_new_session()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_select(self, event):
|
||||||
|
item = self.tree.selection()
|
||||||
|
session_id = int(self.tree.item(item, "text"))
|
||||||
|
self.selected = True
|
||||||
|
self.selected_id = session_id
|
||||||
|
|
||||||
|
def click_connect(self):
|
||||||
|
"""
|
||||||
|
if no session is selected yet, create a new one else join that session
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if self.selected and self.selected_id is not None:
|
||||||
|
self.join_session(self.selected_id)
|
||||||
|
elif not self.selected and self.selected_id is None:
|
||||||
|
self.click_new()
|
||||||
|
else:
|
||||||
|
logging.error("sessions invalid state")
|
||||||
|
|
||||||
|
def click_shutdown(self):
|
||||||
|
"""
|
||||||
|
if no session is currently selected create a new session else shut the selected
|
||||||
|
session down.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if self.selected and self.selected_id is not None:
|
||||||
|
self.shutdown_session(self.selected_id)
|
||||||
|
elif not self.selected and self.selected_id is None:
|
||||||
|
self.click_new()
|
||||||
|
else:
|
||||||
|
logging.error("querysessiondrawing.py invalid state")
|
||||||
|
|
||||||
|
def join_session(self, session_id):
|
||||||
|
self.app.statusbar.progress_bar.start(5)
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self.app.core.join_session, args=([session_id])
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def on_selected(self, event):
|
||||||
|
item = self.tree.selection()
|
||||||
|
sid = int(self.tree.item(item, "text"))
|
||||||
|
self.join_session(sid)
|
||||||
|
|
||||||
|
def shutdown_session(self, sid):
|
||||||
|
self.app.core.stop_session(sid)
|
||||||
|
self.click_new()
|
||||||
|
self.destroy()
|
252
coretk/coretk/dialogs/shapemod.py
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
"""
|
||||||
|
shape input dialog
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import font, ttk
|
||||||
|
|
||||||
|
from coretk.dialogs.colorpicker import ColorPicker
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.graph import tags
|
||||||
|
from coretk.graph.shapeutils import is_draw_shape, is_shape_text
|
||||||
|
from coretk.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
||||||
|
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
|
||||||
|
class ShapeDialog(Dialog):
|
||||||
|
def __init__(self, master, app, shape):
|
||||||
|
if is_draw_shape(shape.shape_type):
|
||||||
|
title = "Add Shape"
|
||||||
|
else:
|
||||||
|
title = "Add Text"
|
||||||
|
super().__init__(master, app, title, modal=True)
|
||||||
|
self.canvas = app.canvas
|
||||||
|
self.fill = None
|
||||||
|
self.border = None
|
||||||
|
self.shape = shape
|
||||||
|
data = shape.shape_data
|
||||||
|
self.shape_text = tk.StringVar(value=data.text)
|
||||||
|
self.font = tk.StringVar(value=data.font)
|
||||||
|
self.font_size = tk.IntVar(value=data.font_size)
|
||||||
|
self.text_color = data.text_color
|
||||||
|
fill_color = data.fill_color
|
||||||
|
if not fill_color:
|
||||||
|
fill_color = "#CFCFFF"
|
||||||
|
self.fill_color = fill_color
|
||||||
|
self.border_color = data.border_color
|
||||||
|
self.border_width = tk.IntVar(value=0)
|
||||||
|
self.bold = tk.BooleanVar(value=data.bold)
|
||||||
|
self.italic = tk.BooleanVar(value=data.italic)
|
||||||
|
self.underline = tk.BooleanVar(value=data.underline)
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.draw_label_options()
|
||||||
|
if is_draw_shape(self.shape.shape_type):
|
||||||
|
self.draw_shape_options()
|
||||||
|
self.draw_spacer()
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_label_options(self):
|
||||||
|
label_frame = ttk.LabelFrame(self.top, text="Label", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="ew")
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
entry = ttk.Entry(label_frame, textvariable=self.shape_text)
|
||||||
|
entry.grid(sticky="ew", pady=PADY)
|
||||||
|
|
||||||
|
# font options
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
frame.columnconfigure(2, weight=1)
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame,
|
||||||
|
textvariable=self.font,
|
||||||
|
values=sorted(font.families()),
|
||||||
|
state="readonly",
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=0, sticky="nsew")
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.font_size, values=FONT_SIZES, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=1, padx=PADX, sticky="nsew")
|
||||||
|
button = ttk.Button(frame, text="Color", command=self.choose_text_color)
|
||||||
|
button.grid(row=0, column=2, sticky="nsew")
|
||||||
|
|
||||||
|
# style options
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Checkbutton(frame, variable=self.bold, text="Bold")
|
||||||
|
button.grid(row=0, column=0, sticky="ew")
|
||||||
|
button = ttk.Checkbutton(frame, variable=self.italic, text="Italic")
|
||||||
|
button.grid(row=0, column=1, padx=PADX, sticky="ew")
|
||||||
|
button = ttk.Checkbutton(frame, variable=self.underline, text="Underline")
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
def draw_shape_options(self):
|
||||||
|
label_frame = ttk.LabelFrame(self.top, text="Shape", padding=FRAME_PAD)
|
||||||
|
label_frame.grid(sticky="ew", pady=PADY)
|
||||||
|
label_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(1, 3):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Fill Color")
|
||||||
|
label.grid(row=0, column=0, padx=PADX, sticky="w")
|
||||||
|
self.fill = ttk.Label(frame, text=self.fill_color, background=self.fill_color)
|
||||||
|
self.fill.grid(row=0, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Color", command=self.choose_fill_color)
|
||||||
|
button.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
label = ttk.Label(frame, text="Border Color")
|
||||||
|
label.grid(row=1, column=0, sticky="w", padx=PADX)
|
||||||
|
self.border = ttk.Label(
|
||||||
|
frame, text=self.border_color, background=self.border_color
|
||||||
|
)
|
||||||
|
self.border.grid(row=1, column=1, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Color", command=self.choose_border_color)
|
||||||
|
button.grid(row=1, column=2, sticky="ew")
|
||||||
|
|
||||||
|
frame = ttk.Frame(label_frame)
|
||||||
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
label = ttk.Label(frame, text="Border Width")
|
||||||
|
label.grid(row=0, column=0, sticky="w", padx=PADX)
|
||||||
|
combobox = ttk.Combobox(
|
||||||
|
frame, textvariable=self.border_width, values=BORDER_WIDTH, state="readonly"
|
||||||
|
)
|
||||||
|
combobox.grid(row=0, column=1, sticky="nsew")
|
||||||
|
|
||||||
|
def draw_buttons(self):
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="nsew")
|
||||||
|
frame.columnconfigure(0, weight=1)
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Add shape", command=self.click_add)
|
||||||
|
button.grid(row=0, column=0, sticky="ew", padx=PADX)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.cancel)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def choose_text_color(self):
|
||||||
|
color_picker = ColorPicker(self, self.app, "#000000")
|
||||||
|
color = color_picker.askcolor()
|
||||||
|
self.text_color = color
|
||||||
|
|
||||||
|
def choose_fill_color(self):
|
||||||
|
color_picker = ColorPicker(self, self.app, self.fill_color)
|
||||||
|
color = color_picker.askcolor()
|
||||||
|
self.fill_color = color
|
||||||
|
self.fill.config(background=color, text=color)
|
||||||
|
|
||||||
|
def choose_border_color(self):
|
||||||
|
color_picker = ColorPicker(self, self.app, self.border_color)
|
||||||
|
color = color_picker.askcolor()
|
||||||
|
self.border_color = color
|
||||||
|
self.border.config(background=color, text=color)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.shape.delete()
|
||||||
|
self.canvas.shapes.pop(self.shape.id)
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def click_add(self):
|
||||||
|
if is_draw_shape(self.shape.shape_type):
|
||||||
|
self.add_shape()
|
||||||
|
elif is_shape_text(self.shape.shape_type):
|
||||||
|
self.add_text()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def make_font(self):
|
||||||
|
"""
|
||||||
|
create font for text or shape label
|
||||||
|
:return: list(font specifications)
|
||||||
|
"""
|
||||||
|
size = int(self.font_size.get())
|
||||||
|
text_font = [self.font.get(), size]
|
||||||
|
if self.bold.get():
|
||||||
|
text_font.append("bold")
|
||||||
|
if self.italic.get():
|
||||||
|
text_font.append("italic")
|
||||||
|
if self.underline.get():
|
||||||
|
text_font.append("underline")
|
||||||
|
return text_font
|
||||||
|
|
||||||
|
def save_text(self):
|
||||||
|
"""
|
||||||
|
save info related to text or shape label
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
data = self.shape.shape_data
|
||||||
|
data.text = self.shape_text.get()
|
||||||
|
data.font = self.font.get()
|
||||||
|
data.font_size = int(self.font_size.get())
|
||||||
|
data.text_color = self.text_color
|
||||||
|
data.bold = self.bold.get()
|
||||||
|
data.italic = self.italic.get()
|
||||||
|
data.underline = self.underline.get()
|
||||||
|
|
||||||
|
def save_shape(self):
|
||||||
|
"""
|
||||||
|
save info related to shape
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
data = self.shape.shape_data
|
||||||
|
data.fill_color = self.fill_color
|
||||||
|
data.border_color = self.border_color
|
||||||
|
data.border_width = int(self.border_width.get())
|
||||||
|
|
||||||
|
def add_text(self):
|
||||||
|
"""
|
||||||
|
add text to canvas
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
text = self.shape_text.get()
|
||||||
|
text_font = self.make_font()
|
||||||
|
self.canvas.itemconfig(
|
||||||
|
self.shape.id, text=text, fill=self.text_color, font=text_font
|
||||||
|
)
|
||||||
|
self.save_text()
|
||||||
|
|
||||||
|
def add_shape(self):
|
||||||
|
self.canvas.itemconfig(
|
||||||
|
self.shape.id,
|
||||||
|
fill=self.fill_color,
|
||||||
|
dash="",
|
||||||
|
outline=self.border_color,
|
||||||
|
width=int(self.border_width.get()),
|
||||||
|
)
|
||||||
|
shape_text = self.shape_text.get()
|
||||||
|
size = int(self.font_size.get())
|
||||||
|
x0, y0, x1, y1 = self.canvas.bbox(self.shape.id)
|
||||||
|
_y = y0 + 1.5 * size
|
||||||
|
_x = (x0 + x1) / 2
|
||||||
|
text_font = self.make_font()
|
||||||
|
if self.shape.text_id is None:
|
||||||
|
self.shape.text_id = self.canvas.create_text(
|
||||||
|
_x,
|
||||||
|
_y,
|
||||||
|
text=shape_text,
|
||||||
|
fill=self.text_color,
|
||||||
|
font=text_font,
|
||||||
|
tags=tags.SHAPE_TEXT,
|
||||||
|
)
|
||||||
|
self.shape.created = True
|
||||||
|
else:
|
||||||
|
self.canvas.itemconfig(
|
||||||
|
self.shape.text_id,
|
||||||
|
text=shape_text,
|
||||||
|
fill=self.text_color,
|
||||||
|
font=text_font,
|
||||||
|
)
|
||||||
|
self.save_text()
|
||||||
|
self.save_shape()
|
66
coretk/coretk/dialogs/wlanconfig.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
"""
|
||||||
|
wlan configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from coretk.dialogs.dialog import Dialog
|
||||||
|
from coretk.errors import show_grpc_error
|
||||||
|
from coretk.themes import PADX, PADY
|
||||||
|
from coretk.widgets import ConfigFrame
|
||||||
|
|
||||||
|
|
||||||
|
class WlanConfigDialog(Dialog):
|
||||||
|
def __init__(self, master, app, canvas_node):
|
||||||
|
super().__init__(
|
||||||
|
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
|
||||||
|
)
|
||||||
|
self.canvas_node = canvas_node
|
||||||
|
self.node = canvas_node.core_node
|
||||||
|
self.config_frame = None
|
||||||
|
try:
|
||||||
|
self.config = self.app.core.get_wlan_config(self.node.id)
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
show_grpc_error(e)
|
||||||
|
self.destroy()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, self.config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky="nsew", pady=PADY)
|
||||||
|
self.draw_apply_buttons()
|
||||||
|
|
||||||
|
def draw_apply_buttons(self):
|
||||||
|
"""
|
||||||
|
create node configuration options
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky="ew")
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky="ew")
|
||||||
|
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
def click_apply(self):
|
||||||
|
"""
|
||||||
|
retrieve user's wlan configuration and store the new configuration values
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
config = self.config_frame.parse_config()
|
||||||
|
self.app.core.wlan_configs[self.node.id] = self.config
|
||||||
|
if self.app.core.is_runtime():
|
||||||
|
session_id = self.app.core.session_id
|
||||||
|
self.app.core.client.set_wlan_config(session_id, self.node.id, config)
|
||||||
|
self.destroy()
|
8
coretk/coretk/errors.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
|
||||||
|
def show_grpc_error(e):
|
||||||
|
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
||||||
|
title = " ".join(title)
|
||||||
|
title = f"GRPC {title}"
|
||||||
|
messagebox.showerror(title, e.details())
|
0
coretk/coretk/graph/__init__.py
Normal file
181
coretk/coretk/graph/edges.py
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter.font import Font
|
||||||
|
|
||||||
|
from coretk import themes
|
||||||
|
from coretk.dialogs.linkconfig import LinkConfiguration
|
||||||
|
from coretk.graph import tags
|
||||||
|
from coretk.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
TEXT_DISTANCE = 0.30
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasWirelessEdge:
|
||||||
|
def __init__(self, token, position, src, dst, canvas):
|
||||||
|
self.token = token
|
||||||
|
self.src = src
|
||||||
|
self.dst = dst
|
||||||
|
self.canvas = canvas
|
||||||
|
self.id = self.canvas.create_line(
|
||||||
|
*position, tags=tags.WIRELESS_EDGE, width=1.5, fill="#009933"
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.canvas.delete(self.id)
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasEdge:
|
||||||
|
"""
|
||||||
|
Canvas edge class
|
||||||
|
"""
|
||||||
|
|
||||||
|
width = 3
|
||||||
|
|
||||||
|
def __init__(self, x1, y1, x2, y2, src, canvas):
|
||||||
|
"""
|
||||||
|
Create an instance of canvas edge object
|
||||||
|
:param int x1: source x-coord
|
||||||
|
:param int y1: source y-coord
|
||||||
|
:param int x2: destination x-coord
|
||||||
|
:param int y2: destination y-coord
|
||||||
|
:param int src: source id
|
||||||
|
:param coretk.graph.graph.GraphCanvas canvas: canvas object
|
||||||
|
"""
|
||||||
|
self.src = src
|
||||||
|
self.dst = None
|
||||||
|
self.src_interface = None
|
||||||
|
self.dst_interface = None
|
||||||
|
self.canvas = canvas
|
||||||
|
self.id = self.canvas.create_line(
|
||||||
|
x1, y1, x2, y2, tags=tags.EDGE, width=self.width, fill="#ff0000"
|
||||||
|
)
|
||||||
|
self.text_src = None
|
||||||
|
self.text_dst = None
|
||||||
|
self.token = None
|
||||||
|
self.font = Font(size=8)
|
||||||
|
self.link = None
|
||||||
|
self.asymmetric_link = None
|
||||||
|
self.throughput = None
|
||||||
|
self.set_binding()
|
||||||
|
|
||||||
|
def set_binding(self):
|
||||||
|
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context)
|
||||||
|
|
||||||
|
def set_link(self, link):
|
||||||
|
self.link = link
|
||||||
|
self.draw_labels()
|
||||||
|
|
||||||
|
def get_coordinates(self):
|
||||||
|
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||||
|
v1 = x2 - x1
|
||||||
|
v2 = y2 - y1
|
||||||
|
ux = TEXT_DISTANCE * v1
|
||||||
|
uy = TEXT_DISTANCE * v2
|
||||||
|
x1 = x1 + ux
|
||||||
|
y1 = y1 + uy
|
||||||
|
x2 = x2 - ux
|
||||||
|
y2 = y2 - uy
|
||||||
|
return x1, y1, x2, y2
|
||||||
|
|
||||||
|
def draw_labels(self):
|
||||||
|
x1, y1, x2, y2 = self.get_coordinates()
|
||||||
|
label_one = None
|
||||||
|
if self.link.HasField("interface_one"):
|
||||||
|
label_one = (
|
||||||
|
f"{self.link.interface_one.ip4}/{self.link.interface_one.ip4mask}\n"
|
||||||
|
f"{self.link.interface_one.ip6}/{self.link.interface_one.ip6mask}\n"
|
||||||
|
)
|
||||||
|
label_two = None
|
||||||
|
if self.link.HasField("interface_two"):
|
||||||
|
label_two = (
|
||||||
|
f"{self.link.interface_two.ip4}/{self.link.interface_two.ip4mask}\n"
|
||||||
|
f"{self.link.interface_two.ip6}/{self.link.interface_two.ip6mask}\n"
|
||||||
|
)
|
||||||
|
self.text_src = self.canvas.create_text(
|
||||||
|
x1,
|
||||||
|
y1,
|
||||||
|
text=label_one,
|
||||||
|
justify=tk.CENTER,
|
||||||
|
font=self.font,
|
||||||
|
tags=tags.LINK_INFO,
|
||||||
|
)
|
||||||
|
self.text_dst = self.canvas.create_text(
|
||||||
|
x2,
|
||||||
|
y2,
|
||||||
|
text=label_two,
|
||||||
|
justify=tk.CENTER,
|
||||||
|
font=self.font,
|
||||||
|
tags=tags.LINK_INFO,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_labels(self):
|
||||||
|
"""
|
||||||
|
Move edge labels based on current position.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
x1, y1, x2, y2 = self.get_coordinates()
|
||||||
|
self.canvas.coords(self.text_src, x1, y1)
|
||||||
|
self.canvas.coords(self.text_dst, x2, y2)
|
||||||
|
|
||||||
|
def complete(self, dst):
|
||||||
|
self.dst = dst
|
||||||
|
self.token = tuple(sorted((self.src, self.dst)))
|
||||||
|
x, y = self.canvas.coords(self.dst)
|
||||||
|
x1, y1, _, _ = self.canvas.coords(self.id)
|
||||||
|
self.canvas.coords(self.id, x1, y1, x, y)
|
||||||
|
self.check_wireless()
|
||||||
|
self.canvas.tag_raise(self.src)
|
||||||
|
self.canvas.tag_raise(self.dst)
|
||||||
|
|
||||||
|
def check_wireless(self):
|
||||||
|
src_node = self.canvas.nodes[self.src]
|
||||||
|
dst_node = self.canvas.nodes[self.dst]
|
||||||
|
src_node_type = src_node.core_node.type
|
||||||
|
dst_node_type = dst_node.core_node.type
|
||||||
|
is_src_wireless = NodeUtils.is_wireless_node(src_node_type)
|
||||||
|
is_dst_wireless = NodeUtils.is_wireless_node(dst_node_type)
|
||||||
|
if is_src_wireless or is_dst_wireless:
|
||||||
|
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
|
||||||
|
self._check_antenna()
|
||||||
|
|
||||||
|
def _check_antenna(self):
|
||||||
|
src_node = self.canvas.nodes[self.src]
|
||||||
|
dst_node = self.canvas.nodes[self.dst]
|
||||||
|
src_node_type = src_node.core_node.type
|
||||||
|
dst_node_type = dst_node.core_node.type
|
||||||
|
is_src_wireless = NodeUtils.is_wireless_node(src_node_type)
|
||||||
|
is_dst_wireless = NodeUtils.is_wireless_node(dst_node_type)
|
||||||
|
if is_src_wireless or is_dst_wireless:
|
||||||
|
if is_src_wireless and not is_dst_wireless:
|
||||||
|
dst_node.add_antenna()
|
||||||
|
elif not is_src_wireless and is_dst_wireless:
|
||||||
|
src_node.add_antenna()
|
||||||
|
# TODO: remove this? dont allow linking wireless nodes?
|
||||||
|
else:
|
||||||
|
src_node.add_antenna()
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.canvas.delete(self.id)
|
||||||
|
if self.link:
|
||||||
|
self.canvas.delete(self.text_src)
|
||||||
|
self.canvas.delete(self.text_dst)
|
||||||
|
|
||||||
|
def create_context(self, event):
|
||||||
|
logging.debug("create link context")
|
||||||
|
context = tk.Menu(self.canvas)
|
||||||
|
themes.style_menu(context)
|
||||||
|
context.add_command(label="Configure", command=self.configure)
|
||||||
|
context.add_command(label="Delete")
|
||||||
|
context.add_command(label="Split")
|
||||||
|
context.add_command(label="Merge")
|
||||||
|
if self.canvas.app.core.is_runtime():
|
||||||
|
context.entryconfigure(1, state="disabled")
|
||||||
|
context.entryconfigure(2, state="disabled")
|
||||||
|
context.entryconfigure(3, state="disabled")
|
||||||
|
context.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
logging.debug("link configuration")
|
||||||
|
dialog = LinkConfiguration(self.canvas, self.canvas.app, self)
|
||||||
|
dialog.show()
|
18
coretk/coretk/graph/enums.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class GraphMode(enum.Enum):
|
||||||
|
SELECT = 0
|
||||||
|
EDGE = 1
|
||||||
|
PICKNODE = 2
|
||||||
|
NODE = 3
|
||||||
|
ANNOTATION = 4
|
||||||
|
OTHER = 5
|
||||||
|
|
||||||
|
|
||||||
|
class ScaleOption(enum.Enum):
|
||||||
|
NONE = 0
|
||||||
|
UPPER_LEFT = 1
|
||||||
|
CENTERED = 2
|
||||||
|
SCALED = 3
|
||||||
|
TILED = 4
|