Compare commits

..

143 commits

Author SHA1 Message Date
Blake J. Harnden
2a879ce0ba corefx - added throughput display toggle 2018-12-20 09:22:40 -08:00
Blake J. Harnden
c45a0af088 Merge branch 'rel/5.3' of git-ssh.web.boeing.com:Boeing-CORE/CORE into rel/5.3 2018-12-13 09:25:45 -08:00
Blake J. Harnden
5382ab2d30 corefx - initial work to add option to display throughputs received from core rest 2018-12-13 09:25:37 -08:00
bharnden
661295a4d3 Merge branch 'rel/5.3' of git-ssh.web.boeing.com:Boeing-CORE/CORE into rel/5.3 2018-12-12 17:31:06 -08:00
bharnden
4c260c288e core rest - added routes to parse throughput from /proc/net/dev and broadcast out over websocket 2018-12-12 17:30:55 -08:00
Blake J. Harnden
6bbc47d61a corefx - updated mobility scripts to provide dialogs for each script to control, which can be closed and opened back up, from a mobility node context menu 2018-12-10 15:02:13 -08:00
Blake J. Harnden
9b1bc3e444 corefx - modified base dialogs to allow modality configuration, updated terminal dialogs to not have modality and can be instanced per node 2018-11-30 16:24:21 -08:00
Blake J. Harnden
e37caedf61 corefx - updated terminal and session dialogs to leverage javafx tasks for running background work 2018-11-30 16:02:28 -08:00
Blake J. Harnden
3025287486 corefx - changed core client to use address and port, added pseudo terminal for remote connections 2018-11-30 15:12:30 -08:00
bharnden
080a03c8be Merge branch 'rel/5.3' of git-ssh.web.boeing.com:Boeing-CORE/CORE into rel/5.3 2018-11-25 12:15:00 -08:00
Blake J. Harnden
e1e4322a23 corefx - cleaned up sessions dialog and added a delete session button 2018-11-30 11:14:45 -08:00
Blake J. Harnden
4675f3a836 corefx - updated details panel css, changed how graph toolbar node/device selection works 2018-11-30 10:21:14 -08:00
Blake J. Harnden
467b1ea2a8 corefx - update title in ui thread 2018-11-30 08:18:22 -08:00
Blake J. Harnden
56f9e6ec8a corefx - update to clear right border pane when joining a session 2018-11-29 16:24:30 -08:00
Blake J. Harnden
9a2625e502 corefx - fixed issue with home directory creation due to log file, added configuration for node labels in preferences 2018-11-29 15:52:59 -08:00
Blake J. Harnden
fb71e55b0b corefx - updated node labels for easier visibility with backgrounds 2018-11-29 15:00:19 -08:00
Blake J. Harnden
55e56e992f corefx - quick change to display session id in title bar when joined 2018-11-29 13:53:02 -08:00
Blake J. Harnden
77e8006570 corefx - added session create to sessions dialog 2018-11-29 13:36:01 -08:00
Blake J. Harnden
6f0e22cc28 corefx - moved background configuration to session menu, added icon path configuration to preferences 2018-11-29 10:36:42 -08:00
Blake J. Harnden
7e93b607c4 corefx - adding logging to file within create home directory to help capture information for debugging issues 2018-11-29 10:27:22 -08:00
Blake J. Harnden
113bd3cb6c corefx - updated configuration dialogs for nodes to be disabled when running 2018-11-29 09:26:42 -08:00
bharnden
9b56b489fa core rest - added node command api, changed default rest address to the local machine to avoid external connections 2018-11-25 12:14:50 -08:00
bharnden
f9402f53d5 Merge branch 'rel/5.3' of git-ssh.web.boeing.com:Boeing-CORE/CORE into rel/5.3 2018-11-24 07:01:29 -08:00
Blake J. Harnden
0805552aea corefx - added start/stop/restart/validate service to running node context menu 2018-11-28 17:16:00 -08:00
Blake J. Harnden
4f5ac43a06 corefx - updated context menu logic to be contained within classes, fixed issue with service display in details panel for nodes with no default services 2018-11-28 11:46:13 -08:00
Blake J. Harnden
c419518559 corefx - update to display default services for nodes in details pane 2018-11-28 10:08:48 -08:00
Blake J. Harnden
f75f6f9ec0 corefx - removed edit button, set to edit mode on node/device button click instead 2018-11-27 13:58:56 -08:00
Blake J. Harnden
8423bae0af corefx - updated node types to use sets and have the controller maintain default service mapping for other components to leverage 2018-11-27 13:37:15 -08:00
Blake J. Harnden
7e2a79335c corefx - updates to allow creation and saving of custom node types, the gui controls the daemon, instead of pulling, also added icons directory 2018-11-27 12:05:21 -08:00
bharnden
04c2a30fbd core rest - added start/stop/restart/validate services endpoints 2018-11-24 07:01:23 -08:00
bharnden
57dc69b9da core rest - added set default services 2018-11-24 00:48:01 -08:00
bharnden
d522649489 Merge branch 'rel/5.3' of git-ssh.web.boeing.com:Boeing-CORE/CORE into rel/5.3 2018-11-22 00:14:09 -08:00
Blake J. Harnden
0378c66103 corefx - migrated to using a json based configuration file for more robust configuration information 2018-11-26 15:17:10 -08:00
Blake J. Harnden
eacb4d0cc0 corefx - updated node types dialog to display services for a given node type 2018-11-26 14:18:27 -08:00
Blake J. Harnden
61f80ffb9c corefx - consolidated menu bar 2018-11-26 10:54:34 -08:00
Blake J. Harnden
bd1edabb7c corefx - updated client interface to use non rest specific classes 2018-11-26 10:42:02 -08:00
Blake J. Harnden
1877d864dc corefx - clear mobility player on new connections 2018-11-26 09:34:06 -08:00
bharnden
2091e955b7 core rest - added default node type service query 2018-11-22 00:14:04 -08:00
bharnden
8af23ebfd9 core rest - consolidate config group logic and added endpoints for all mobility/emane configurations 2018-11-21 15:41:00 -08:00
Blake J. Harnden
9575ce08be corefx - moved start/stop session into controller, remove coupling of core client and the controller itself, added early way to sync up with a singular mobility script 2018-11-21 14:41:17 -08:00
Blake J. Harnden
2694ae3630 corefx - moved join session out into controller 2018-11-21 10:48:42 -08:00
Blake J. Harnden
5e33583dd4 moved initial join logic to controller to help avoid tying it into the core client 2018-11-21 09:59:51 -08:00
Blake J. Harnden
ac81b7c81a corefx - moved mobility player to bottom, added mobility configuration to emane nodes 2018-11-20 13:50:28 -08:00
Blake J. Harnden
bee94456e0 added mobility directory to user core directory and a configuration option to change it 2018-11-19 17:15:24 -08:00
Blake J. Harnden
12681b6382 updates to create a core directory in users home, added options in ui to allow changes for shell command and xml directory 2018-11-19 16:27:50 -08:00
Blake J. Harnden
8864169683 corefx - added jfx progress bar under the menu, make use of the progress bar for start/stop 2018-11-09 13:26:42 -08:00
Blake J. Harnden
1b728770f1 corefx - added connect dialog to allow connections after initial attempt 2018-11-09 10:08:17 -08:00
Blake J. Harnden
24157e7be5 corefx added FileConfigItem back in for dynamic config dialogs 2018-10-16 10:08:19 -07:00
bharnden
8d610c99e9 removed usage of bottle for handling exceptions for rest api 2018-10-16 09:42:43 -07:00
Blake J. Harnden
9b8f2868fd removed config from .gitignore, added files that were not added due to gitignore previously 2018-10-16 09:21:39 -07:00
Blake J. Harnden
c1f6e3711b gui - save xml only enabled when session running 2018-09-24 11:40:20 -07:00
Blake J. Harnden
a852a60fd4 (rest) - fixed editing link api (gui) - added link update in link details panel 2018-09-24 09:50:26 -07:00
Blake J. Harnden
d2b459e503 gui - some refactoring for creating dynamic config ui elements 2018-09-21 14:01:25 -07:00
Blake J. Harnden
0d4356ae55 gui - moved dialogs into their own package, cleaned up node movement check 2018-09-21 11:02:08 -07:00
Blake J. Harnden
e96c0b4758 gui - added test dialog proof of concept for using leaflet.js for geospatial display 2018-09-21 10:11:39 -07:00
Blake J. Harnden
b87dc6c6c1 gui - updated dynamic config dialogs to support file picking 2018-09-21 07:41:45 -07:00
Blake J. Harnden
1a8618ebde gui - added utility for loading fxml and cleaned up annoatuib toolbar 2018-09-20 14:20:45 -07:00
Blake J. Harnden
9830d63ff3 gui - updated single annotations on class variables to be on the same line 2018-09-20 13:47:16 -07:00
Blake J. Harnden
9f5495ba10 gui - initial support for emane configuration per node and interface 2018-09-20 13:06:03 -07:00
Blake J. Harnden
08922f267a gui - avoid updating node position that has not changed 2018-09-20 10:42:19 -07:00
Blake J. Harnden
f57e931082 rest - account for node update with only geo, that results in calculation the x,y 2018-09-20 10:38:59 -07:00
Blake J. Harnden
a42b29b563 rest - fixed initial location scale value 2018-09-20 10:11:51 -07:00
Blake J. Harnden
28f14a9b66 (gui) - added location/background (rest) - added location 2018-09-19 16:32:25 -07:00
Blake J. Harnden
b6cc2ad86e gui - refactored datavis package name 2018-09-19 11:18:44 -07:00
Blake J. Harnden
6e91aff04b gui - set wlan config, set a default one is not accounted for 2018-09-19 10:39:01 -07:00
Blake J. Harnden
6d885935b7 (gui) - refactored nodetypes to use an id for uniqueness, makes editing and accounting for changes to icons/names an easier process, (rest) - no longer return ctrlnet is session nodes 2018-09-19 09:29:53 -07:00
Blake J. Harnden
670ed96167 gui - initial icon edit support 2018-09-18 13:52:02 -07:00
Blake J. Harnden
c25003c693 updates to help defined classes to support displaying graph data when fleshed out 2018-09-18 09:39:01 -07:00
Blake J. Harnden
106d993b9d gui - set default emane model for emane nodes, slight update to radio icon to draw a circle for each linked wireless network 2018-09-17 14:37:20 -07:00
Blake J. Harnden
f062e2868d (rest) - added broadcast for links,configs,exceptions,files and fixed edit node (gui) - added handling of broadcast links, different rendering for wireless links, removal of wirelesss links on stop 2018-09-17 11:57:47 -07:00
Blake J. Harnden
e7a56cc3ad added test dialog displaying potential live chart data that can be displayed 2018-09-17 08:31:07 -07:00
Blake J. Harnden
fd559c1c4f gui - updated service dialog display based on feedback 2018-09-14 14:08:05 -07:00
Blake J. Harnden
09cdc24427 gui - keep node/device labels constant, based on feedback 2018-09-14 10:51:10 -07:00
Blake J. Harnden
c50dfdda85 rest - fixed socketio import 2018-09-14 10:46:47 -07:00
Blake J. Harnden
0d63630c99 javafx - moved node interface management out of nodes, rely on graph to obtain interfaces from links 2018-09-13 16:00:06 -07:00
Blake J. Harnden
c5f62a106f updated webutils to convert objects to json strings, avoid repeating the process for every use case 2018-09-13 13:30:45 -07:00
Blake J. Harnden
3dc9586817 updated set mobility to upload file to a upload directory and mark files used by set mobility config to look for the file within this directory 2018-09-13 13:10:41 -07:00
Blake J. Harnden
2815554487 initial basics for working with mobility scripts 2018-09-13 12:12:23 -07:00
Blake J. Harnden
1d73f28248 refactoring to rest app to help breakup api code into smaller files 2018-09-13 09:27:02 -07:00
Blake J. Harnden
2593d97cab refactoring to rest app, fixed removal of simple update state logic that was causing a loop for CoreClient 2018-09-12 21:32:40 -07:00
Blake J. Harnden
46730ce216 refactored core client to use an interface and defined the rest client to adhere to it for now, eventually may help make it easier to switch if needed 2018-09-12 16:49:55 -07:00
Blake J. Harnden
f2f83f247d changes to support mobility from rest and configuring mobility from a wlan context menu 2018-09-12 14:34:37 -07:00
Blake J. Harnden
dd9ad5644d added way to not display links for wlan/emane, added very basic way to denote a wireless connection for nodes with no visible links 2018-09-11 14:22:25 -07:00
Blake J. Harnden
895c3bd5cd moved dealing with configuration to its own file, support for naming a different file and command line overrides 2018-09-11 10:10:16 -07:00
Blake J. Harnden
bdd469d386 initial commit for core javafx gui 2018-09-11 09:52:14 -07:00
Blake J. Harnden
832f3e3ee5 Merge branch 'master' into core-rest-flask 2018-09-11 08:09:25 -07:00
Blake J. Harnden
d0c5a159d8 added rest endpoints to get session options 2018-09-07 13:05:54 -07:00
Blake J. Harnden
816ca0c12a Merge branch 'rel/5.2' into core-rest-flask 2018-09-07 11:56:51 -07:00
Blake J. Harnden
d2ebdb0468 added wlan config set/get to rest, added node terminal command for rest, added hook query to rest 2018-08-27 14:57:36 -07:00
Blake J. Harnden
359af03cfc added rest api to add a state hook script 2018-08-22 16:10:09 -07:00
Blake J. Harnden
348f208d4d added cleared exception logging for booting services and fixed rest issue with loading an xml file 2018-08-20 16:01:51 -07:00
Blake J. Harnden
eec7fbb213 updated rest service data returned and names used 2018-08-20 09:27:57 -07:00
Blake J. Harnden
1ddea3ed54 Merge branch 'rel/5.2' into core-rest-flask 2018-08-17 11:00:55 -07:00
Blake J. Harnden
fae6491fbc Merge branch 'rel/5.2' into core-rest-flask 2018-08-16 16:56:33 -07:00
Blake J. Harnden
3e02503c5b Merge branch 'rel/5.2' into core-rest-flask 2018-08-16 16:54:54 -07:00
Blake J. Harnden
60ceba876d updates to values sent to rest set_node_service 2018-08-16 16:54:44 -07:00
Blake J. Harnden
bf6353f168 Merge branch 'rel/5.2' into core-rest-flask 2018-08-14 12:32:23 -07:00
Blake J. Harnden
71e421d4e4 updates to json returned for api calls 2018-08-14 08:59:30 -07:00
Blake J. Harnden
c2c8899d2a Merge branch 'rel/5.2' into core-rest-flask 2018-08-07 10:51:57 -07:00
Blake J. Harnden
1a6d5d9605 Merge branch 'rel/5.2' into core-rest-flask 2018-08-06 16:39:25 -07:00
Blake J. Harnden
f4c2c5f5f8 now include links for session data, helps make a single request able to draw out a session 2018-08-06 14:07:26 -07:00
Blake J. Harnden
f053f11eb4 Merge branch 'rel/5.2' into core-rest-flask 2018-08-03 09:49:27 -07:00
Blake J. Harnden
3b5043ae8d Merge branch 'rel/5.2' into core-rest-flask 2018-07-27 16:31:51 -07:00
Blake J. Harnden
83902a2215 Merge branch 'rel/5.2' into core-rest-flask 2018-07-27 16:12:02 -07:00
Blake J. Harnden
38ddec1ed9 updated to work with changes for rel/5.2 2018-07-27 10:08:03 -07:00
Blake J. Harnden
b18d5b5805 merged latest from 5.2 2018-07-26 16:41:08 -07:00
Blake J. Harnden
3e5cd61ecc initial rough working service edit, with special way to retrieve custom values 2018-05-23 12:41:29 -07:00
Blake J. Harnden
517ef4c3d3 web app, initial edit interface fields within edit node 2018-05-18 14:58:38 -07:00
Blake J. Harnden
98e8e2d627 web app, fleshed out delete sessions, updated sessions dialog to use selection and buttons for joining/deletion 2018-05-18 12:39:45 -07:00
Blake J. Harnden
aa55daf2e8 web app, initial working emane model config 2018-05-18 08:31:10 -07:00
Blake J. Harnden
c90ee5fc84 web app, updated emane config api to be a general config api 2018-05-18 08:20:04 -07:00
Blake J. Harnden
a55cd6a524 web app, initial working emane option configuration 2018-05-17 20:38:32 -07:00
Blake J. Harnden
ddfa0ddfa4 web app, get emane models for sessions joined and default emane nodes to the first model 2018-05-16 16:09:57 -07:00
Blake J. Harnden
8889d121c0 web app, added emane node, got basic emane networks working and joining existing emane network 2018-05-16 15:45:46 -07:00
Blake J. Harnden
b15b838555 web app, further cleanup to javascript ui 2018-05-16 08:47:41 -07:00
Blake J. Harnden
c989d809cb web app updated node context to use async 2018-05-15 16:40:41 -07:00
Blake J. Harnden
e11798bae5 web app refactored service modal code and added proper support for sending current node services 2018-05-15 16:28:41 -07:00
Blake J. Harnden
d4c05dab09 web app initial work towards configuring services, only shows an empty modal for now 2018-05-15 16:05:18 -07:00
Blake J. Harnden
053c9789bc web app updates to support save/open session xml files 2018-05-15 13:35:38 -07:00
Blake J. Harnden
979dc05a9f web app added toast library for notificatios 2018-05-15 12:09:09 -07:00
Blake J. Harnden
788712d12c web app, added logic to hide info panel on remove events for edges/nodes 2018-05-15 10:22:26 -07:00
Blake J. Harnden
c4d8dcbdf5 web app added initial basic linking to wlan nodes 2018-05-15 10:11:55 -07:00
Blake J. Harnden
014dea2dd0 web app update to attempt to re-use deleted node ids 2018-05-15 08:46:53 -07:00
Blake J. Harnden
f004d20b79 web app updates for deleting a node and disabling node context options 2018-05-14 14:33:17 -07:00
Blake J. Harnden
8c31b75c39 web app refactoring javascript into class based components representing ui elements 2018-05-14 11:23:52 -07:00
Blake J. Harnden
d9db4a427a web app implemented ui to display and configure services for a node 2018-05-14 09:28:25 -07:00
Blake J. Harnden
8347debda9 implemented node renaming within webapp, before starting, like old gui 2018-05-11 10:32:45 -07:00
Blake J. Harnden
f9200db939 cleanup and support for editing links within the web app and rest api 2018-05-11 10:23:06 -07:00
Blake J. Harnden
5f6f718e92 web app update to use a link object, that should help with editing and retaining values 2018-05-10 08:30:52 -07:00
Blake J. Harnden
10486dfe1a web app added node position updates 2018-05-09 17:12:01 -07:00
Blake J. Harnden
0ee3fca97c initial work to provide context menus for nodes/edges and edit modals 2018-05-09 16:22:15 -07:00
Blake J. Harnden
b1b05a7eaa web app can create and join sessions, updated node info panel and edge info panel 2018-05-09 13:38:46 -07:00
Blake J. Harnden
cd949340ac use icons for showing which node can be created, removed all edge labeling 2018-05-09 12:19:24 -07:00
Blake J. Harnden
2c353e787c updates to show link details on click and consolidate link data creation 2018-05-08 12:02:29 -07:00
Blake J. Harnden
f588757159 added some checks to enable/disable node creation based on start/stop state 2018-05-08 09:40:05 -07:00
Blake J. Harnden
915d65cc8e refactored some of the node helper js code to consolidate it into one class 2018-05-08 09:10:07 -07:00
Blake J. Harnden
bf05fe0b9b updates to account for links to network nodes and recreating them, ignoring recreated edges 2018-05-07 16:08:38 -07:00
Blake J. Harnden
8e99af96a4 first initial working link for ptp and joining back and drawing the edge, also making use of old core icons to provide a basic look and feel, updated coloring to dark mode instead of info, seems to fit better 2018-05-07 15:04:54 -07:00
Blake J. Harnden
b10c7fe502 initial basic mockup on gui layout before integrating with api calls 2018-05-03 20:28:00 -07:00
Blake J. Harnden
aaa125a896 working after merging latest from rel/5.1 2018-05-02 09:14:55 -07:00
Blake J. Harnden
1ea9de34db Merge branch 'rel/5.1' into core-rest-flask 2018-05-02 09:06:53 -07:00
Blake J. Harnden
9b90ee8917 Merge branch 'core-future' into core-rest-flask 2018-04-30 16:28:42 -07:00
Blake J. Harnden
7c6c5edf46 initial commit for a take on having a restful core api, along with an example scripts leveraging the available API calls 2018-04-30 16:20:20 -07:00
915 changed files with 92160 additions and 56437 deletions

View file

@ -11,7 +11,6 @@ insert_final_newline = true
[*.py] [*.py]
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
max_line_length = 88
[*.am] [*.am]
indent_style = tab indent_style = tab

View file

@ -1,32 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Ubuntu 18.04]
- CORE Version [e.g. 5.2.1]
- EMANE Version [e.g. 1.2.4]
**Additional context**
Add any other context about the problem here.

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,41 +0,0 @@
name: Daemon Checks
on: [push]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: install poetry
run: |
python -m pip install --upgrade pip
pip install poetry
cd daemon
cp core/constants.py.in core/constants.py
sed -i 's/required=True/required=False/g' core/emulator/coreemu.py
poetry install
- name: isort
run: |
cd daemon
poetry run isort -c -df
- name: black
run: |
cd daemon
poetry run black --check .
- name: flake8
run: |
cd daemon
poetry run flake8
- name: grpc
run: |
cd daemon/proto
poetry run python -m grpc_tools.protoc -I . --python_out=.. --grpc_python_out=.. core/api/grpc/*.proto
- name: test
run: |
cd daemon
poetry run pytest --mock tests

View file

@ -1,21 +0,0 @@
name: documentation
on:
push:
branches:
- master
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- uses: actions/cache@v2
with:
key: ${{ github.ref }}
path: .cache
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force

32
.gitignore vendored
View file

@ -14,22 +14,14 @@ config.h.in
config.log config.log
config.status config.status
configure configure
configure~
debian debian
stamp-h1 stamp-h1
# python virtual environments # python
venv
# generated protobuf files
*_pb2.py
*_pb2_grpc.py
# python build directory
dist dist
*.egg-info
# vscode .cache
.vscode *.pyc
# intellij # intellij
*.iml *.iml
@ -42,26 +34,14 @@ dist
coverage.xml coverage.xml
# python files # python files
*.egg-info
*.pyc
# ignore package files # ignore package files
*.rpm *.rpm
*.deb *.deb
*.tar.gz *.tar.gz
# pytest cache files
.cache
# ignore swap files # ignore swap files
*.swp *.swp
# ignore built input files # java files
netns/setup.py target
daemon/setup.py
# python
__pycache__
# ignore core player files
*.core

View file

@ -1,977 +0,0 @@
## 2023-08-01 CORE 9.0.3
* Installation
* updated various dependencies
* Documentation
* improved GUI docs to include node interaction and note xhost usage
* \#780 - fixed gRPC examples
* \#787 - complete documentation revamp to leverage mkdocs material
* \#790 - fixed custom emane model example
* core-daemon
* update type hinting to avoid deprecated imports
* updated commands ran within docker based nodes to have proper environment variables
* fixed issue improperly setting session options over gRPC
* \#668 - add fedora sbin path to frr service
* \#774 - fixed pcap configservice
* \#805 - fixed radvd configservice template error
* core-gui
* update type hinting to avoid deprecated imports
* fixed issue allowing duplicate named hook scripts
* fixed issue joining sessions with RJ45 nodes
* utility scripts
* fixed issue in core-cleanup for removing devices
## 2023-03-02 CORE 9.0.2
* Installation
* updated python dependencies, including invoke to resolve python 3.10+ issues
* improved example dockerfiles to use less space for built images
* Documentation
* updated emane install instructions
* added Docker related issues to install instructions
* core-daemon
* fixed issue using invalid device name in sysctl commands
* updated PTP nodes to properly disable mac learning for their linux bridge
* fixed issue for LXC nodes to properly use a configured image name and write it to XML
* \#742 - fixed issue with bad wlan node id being used
* \#744 - fixed issue not properly setting broadcast address
* core-gui
* fixed sample1.xml to remove SSH service
* fixed emane demo examples
* fixed issue displaying emane configs generally configured for a node
## 2022-11-28 CORE 9.0.1
* Installation
* updated protobuf and grpcio-tools versions in pyproject.toml to account for bad version mix
## 2022-11-18 CORE 9.0.0
* Breaking Changes
* removed session nodes file
* removed session state file
* emane now runs in one process per nem with unique control ports
* grpc client has been refactored and updated
* removed tcl/legacy gui, imn file support and the tlv api
* link configuration is now different, but consistent, for wired links
* Installation
* added packaging for single file distribution
* python3.9 is now the minimum required version
* updated Dockerfile examples
* updated various python dependencies
* virtual environment is now installed to /opt/core/venv
* Documentation
* updated emane invoke task examples
* revamped install documentation
* added wireless node notes
* core-gui
* updated config services to display rendered templated and allow editing
* fixed node icon issue when updating preferences
* \#89 - throughput widget now works for hubs/switches
* \#691 - fixed custom nodes to properly use config services
* gRPC API
* add linked call to support linking and unlinking interfaces without destroying them
* fixed issue during start session clearing out session options
* added call to get rendered config service files
* removed get_node_links from links from client
* nem id and nem port have been added to GetNode and AddLink calls
* core-daemon
* wired links always create two veth pairs joined by a bridge
* node interfaces are now configured within the container to apply to outgoing traffic
* session.add_node now uses NodeOptions, allowing for node specific options
* fixed issue with xml reading node canvas values
* removed Session.add_node_file
* fixed get requirements logic
* fixed docker/lxd node support terminal commands on remote servers
* improved docker node command execution time using nsenter
* new wireless node type added to support dynamic loss based on distance
* \#513 - add and deleting distributed links during runtime is now supported
* \#703 - fixed issue not starting emane event listening service
## 2022-03-21 CORE 8.2.0
* core-gui
* improved failed starts to trigger runtime to allow node investigation
* core-daemon
* improved default service loading to use a full import path
* updated session instantiation to always set to a runtime state
* core-cli
* \#672 - fixed xml loading
* \#578 - restored json flag and added geo output to session overview
* Documentation
* updated emane example and documentation
* improved table markdown
## 2022-02-18 CORE 8.1.0
* Installation
* updated dependency versions to account for known vulnerabilities
* GUI
* fixed issue drawing asymmetric link configurations when joining a session
* daemon
* fixed issue getting templates and creating files for config services
* added by directional support for network to network links
* \#647 - fixed issue when creating RJ45 nodes
* \#646 - fixed issue when creating files for Docker nodes
* \#645 - improved wlan change updates to account for all updates with no delay
* services
* fixed file generation for OSPFv2 config service
## 2022-01-12 CORE 8.0.0
*Breaking Changes
* heavily refactored gRPC client, removing some calls, adding others, all using type hinted classes representing their protobuf counterparts
* emane adjustments to run each nem in its own process, includes adjustments to configuration, which may cause issues
* internal daemon cleanup and refactoring, in a script directly driving a scenario is used
* Installation
* added options to allow installation without ospf mdr
* removed tasks that are no longer needed
* updates to properly install/remove example files
* pipx/poetry/invoke versions are now locked to help avoid update related issues
* install.sh is now setup.sh and is a convenience to get tool setup to run invoke
* Documentation
* formally added notes for Docker and LXD based node types
* added config services
* Updated README to have quick notes for installation
* \#563 - update to note how to enable core service
* Examples
* \#598 - update to fix sample1.imn to working order
* core-daemon
* emane global configuration is now configurable per nem
* fixed wlan loss to support float values
* improved default service loading to use full core path
* improved emane model loading to occur one time
* fixed handling rj45 link edits from tlv api
* fixed wlan config getting a default value for the promiscuous setting when not provided
* ebtables usage has now been replaced with nftables
* \#564 - logging is now using module named loggers
* \#573 - emane processes are not created 1 to 1 with nems
* \#608 - update lxml version
* \#609 - update pyyaml version
* \#623 - fixed issue with ovs mode and mac learning
* core-gui
* config services are now the default service type
* legacy services are marked as deprecated
* fix to properly load session options
* logging is now using module named loggers
* save as will not update the current session file name as expected
* fix to properly clear out removed customized services
* adding directories to a service that do not exist, is now valid
* added flag to exit after creating gui directory from command line
* added new options to enable/disable ip4/ip6 assignment
* improved canvas draw order, when joining sessions
* improved node copy/paste to avoid issues when pasting text into service config dialogs
* each canvas will not correctly save and load their size from xml
* gRPC API
* session options are now returned for GetSession
* fixed issue not properly creating the session directory during start session definition state
* updates to separate editing a node and moving a node, new MoveNode call added, EditNode is now used for editing icons
* Services
* fixed default route config service
* config services now have options for shadowing directories, including per node customization
## 2021-09-17 CORE 7.5.2
* Installation
* \#596 - fixes issue related to installing poetry by pinning version to 1.1.7
* updates pipx installation to pinned version 0.16.4
* core-daemon
* \#600 - fixes known vulnerability for pillow dependency by updating version
## 2021-04-15 CORE 7.5.1
* core-pygui
* fixed issues creating and drawing custom nodes
## 2021-03-11 CORE 7.5.0
* core-daemon
* fixed issue setting mobility loop value properly
* fixed issue that some states would not properly remove session directories
* \#560 - fixed issues with sdt integration for mobility movement and layer creation
* core-pygui
* added multiple canvas support
* added support to hide nodes and restore them visually
* update to assign full netmasks to wireless connected nodes by default
* update to display services and action controls for nodes during runtime
* fixed issues with custom nodes
* fixed issue auto assigning macs, avoiding duplication
* fixed issue joining session with different netmasks
* fixed issues when deleting a session from the sessions dialog
* \#550 - fixed issue not sending all service customization data
* core-cli
* added delete session command
## 2021-01-11 CORE 7.4.0
* Installation
* fixed issue for automated install assuming ID_LIKE is always present in /etc/os-release
* gRPC API
* fixed issue stopping session and not properly going to data collect state
* fixed issue to have start session properly create a directory before configuration state
* core-pygui
* fixed issue handling deletion of wired link to a switch
* avoid saving edge metadata to xml when values are default
* fixed issue editing node mac addresses
* added support for configuring interface names
* fixed issue with potential node names to allow hyphens and remove under bars
* \#531 - fixed issue changing distributed nodes back to local
* core-daemon
* fixed issue to properly handle deleting links from a network to network node
* updated xml to support writing and reading link buffer configurations
* reverted change and removed mac learning from wlan, due to promiscuous like behavior
* fixed issue creating control interfaces when starting services
* fixed deadlock issue when clearing a session using sdt
* \#116 - fixed issue for wlans handling multiple mobility scripts at once
* \#539 - fixed issue in udp tlv api
## 2020-12-02 CORE 7.3.0
* core-daemon
* fixed issue where emane global configuration was not being sent to core-gui
* updated controlnet names on host to be prefixed with ctrl
* fixed RJ45 link shutdown from core-gui causing an error
* fixed emane external transport xml generation
* \#517 - update to account for radvd required directory
* \#514 - support added for session specific environment files
* \#529 - updated to configure netem limit based on delay or user specified, requires kernel 3.3+
* core-pygui
* fixed issue drawing wlan/emane link options when it should not have
* edge labels are now placed a set distance from nodes like original gui
* link color/width are now saved to xml files
* added support to configure buffer size for links
* \#525 - added support for multiple wired links between the same nodes
* \#526 - added option to hide/show links with 100% loss
* Documentation
* \#527 - typo in service documentation
* \#515 - added examples to docs for using EMANE features within a CORE context
## 2020-09-29 CORE 7.2.1
* core-daemon
* fixed issue where shutting down sessions may not have removed session directories
* fixed issue with multiple emane interfaces on the same node not getting the right configuration
* Installation
* updated automated install to be a bit more robust for alternative distros
* added force install type to try and leverage a redhat/debian like install
* locked ospf mdr version installed to older commit to avoid issues with multiple interfaces on same node
## 2020-09-15 CORE 7.2.0
* Installation
* locked down version of ospf-mdr installed in automated install
* locked down version of emane to v1.2.5 in automated emane install
* added option to install locally using the -l option
* core-daemon
* improve error when retrieving services that do not exist, or failed to load
* fixed issue with writing/reading emane node interface configurations to xml
* fixed issue with not setting the emane model when creating a node
* added common utility method for getting a emane node interface config id in core.utils
* fixed issue running emane on more than one interface for a node
* fixed issue validating paths when creating emane transport xml for a node
* fixed issue avoiding multiple calls to shutdown, if already in shutdown state
* core-pygui
* fixed issue configuring emane for a node interface
* gRPC API
* added wrapper client that can provide type hinting and a simpler interface at core.api.grpc.clientw
* fixed issue creating sessions that default to having a very large reference scale
* fixed issue with GetSession returning control net nodes
## 2020-08-21 CORE 7.1.0
* Installation
* added core-python script that gets installed to help globally reference the virtual environment
* gRPC API
* GetSession will now return all configuration information for a session and the file it was opened from, if applicable
* node update events will now include icon information
* fixed issue with getting session throughputs for sessions with a high id
* core-daemon
* \#503 - EMANE networks will now work with mobility again
* \#506 - fixed service dependency resolution issue
* fixed issue sending hooks to core-gui when joining session
* core-pygui
* fixed issues editing hooks
* fixed issue with cpu usage when joining a session
* fixed mac field not being disabled during runtime when configuring a node
* removed unlimited button from link config dialog
* fixed issue with copy/paste links and their options
* fixed issue with adding nodes/links and editing links during runtime
* updated open file dialog in config dialogs to open to ~/.coregui home directory
* fixed issue double clicking sessions dialog in invalid areas
* added display of asymmetric link options on links
* fixed emane config dialog display
* fixed issue saving backgrounds in xml files
* added view toggle for wired/wireless links
* node events will now update icons
## 2020-07-28 CORE 7.0.1
* Bugfixes
* \#500 - fixed issue running node commands with shell=True
* fixed issue for poetry based install not properly vetting requirements for dataclasses dependency
## 2020-07-23 CORE 7.0.0
* Breaking Changes
* core.emudata and core.data combined and cleaned up into core.data
* updates to consistently use mac instead of hwaddr/mac
* \#468 - code related to adding/editing/deleting links cleaned up
* \#469 - usages of per all changed to loss to be consistent
* \#470 - variables with numbered names now use numbers directly
* \#471 - node startup is no longer embedded within its constructor
* \#472 - code updated to refer to interfaces consistently as iface
* \#475 - code updates changing how ip addresses are stored on interfaces
* \#476 - executables to check for moved into own module core.executables
* \#486 - core will now install into its own python virtual environment managed by poetry
* core-daemon
* updates to properly save/load distributed servers to xml
* \#474 - added type hinting to all service files
* \#478 - fixed typo in config service directory
* \#479 - opening an xml file will now cycle through states like a normal session
* \#480 - ovs configuration will now save/load from xml and display in guis
* \#484 - changes to support adding emane links during runtime
* core-pygui
* fixed issue not displaying services for the default group in service dialogs
* fixed issue starting a session when the daemon is not present
* fixed issue attempting to open terminals for invalid nodes
* fixed issue syncing session location
* fixed issue joining a session with mobility, not in runtime
* added cpu usage monitor to status bar
* emane configurations can now be seen during runtime
* rj45 nodes can only have one link
* disabling throughputs will clear labels
* improvements to custom service copy
* link options will now be drawn on as a label
* updates to handle runtime link events
* \#477 - added optional details pane for a quick view of node/link details
* \#485 - pygui fixed observer widget for invalid nodes
* \#496 - improved alert handling
* core-gui
* \#493 - increased frame size to show all emane configuration options
* gRPC API
* added set session user rpc
* added cpu usage stream
* interface objects returned from get_node will now provide node_id, net_id, and net2_id data
* peer to peer nodes will not be included in get_session calls
* pathloss events will now throw an error when nem id not found
* \#481 - link rpc calls will broadcast out
* \#496 - added alert rpc call
* Services
* fixed issue reading files in security services
* \#494 - add staticd to daemons list for frr services
## 2020-06-11 CORE 6.5.0
* Breaking Changes
* CoreNode.newnetif - both parameters are required and now takes an InterfaceData object as its second parameter
* CoreNetworkBase.linkconfig - now takes a LinkOptions parameter instead of a subset of some of the options (ie bandwidth, delay, etc)
* \#453 - Session.add_node and Session.get_node now requires the node class you expect to create/retrieve
* \#458 - rj45 cleanup to only inherit from one class
* Enhancements
* fixed issues with handling bad commands for TLV execute messages
* removed unused boot.sh from CoreNode types
* added linkconfig to CoreNetworkBase and cleaned up function signature
* emane position hook now saves geo position to node
* emane pathloss support
* core.emulator.emudata leveraged dataclass and type hinting
* \#459 - updated transport type usage to an enum
* \#460 - updated network policy type usage to an enum
* Python GUI Enhancements
* fixed throughput events do not work for joined sessions
* fixed exiting app with a toolbar picker showing
* fixed issue with creating interfaces and reusing subnets after deletion
* fixed issue with moving text shapes
* fixed scaling with custom node selected
* fixed toolbar state switching issues
* enable/disable toolbar when running stop/start
* marker config integrated into toolbar
* improved color picker layout
* shapes can now be moved while drawing shapes
* added observers to toolbar in run mode
* gRPC API
* node events will now have geo positional data
* node geo data is now returned in get_session and get_node calls
* \#451 - added wlan link api to allow direct linking/unlinking of wireless links between nodes
* \#462 - added streaming call for sending node position/geo changes
* \#463 - added streaming call for emane pathloss events
* Bugfixes
* \#454 - fixed issue creating docker nodes, but containers are now required to have networking tools
* \#466 - fixed issue in python gui when xml file is loading nodes with no ip4 addresses
## 2020-05-11 CORE 6.4.0
* Enhancements
* updates to core-route-monitor, allow specific session, configurable settings, and properly
listen on all interfaces
* install.sh now has a "-r" option to help with reinstalling from current branch and installing
current python dependencies
* \#202 - enable OSPFv2 fast convergence
* \#178 - added comments to OVS service
* Python GUI Enhancements
* added initial documentation to help support usage
* supports drawing multiple links for wireless connections
* supports differentiating wireless networks with different colored links
* implemented unlink in node context menu to delete links to other nodes
* implemented node run tool dialog
* implemented find node dialog
* implemented address configuration dialog
* implemented mac configuration dialog
* updated link address creation to more closely mimic prior behavior
* updated configuration to use yaml class based configs
* implemented auto grid layout for nodes
* fixed drawn wlan ranges during configuration
* Bugfixes
* no longer writes link option data for WLAN/EMANE links in XML
* avoid configuring links for WLAN/EMANE link options in XML, due to them being written to XML prior
* updates to allow building python docs again
* \#431 - peer to peer node uplink link data was not using an enum properly due to code changes
* \#432 - loading XML was not setting EMANE nodes model
* \#435 - loading XML was not maintaining existing session options
* \#448 - fixed issue sorting hooks being saved to XML
## 2020-04-13 CORE 6.3.0
* Features
* \#424 - added FRR IS-IS service
* Enhancements
* \#414 - update GUI OSPFv2 adjacency widget to work with FRR
* \#416 - EMANE links can now be drawn for 80211 and RF Pipe models
* \#418 #409 - code cleanup
* \#425 - added route monitor script for SDT3D integration
* a formal error will now be thrown when EMANE binding are not installed, but attempted to be used
* node positions will now default to 0,0 to avoid GUI errors, when one is not provided
* improved SDT3D integration, multiple link support and usage of custom layers
* Python GUI Enhancements
* enabled edit menu delete
* cleaned up node context menu and enabled delete
* Bugfixes
* \#427 - fixed issue in default route service
* \#426 - fixed issue reading ipsec template file
* \#420 - fixed issue with TLV API udp handler
* \#411 - allow wlan to be configured with 0 values
* \#415 - general EMANE configuration was not being saved/loaded from XML
## 2020-03-16 CORE 6.2.0
* gRPC API
* Added call to execute python script
* Enhancements
* \#371 - improved coretk gui scaling
* \#374 - display range visually for wlan in coretk gui, when configuring
* \#377 - improved coretk error dialogs
* \#379 - fixed issues with core converting between x,y and lon,lat for values that would cross utm zones
* \#384 - sdt integration moved internally to core code allowing it to work for coretk gui as well
* \#387 - coretk gui will now auto detect potential valid terminal and command to use for interacting with nodes during runtime
* \#389 - coretk gui will now attempt to reconnect to daemon without need to restart
* \#395 - coretk gui now has "save" and "save as" menu options
* \#402 - coretk will now allow terminal preference to be directly edited
* Bugfixes
* \#375 - fixed issues with emane event monitor handling data
* \#381 - executing a python script will now wait until completion before looking to join a new session
* \#391 - fixed configuring node ip addresses in coretk gui
* \#392 - fixed coretk link display when addresses are cleared out
* \#393 - coretk gui will properly clear marker annotations when switching sessions
* \#396 - Docker and LXC nodes will now properly save to XML
* \#406- WLAN bridge initialization was not ran when all nodes are disconnected
## 2020-02-20 CORE 6.1.0
* New
* config services - these services leverage a proper template engine and have configurable parameters, given enough time may replace existing services
* core-imn-to-xml - IMN to XML utility script
* replaced internal code for determining ip/mac address with netaddr library
* Enhancements
* added distributed package for built packages
* made use of python type hinting for functions and their return values
* updated Quagga zebra service to remove deprecated warning
* Removed
* removed stale ns3 code
* CORETK GUI
* added logging
* improved error dialog
* properly use global ipv6 addresses for nodes
* disable proxy usage by default, flag available to enable
* gRPC API
* add_link - now returns created interface information
* set_node_service - can now set files and directories to properly replicate previous usage
* get_emane_event_channel - return information related to the currently used emane event channel
* Bugfixes
* fixed session SDT functionality back to working order, due to python3 changes
* avoid shutting down services for nodes that are not up
* EMANE bypass model options will now display properly in GUIs
* XML scenarios will now properly read in custom node icons
* \#372 - fixed mobility waypoint comparisons
* \#370 - fixed radvd service
* \#368 - updated frr services to properly start staticd when needed
* \#358 - fixed systemd service install path
* \#350 - fixed frr babel wireless configuration
* \#354 - updated frr to reset interfaces to properly take configurations
## 2020-01-01 CORE 6.0.0
* New
* beta release of the python based tk GUI, use **coretk-gui** to try it out, plan will be to eventually sunset the old GUI once this is good enough
* this GUI will allow us to provide enhancements and a consistent python dev environment for developers
* Major Changes
* python3.6+ support only, due to python2 EOL https://pyfound.blogspot.com/2019/12/python-2-sunset.html
* distributed sessions now leverages the fabric library for sending remote SSH commands
* Enhancements
* changed usage of bridge-utils to using ip based bridge commands due to deprecation
* installation.sh script to help automate a standard make install or dev install
* when sessions are created without an id they will now always start from 1 and return the next unused id
* gRPC is now running by default
* Session API
* removed **create_emane_network** and **create_wlan_network** to help force using **add_node** for all cases
* removed **session.master** as it was only used for previous distributed sessions
* updated **add_node** to allow providing a custom class for node creation
* gRPC API
* added get all services configurations
* added get all wlan configurations
* added start/stop session calls, provides more freedom for startup and shutdown logic
* session events now have a session id to help differentiate which session they are coming from
* throughput events now require a session id and responses include session id for differentiating data
* session events can now be subscribed to with a subset of events or all
* emane model config data now include interface ids properly
* sessions returned from get sessions call may include file names when created from xml
* when opening an xml the session can now be started or not
* edit node will now broadcast the edit for others to listen to
* all config responses will now be in the form of a mapped value of key to ConfigOption, or a list of these when retrieving all, sometimes the config response may be wrapped in a different message to include other metadata
* Bugfixes
* \#311 - initialize ebtables chains for wlan networks only
* \#312 - removed sudo from init script
* \#313 - check if interface exists before flushing, previously would log an exception that didn't matter
* \#314 - node locations stored as floats instead of ints to avoid mobility calculations due to loss of precision
* \#321 - python installation path will be based on distr ibution/python building it
* emane options xml parsing didn't properly take into account the **emane_prefix** configuration
* updates services that checked for ipv4/ipv6 addresses to not fail for valid ipv6 addresses with a decimal
* Documentation
* updated NRL links to new GitHub locations
* updates for distributed session
* updates to dev guide
* updates to examples LXD/Docker setup
* updates to FRR service documentation
* gRPC get node service file will not throw an exception when node doesn't exist
## 2019-10-12 CORE 5.5.2
* gRPC
* Added emane_link API for linking/unlinking EMANE nodes within the GUI
* Bugfixes
* Fixed python3 issues when configuring WLAN nodes
* Fixed issue due to refactoring when running distributed
* Fixed issue when running python script from GUI
## 2019-10-09 CORE 5.5.1
* Bugfix
* Fixed issue with 5.5.0 refactoring causing issues in python2.
* Fixed python3 issues with NRL services
## 2019-10-03 CORE 5.5.0
* Documentation
* updated dependencies for building OSPF MDR on installation page
* added python/pip instruction on installation page
* added ethtool dependency for CORE
* GUI
* removed experimental OVS node to avoid confusion and issues related to using it
* Daemon
* fixed core-daemon --ovs flag back to working order for running CORE using OVS bridges instead of Linux bridges
* updated requirements.txt to refer to configparser 4.0.2, due to 4.0.1 removal by developers
* update to fail fast for dependent executables that are not found within PATH
* update to not load services that fail during service.on_load and move on
* Build
* fixed issue with configure script when using option flags
* python install path will use the native install path for AM_PATH_PYTHON, instead of coercing to python3
* Issues
* \#271 - OVS node error in GUI
* \#291 - configparser 4.0.1 issue
* \#290 - python3 path issue when building
## 2019-09-23 CORE 5.4.0
* Documentation
* Updates to documentation dev guide
* Improvements
* Added support for Pipenv for development
* Added configuration to leverage pre-commit during development
* Added configuration to leverage isort, black, and flake8 during development
* Added Github Actions to help verify pull requests in the same way as pre-commit
* Issues
* \#279 - WLAN configuration does not get set by default
* \#272 - error installing python package futures==3.2.0
* Pull Requests
* \#275 - Disable MAC learning on WLAN
* \#281 - Bumped jackson version on corefx
## 2019-07-05 CORE 5.3.1
* Documentation
* Updates to provide more information regarding several of the included services
* Issues
* \#252 - fixed changing wlan configurations during runtime
* \#256 - fixed mobility waypoint comparison for python3
* \#174 - turn tx/rx checksums off by default as they will never be valid for virtual interfaces
* \#259 - fixes for distributed EMANE
* \#260 - fixed issue with how execfile was being used due to it not existing within python3
## 2019-06-10 CORE 5.3.0
* Enhancements
* python 2 / 3 support
* added new API using [gRPC](https://grpc.io/)
* --grpc --grpc-port --grpc-address flags added to core-daemon
* core.api.grpc.client.CoreGrpcClient, provides a convenience wrapper for leveraging the API
* Docs
* Updates to installation instructions for latest changes
* Services
* Added FRR service
* EMANE
* Added EMANE prefix configuration when looking for emane model manifest files
* requires configuring **emane_prefix** in /etc/core/core.conf
* Cleanup
* Refactoring of the core python package structure, trying to help provide better organization and
logical groupings
* Issues
* \#246 - Fixed network to network link handling when reading xml files
* \#236 - Fixed storing/reading of link configuration values within xml files
* \#170 - FRR Service
* \#155 - EMANE path configuration
* \#233 - Python 3 support
* \#245 - Fixed bidirectional link configurations when reading from xml files
* \#208 - gRPC API
* Fixed link configuration dup handling when loaded from xml files
## 2019-06-07 CORE 5.2.2
* Enhancements:
* adds back in core-daemon udp support for coresendmsg, people may have depended on previously for certain scenarios
* Bug Fixes:
* fixes issue in GUI that would prevent moving nodes during mobility scenarios
## 2019-03-25 CORE 5.2.1
* Packaging:
* documentation no longer builds by default, must use configure flag
* added configure flag to allow only building vcmd
* sphinx will no long be required when not building documentation
* Services:
* Added source NAT service
* Fixed DHCP service for Ubuntu 18.04
* BUGFIXES:
* \#188 - properly remove session on delete TLV API call
* \#192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible
* \#193 - updates to service validation, will retry on failure and better exception logging
* \#195 - TLV link message data fix
* \#196 - fix to avoid clearing out default services
* \#197 - removed wireless_link_all API from EmuSession
* \#216 - updated default WLAN bandwidth to 54Mbps
* \#223 - fix to saving RJ45 to session XML files
## 2018-05-22 CORE 5.1
* DAEMON:
* removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB
specific files)
* default nodes are now set in the node map
* moved ns3 and netns directories to the top of the repo
* changes to make use of fpm as the tool for building packages
* removed usage of logzero to avoid dependency issues for built packages
* removed daemon addons directory
* added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting
and creating new external APIs out of
* cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and
informative
* EMANE 1.0.1-1.21 supported
* updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files
* example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py
* EMANE TDMA model now supports an option to start a TDMA schedule when running
* fixed issues with coresendmsg script due to code refactoring
* added make target for generating documentation "make doc"
* Python 2.7+ is now required
* ns3 is no longer bundled by default, but will be produced as a separate package for installation
* GUI:
* updated broken help links in GUI Help->About
* Packaging:
* fixed PYTHON_PATH to PYTHONPATH in sysv script
* added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation
within README.md to try it out
* TEST:
* fixed some broken tests
* new test cases based on CoreEmu usage
* BUGFIXES:
* \#142 - duplication of custom services
* \#136 - sphinx-apidoc command not found
* \#137 - make command fails when using distclean
## 2017-09-01 CORE 5.0
* DEVELOPMENT:
* support for editorconfig to help standardize development across IDEs, from the defined configuration file
* support for sonarqube analysis, from the defined configuration file
* DAEMON:
* code cleanup and improvements to adhere to coding standards (SonarQube)
* leverage "logzero" module to make easy usage of the standard logging module
* improvements to documentation across the code base
* initial work to separate the dependence on TCP API messaging from the core library (easier core scripting)
* beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges
* SERVICES:
* added Ryu SDN controller service
* added Open vSwitch service
* TEST:
* added unit/integration tests to support validating changes going forward
* BUGFIXES:
* merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96
## 2015-06-05 CORE 4.8
* EMANE:
* support for EMANE 0.9.2
* run emane in each container when using EMANE 0.9.2
* support using separate control networks for EMANE OTA and event traffic
* GUI:
* fixed an issue where the adjacency widget lines pointed to old node positions
* fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable
* fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6
* improved batch mode execution to display the check emulation light status
* improved managing multiple sessions
* improved support for using multiple canvases
* added a reload option to the file menu to revert back to a saved scenario
* DAEMON:
* support exporting scenarios in NRL Network Modeling Framework 1.0 XML format
* support importing scenarios in NRL Network Modeling Framework 1.0 XML format
* support exporting the deployed scenario state in NRL NMF XML 1.0 format
* improved EMANE post-startup processing to better synchronize distributed emulations
* improved how addresses are assigned to tun/tap devices
* added support for python state-change callbacks
* SERVICES:
* added mgen sink and mgen actor services
* added oslrv2 and olsr.org services
* added a docker service
* BUILD:
* improved the install/uninstall process
* improved debian and rpm packaging
* BUGFIXES:
* updated the http service for ubuntu 14.04
* improved included examples
* shortened the length of network interface names
* improved how the core system service manages running the core daemon
* fixed an issues related to applying session configuration setting
* improved detecting when a distributed emulation is already running
* improved documentation
## 2014-08-06 CORE 4.7
* EMANE:
* support for EMANE 0.9.1
* fix error when using Comm Effect model with loss/duplicate string values
* enable flow control in virtual transport if enabled in the MAC model
* fix bug #150 where EMANE event service/address port were not used
* GUI:
* support Tcl/Tk 8.6 when available
* added --(a)ddress and --(p)ort arguments to core-gui command-line
* added File > Execute XML or Python script... option
* added File > Execute Python script with options... menu item
* when executing Python script from GUI, run in background thread, wait for
RUNTIME state
* enter RUNTIME state when start button pressed with empty canvas
* added support for asymmetric link effects
* support link delays up to 274 seconds (netem maximum)
* allow runtime changes of WLAN link effects
* DAEMON:
* set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment
* changed host device naming to use veth, tap prefixes; b.n.SS for bridges
* allow parsing XML files into live running session
* enable link effects between hub/switch and hub/switch connections
* update MDR service to use broadcast interfaces for non-WLAN links
* allow node class to be specified when initializing XML parser
* save and parse canvas origin (reference point) and scale in MP XML
* up/down control script session option
* fix hash calculation used to determine GRE tunnel keys
* use shell script to detach SMF on startup
* added NRL services for mgen sink and nrlolsrv2
* use SDT URL session option
* added core-manage tool for addons to add/remove/check services, models,
and custom node types
* API:
* implement local flag in Execute Message for running host commands
* jitter changed to 64-bit value to align with delay in Link Message
* added unidirectional link flag TLV to Link Message
* added reconfigure event type for re-generating service config files
* return errors in API with failed services
* BUGFIXES:
* fix HTTP service running under Ubuntu
* fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244,
247, 248, 250, 251
## 2013-09-25 CORE 4.6
* NOTE: cored is now core-daemon, and core is now core-gui (for Debian acceptance)
* NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv compatibility)
* EMANE:
* don't start EMANE locally if no local NEMs
* EMANE poststartup() to re-transmit location events during initialization
* added debug port to EMANE options
* added a basic EMANE 802.11 CORE Python script example
* expose transport XML block generation to EmaneModels
* expose NEM entry to the EmaneModel so it can be overridden by a model
* add the control interface bridge prior to starting EMANE, as some models may
* depend on the controlnet functionality
* added EMANE model to CORE converter
* parse lat/long/alt from node messages, for moving nodes using command-line
* fix bug #196 incorrect distance when traversing UTM zones
* GUI:
* added Cut, Copy, and Paste options to the Edit menu
* paste will copy selected services and take care of node and interface
* renumbering
* implement Edit > Find dialog for searching nodes and links
* when copying existing file for a service, perform string replacement of:
* "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%"
* use CORE_DATA_DIR insteadof LIBDIR
* fix Adjacency Widget to work with OSPFv2 only networks
* BUILD:
* build/packaging improvements for inclusion on Debian
* fix error when running scenario with a mobility script in batch mode
* include Linux kernel patches for 3.8
* renamed core-cleanup.sh to core-cleanup for Debian conformance
* don't always generate man pages from Makefile; new manpages for
coresendmsg and core-daemon
* BUGFIXES:
* don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect)
* fixed lock view
* fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2)
* fix broker node count for distributed session entering the RUNTIME state when
* (non-EMANE) WLANs or GreTapBridges are involved;
* fix "file exists" error message when distributed session number is re-used
* and servers file is written
* fix bug #194 configuration dialog too long, make dialog scrollable/resizable
* allow float values for loss and duplicates percent
* fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202,
205, 206, 210, 212, 213, 214, 221
## 2013-04-13 CORE 4.5
* GUI:
* improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost
* fix various GUI issues when reconnecting to a session
* support 3D GUI via output to SDT3D
* added "Execute Python script..." entry to the File Menu
* support user-defined terminal program instead of hard-coded xterm
* added session options for "enable RJ45s", "preserve session dir"
* added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6
* allow sessions with multiple canvases to enter RUNTIME state
* added "--addons" startup mode to pass control to code included from addons dir
* added "Locked" entry to View menu to prevent moving items
* use currently selected node type when invoking a topology generator
* updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot
configuration with imn file
* improved session dialog
* EMANE:
* EMANE 0.8.1 support with backwards-compatibility for 0.7.4
* extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects
* Services:
* updated FTP service with root directory for anonymous users
* added HTTP, PCAP, BIRD, RADVD, and Babel services
* support copying existing files instead of always generating them
* added "Services..." entry to node right-click menu
* added "View" button for side-by-side comparison when copying customized config files
* updated Quagga daemons to wait for zebra.vty VTY file before starting
* General:
* XML import and export
* renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg"
* code reorganization and clean-up
* updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new
"Save As XML..." File menu entry
* added script_start/pause/stop options to Ns2ScriptedMobility
* "python" source sub-directory renamed to "daemon"
* added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for
GUI connection
* support comma-separated list for custom_services_dir in core.conf file
* updated kernel patches for Linux kernel 3.5
* support RFC 6164-style IPv6 /127 addressing
* ns-3:
* integrate ns-3 node location between CORE and ns-3 simulation
* added ns-3 random walk mobility example
* updated ns-3 Wifi example to allow GUI connection and moving of nodes
* fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176,
184, 190, 193
## 2012-09-25 CORE 4.4
* GUI:
* real-time bandwidth plotting tool
* added Wireshark and tshark right-click menu items
* X,Y coordinates shown in the status bar
* updated GUI attribute option to link messages for changing color/width/dash
* added sample IPsec and VPN scenarios, how many nodes script
* added jitter parameter to WLANs
* renamed Experiment menu to Session menu, added session options
* use 'key=value' configuration for services, EMANE models, WLAN models, etc.
* save only service values that have been customized
* copy service parameters from one customized service to another
* right-click menu to start/stop/restart each service
* EMANE:
* EMANE 0.7.4 support
* added support for EMANE CommEffect model and Comm Effect controller GUI
* added support for EMANE Raw Transport when using RJ45 devices
* Services:
* improved service customization; allow a service to define custom Tcl tab
* added vtysh.conf for Quagga service to support 'write mem'
* support scheduled events and services that start N seconds after runtime
* added UCARP service
* Documentation:
* converted the CORE manual to reStructuredText using Sphinx; added Python docs
* General:
* Python code reorganization
* improved cored.py thread locking
* merged xen branch into trunk
* added an event queue to a session with notion of time zero
* added UDP support to cored.py
* use UDP by default in coresendmsg.py; added '-H' option to print examples
* enter a bash shell by default when running vcmd with no arguments
* fixes to distributed emulation entering runtime state
* write 'nodes' file upon session startup
* make session number and other attributes available in environment
* support /etc/core/environment and ~/.core/environment files
* added Ns2ScriptedMobility model to Python, removed from the GUI
* namespace nodes mount a private /sys
* fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146,
147, 151, 154, 155
## 2012-03-07 CORE 4.3
* EMANE 0.7.2 and 0.7.3 support
* hook scripts: customize actions at any of six different session states
* Check Emulation Light (CEL) exception feedback system
* added FTP and XORP services, and service validate commands
* services can flag when customization is required
* Python classes to support ns-3 simulation experiments
* write state, node X,Y position, and servers to pycore session dir
* removed over 9,000 lines of unused GUI code
* performance monitoring script
* batch mode improvements and --closebatch option
* export session to EmulationScript XML files
* basic range model moved from GUI to Python, supports 3D coordinates
* improved WLAN dialog with tabs
* added PhysicalNode class for joining real nodes with emulated networks
* fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120
## 2011-08-19 CORE 4.2
* EMANE 0.7.1 support
* support for Bypass model, Universal PHY, logging, realtime
* configurable MAC addresses
* control interfaces (backchannel between node and host)
* service customization dialog improved (tabbed)
* new testing scripts for MDR and EMANE performance testing
* improved upgrading of old imn files
* new coresendmsg.py utility (deprecates libcoreapi and coreapisend)
* new security services, custom service becomes UserDefined
* new services and Python scripting chapters in manual
* fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches
* fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64,
65, 66, 68, 71, 72, 74
## 2011-01-05 CORE 4.1
* new icons for toolbars and nodes
* node services introduced, node models deprecated
* customizable node types
* traffic flow editor with MGEN support
* user configs moved from /etc/core/`*` to ~/.core/
* allocate addresses from custom IPv4/IPv6 prefixes
* distributed emulation using GRE tunnels
* FreeBSD 8.1 now uses cored.py
* EMANE 0.6.4 support
* numerous bugfixes
## 2010-08-17 CORE 4.0
* Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform)
* ability to close the GUI and later reconnect to a running session (netns only)
* EMANE integration (netns only)
* new topology generators, host file generator
* user-editable Observer Widgets
* use of /etc/core instead of /usr/local/etc/core
* various bugfixes
## 2009-09-15 CORE 3.5
## 2009-06-23 CORE 3.4
## 2009-03-11 CORE 3.3

324
Changelog Normal file
View file

@ -0,0 +1,324 @@
2018-05-22 CORE 5.1
* DAEMON:
- removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB specific files)
- default nodes are now set in the node map
- moved ns3 and netns directories to the top of the repo
- changes to make use of fpm as the tool for building packages
- removed usage of logzero to avoid dependency issues for built packages
- removed daemon addons directory
- added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting and creating new external APIs out of
- cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and informative
- EMANE 1.0.1-1.21 supported
- updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files
- example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py
- EMANE TDMA model now supports an option to start a TDMA schedule when running
- fixed issues with coresendmsg script due to code refactoring
- added make target for generating documentation "make doc"
- Python 2.7+ is now required
- ns3 is no longer bundled by default, but will be produced as a separate package for installation
* GUI
- updated broken help links in GUI Help->About
* Packaging
- fixed PYTHON_PATH to PYTHONPATH in sysv script
- added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation within README.md to try it out
* TEST:
- fixed some broken tests
- new test cases based on CoreEmu usage
* BUGFIXES:
- #142 - duplication of custom services
- #136 - sphinx-apidoc command not found
- #137 - make command fails when using distclean
2017-09-01 CORE 5.0
* DEVELOPMENT:
- support for editorconfig to help standardize development across IDEs, from the defined configuration file
- support for sonarqube analysis, from the defined configuration file
* DAEMON:
- code cleanup and improvements to adhere to coding standards (SonarQube)
- leverage "logzero" module to make easy usage of the standard logging module
- improvements to documentation across the code base
- initial work to separate the dependence on TCP API messaging from the core library (easier core scripting)
- beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges
* SERVICES:
- added Ryu SDN controller service
- added Open vSwitch service
* TEST:
- added unit/integration tests to support validating changes going forward
* BUGFIXES:
- merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96
2015-06-05 CORE 4.8
* EMANE:
- support for EMANE 0.9.2
- run emane in each container when using EMANE 0.9.2
- support using separate control networks for EMANE OTA and event traffic
* GUI:
- fixed an issue where the adjacency widget lines pointed to old node positions
- fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable
- fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6
- improved batch mode execution to display the check emulation light status
- improved managing multiple sessions
- improved support for using multiple canvases
- added a reload option to the file menu to revert back to a saved scenario
* DAEMON:
- support exporting scenarios in NRL Network Modeling Framework 1.0 XML format
- support importing scenarios in NRL Network Modeling Framework 1.0 XML format
- support exporting the deployed scenario state in NRL NMF XML 1.0 format
- improved EMANE post-startup processing to better synchronize distributed emulations
- improved how addresses are assigned to tun/tap devices
- added support for python state-change callbacks
* SERVICES:
- added mgen sink and mgen actor services
- added oslrv2 and olsr.org services
- added a docker service
* BUILD:
- improved the install/uninstall process
- improved debian and rpm packaging
* BUGFIXES:
- updated the http service for ubuntu 14.04
- improved included examples
- shortened the length of network interface names
- improved how the core system service manages running the core daemon
- fixed an issues related to applying session configuration setting
- improved detecting when a distributed emulation is already running
- improved documentation
2014-08-06 CORE 4.7
* EMANE:
- support for EMANE 0.9.1
- fix error when using Comm Effect model with loss/duplicate string values
- enable flow control in virtual transport if enabled in the MAC model
- fix bug #150 where EMANE event service/address port were not used
* GUI:
- support Tcl/Tk 8.6 when available
- added --(a)ddress and --(p)ort arguments to core-gui command-line
- added File > Execute XML or Python script... option
- added File > Execute Python script with options... menu item
- when executing Python script from GUI, run in background thread, wait for
RUNTIME state
- enter RUNTIME state when start button pressed with empty canvas
- added support for asymmetric link effects
- support link delays up to 274 seconds (netem maximum)
- allow runtime changes of WLAN link effects
* DAEMON:
- set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment
- changed host device naming to use veth, tap prefixes; b.n.SS for bridges
- allow parsing XML files into live running session
- enable link effects between hub/switch and hub/switch connections
- update MDR service to use broadcast interfaces for non-WLAN links
- allow node class to be specified when initializing XML parser
- save and parse canvas origin (reference point) and scale in MP XML
- up/down control script session option
- fix hash calculation used to determine GRE tunnel keys
- use shell script to detach SMF on startup
- added NRL services for mgen sink and nrlolsrv2
- use SDT URL session option
- added core-manage tool for addons to add/remove/check services, models,
and custom node types
* API:
- implement local flag in Execute Message for running host commands
- jitter changed to 64-bit value to align with delay in Link Message
- added unidirectional link flag TLV to Link Message
- added reconfigure event type for re-generating service config files
- return errors in API with failed services
* BUGFIXES:
- fix HTTP service running under Ubuntu
- fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244,
247, 248, 250, 251
2013-09-25 CORE 4.6
* NOTE: cored is now core-daemon, and core is now core-gui (for Debian
acceptance)
* NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv
compatibility)
* EMANE:
- don't start EMANE locally if no local NEMs
- EMANE poststartup() to re-transmit location events during initialization
- added debug port to EMANE options
- added a basic EMANE 802.11 CORE Python script example
- expose transport XML block generation to EmaneModels
- expose NEM entry to the EmaneModel so it can be overridden by a model
- add the control interface bridge prior to starting EMANE, as some models may
- depend on the controlnet functionality
- added EMANE model to CORE converter
- parse lat/long/alt from node messages, for moving nodes using command-line
- fix bug #196 incorrect distance when traversing UTM zones
* GUI:
- added Cut, Copy, and Paste options to the Edit menu
- paste will copy selected services and take care of node and interface
- renumbering
- implement Edit > Find dialog for searching nodes and links
- when copying existing file for a service, perform string replacement of:
- "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%"
- use CORE_DATA_DIR insteadof LIBDIR
- fix Adjacency Widget to work with OSPFv2 only networks
* BUILD:
- build/packaging improvements for inclusion on Debian
- fix error when running scenario with a mobility script in batch mode
- include Linux kernel patches for 3.8
- renamed core-cleanup.sh to core-cleanup for Debian conformance
- don't always generate man pages from Makefile; new manpages for
coresendmsg and core-daemon
* BUGFIXES:
- don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect)
- fixed lock view
- fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2)
- fix broker node count for distributed session entering the RUNTIME state when
- (non-EMANE) WLANs or GreTapBridges are involved;
- fix "file exists" error message when distributed session number is re-used
- and servers file is written
- fix bug #194 configuration dialog too long, make dialog scrollable/resizable
- allow float values for loss and duplicates percent
- fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202,
205, 206, 210, 212, 213, 214, 221
2013-04-13 CORE 4.5
* GUI:
- improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost
- fix various GUI issues when reconnecting to a session
- support 3D GUI via output to SDT3D
- added "Execute Python script..." entry to the File Menu
- support user-defined terminal program instead of hard-coded xterm
- added session options for "enable RJ45s", "preserve session dir"
- added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6
- allow sessions with multiple canvases to enter RUNTIME state
- added "--addons" startup mode to pass control to code included from addons dir
- added "Locked" entry to View menu to prevent moving items
- use currently selected node type when invoking a topology generator
- updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot configuration with imn file
- improved session dialog
* EMANE:
- EMANE 0.8.1 support with backwards-compatibility for 0.7.4
- extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects
* Services:
- updated FTP service with root directory for anonymous users
- added HTTP, PCAP, BIRD, RADVD, and Babel services
- support copying existing files instead of always generating them
- added "Services..." entry to node right-click menu
- added "View" button for side-by-side comparison when copying customized config files
- updated Quagga daemons to wait for zebra.vty VTY file before starting
* General:
- XML import and export
- renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg"
- code reorganization and clean-up
- updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new "Save As XML..." File menu entry
- added script_start/pause/stop options to Ns2ScriptedMobility
- "python" source sub-directory renamed to "daemon"
- added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for GUI connection
- support comma-separated list for custom_services_dir in core.conf file
- updated kernel patches for Linux kernel 3.5
- support RFC 6164-style IPv6 /127 addressing
* ns-3:
- integrate ns-3 node location between CORE and ns-3 simulation
- added ns-3 random walk mobility example
- updated ns-3 Wifi example to allow GUI connection and moving of nodes
* fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, 184, 190, 193
2012-09-25 CORE 4.4
* GUI:
- real-time bandwidth plotting tool
- added Wireshark and tshark right-click menu items
- X,Y coordinates shown in the status bar
- updated GUI attribute option to link messages for changing color/width/dash
- added sample IPsec and VPN scenarios, how many nodes script
- added jitter parameter to WLANs
- renamed Experiment menu to Session menu, added session options
- use 'key=value' configuration for services, EMANE models, WLAN models, etc.
- save only service values that have been customized
- copy service parameters from one customized service to another
- right-click menu to start/stop/restart each service
* EMANE:
- EMANE 0.7.4 support
- added support for EMANE CommEffect model and Comm Effect controller GUI
- added support for EMANE Raw Transport when using RJ45 devices
* Services:
- improved service customization; allow a service to define custom Tcl tab
- added vtysh.conf for Quagga service to support 'write mem'
- support scheduled events and services that start N seconds after runtime
- added UCARP service
* Documentation:
- converted the CORE manual to reStructuredText using Sphinx; added Python docs
* General:
- Python code reorganization
- improved cored.py thread locking
- merged xen branch into trunk
- added an event queue to a session with notion of time zero
- added UDP support to cored.py
- use UDP by default in coresendmsg.py; added '-H' option to print examples
- enter a bash shell by default when running vcmd with no arguments
- fixes to distributed emulation entering runtime state
- write 'nodes' file upon session startup
- make session number and other attributes available in environment
- support /etc/core/environment and ~/.core/environment files
- added Ns2ScriptedMobility model to Python, removed from the GUI
- namespace nodes mount a private /sys
- fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, 147, 151, 154, 155
2012-03-07 CORE 4.3
* EMANE 0.7.2 and 0.7.3 support
* hook scripts: customize actions at any of six different session states
* Check Emulation Light (CEL) exception feedback system
* added FTP and XORP services, and service validate commands
* services can flag when customization is required
* Python classes to support ns-3 simulation experiments
* write state, node X,Y position, and servers to pycore session dir
* removed over 9,000 lines of unused GUI code
* performance monitoring script
* batch mode improvements and --closebatch option
* export session to EmulationScript XML files
* basic range model moved from GUI to Python, supports 3D coordinates
* improved WLAN dialog with tabs
* added PhysicalNode class for joining real nodes with emulated networks
* fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120
2011-08-19 CORE 4.2
* EMANE 0.7.1 support
- support for Bypass model, Universal PHY, logging, realtime
* configurable MAC addresses
* control interfaces (backchannel between node and host)
* service customization dialog improved (tabbed)
* new testing scripts for MDR and EMANE performance testing
* improved upgrading of old imn files
* new coresendmsg.py utility (deprecates libcoreapi and coreapisend)
* new security services, custom service becomes UserDefined
* new services and Python scripting chapters in manual
* fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches
* fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, 65, 66, 68, 71, 72, 74
2011-01-05 CORE 4.1
* new icons for toolbars and nodes
* node services introduced, node models deprecated
* customizable node types
* traffic flow editor with MGEN support
* user configs moved from /etc/core/`*` to ~/.core/
* allocate addresses from custom IPv4/IPv6 prefixes
* distributed emulation using GRE tunnels
* FreeBSD 8.1 now uses cored.py
* EMANE 0.6.4 support
* numerous bugfixes
2010-08-17 CORE 4.0
* Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform)
* ability to close the GUI and later reconnect to a running session (netns only)
* EMANE integration (netns only)
* new topology generators, host file generator
* user-editable Observer Widgets
* use of /etc/core instead of /usr/local/etc/core
* various bugfixes
2009-09-15 CORE 3.5
2009-06-23 CORE 3.4
2009-03-11 CORE 3.3

View file

@ -1,126 +0,0 @@
# syntax=docker/dockerfile:1
FROM ubuntu:22.04
LABEL Description="CORE Docker Ubuntu Image"
ARG PREFIX=/usr/local
ARG BRANCH=master
ARG PROTOC_VERSION=3.19.6
ARG VENV_PATH=/opt/core/venv
ENV DEBIAN_FRONTEND=noninteractive
ENV PATH="$PATH:${VENV_PATH}/bin"
WORKDIR /opt
# install system dependencies
RUN apt-get update -y && \
apt-get install -y software-properties-common
RUN add-apt-repository "deb http://archive.ubuntu.com/ubuntu jammy universe"
RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
automake \
bash \
ca-certificates \
ethtool \
gawk \
gcc \
g++ \
iproute2 \
iputils-ping \
libc-dev \
libev-dev \
libreadline-dev \
libtool \
nftables \
python3 \
python3-pip \
python3-tk \
pkg-config \
tk \
xauth \
xterm \
wireshark \
vim \
build-essential \
nano \
firefox \
net-tools \
rsync \
openssh-server \
openssh-client \
vsftpd \
atftpd \
atftp \
mini-httpd \
lynx \
tcpdump \
iperf \
iperf3 \
tshark \
openssh-sftp-server \
bind9 \
bind9-utils \
openvpn \
isc-dhcp-server \
isc-dhcp-client \
whois \
ipcalc \
socat \
hping3 \
libgtk-3-0 \
librest-0.7-0 \
libgtk-3-common \
dconf-gsettings-backend \
libsoup-gnome2.4-1 \
libsoup2.4-1 \
dconf-service \
x11-xserver-utils \
ftp \
git \
sudo \
wget \
tzdata \
libpcap-dev \
libpcre3-dev \
libprotobuf-dev \
libxml2-dev \
protobuf-compiler \
unzip \
uuid-dev \
iproute2 \
vlc \
iputils-ping && \
apt-get autoremove -y
# install core
RUN git clone https://github.com/coreemu/core && \
cd core && \
git checkout ${BRANCH} && \
./setup.sh && \
PATH=/root/.local/bin:$PATH inv install -v -p ${PREFIX} && \
cd /opt && \
rm -rf ospf-mdr
# install emane
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip && \
mkdir protoc && \
unzip protoc-${PROTOC_VERSION}-linux-x86_64.zip -d protoc && \
git clone https://github.com/adjacentlink/emane.git && \
cd emane && \
./autogen.sh && \
./configure --prefix=/usr && \
make -j$(nproc) && \
make install && \
cd src/python && \
make clean && \
PATH=/opt/protoc/bin:$PATH make && \
${VENV_PATH}/bin/python -m pip install . && \
cd /opt && \
rm -rf protoc && \
rm -rf emane && \
rm -f protoc-${PROTOC_VERSION}-linux-x86_64.zip
WORKDIR /root
CMD /opt/core/venv/bin/core-daemon

20
Jenkinsfile vendored Normal file
View file

@ -0,0 +1,20 @@
pipeline {
agent any
stages {
stage('build core') {
steps {
sh './bootstrap.sh'
sh './configure'
sh 'make'
sh 'sudo make install'
}
}
stage('test core') {
steps {
sh 'pytest daemon/tests/test_core.py'
sh 'pytest daemon/tests/test_gui.py'
sh 'pytest daemon/tests/test_emane.py'
}
}
}
}

View file

@ -6,26 +6,29 @@ if WANT_DOCS
DOCS = docs man DOCS = docs man
endif endif
if WANT_GUI
GUI = gui
endif
if WANT_DAEMON if WANT_DAEMON
DAEMON = daemon DAEMON = scripts daemon
endif endif
if WANT_NETNS if WANT_NETNS
NETNS = netns NETNS = netns ns3
endif endif
# keep docs last due to dependencies on binaries # keep docs last due to dependencies on binaries
SUBDIRS = $(DAEMON) $(NETNS) $(DOCS) SUBDIRS = $(GUI) $(DAEMON) $(NETNS) $(DOCS)
ACLOCAL_AMFLAGS = -I config ACLOCAL_AMFLAGS = -I config
# extra files to include with distribution tarball # extra files to include with distribution tarball
EXTRA_DIST = bootstrap.sh \ EXTRA_DIST = bootstrap.sh \
package \
LICENSE \ LICENSE \
README.md \ README.md \
ASSIGNMENT_OF_COPYRIGHT.pdf \ ASSIGNMENT_OF_COPYRIGHT.pdf \
CHANGELOG.md \ Changelog \
.version \ .version \
.version.date .version.date
@ -41,117 +44,80 @@ DISTCLEANFILES = aclocal.m4 \
MAINTAINERCLEANFILES = .version \ MAINTAINERCLEANFILES = .version \
.version.date .version.date
define fpm-distributed-deb = define fpm-python =
fpm -s dir -t deb -n core-distributed \ fpm -s python -t $1 \
-m "$(PACKAGE_MAINTAINERS)" \ -m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "Common Open Research Emulator Distributed Package" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \ --vendor "$(PACKAGE_VENDOR)" \
-p core-distributed_VERSION_ARCH.deb \ $2
-v $(PACKAGE_VERSION) \
-d "ethtool" \
-d "procps" \
-d "libc6 >= 2.14" \
-d "bash >= 3.0" \
-d "nftables" \
-d "iproute2" \
-d "libev4" \
-d "openssh-server" \
-d "xterm" \
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/
endef endef
define fpm-distributed-rpm = define fpm-gui =
fpm -s dir -t rpm -n core-distributed \ fpm -s dir -t $1 -n core-gui \
-m "$(PACKAGE_MAINTAINERS)" \ -m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \ --license "BSD" \
--description "Common Open Research Emulator Distributed Package" \ --description "Common Open Research Emulator GUI front-end" \
--url https://github.com/coreemu/core \ --url http://www.nrl.navy.mil/itd/ncs/products/core \
--vendor "$(PACKAGE_VENDOR)" \ --vendor "$(PACKAGE_VENDOR)" \
-p core-distributed_VERSION_ARCH.rpm \ -p core-gui_VERSION_ARCH.$1 \
-v $(PACKAGE_VERSION) \ -v $(PACKAGE_VERSION) \
-d "ethtool" \ -d "bash" \
-d "procps-ng" \ -d "tcl" \
-d "bash >= 3.0" \
-d "nftables" \
-d "iproute" \
-d "libev" \
-d "net-tools" \
-d "openssh-server" \
-d "xterm" \
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/
endef
define fpm-rpm =
fpm -s dir -t rpm -n core \
-m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "core vnoded/vcmd and system dependencies" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_VERSION_ARCH.rpm \
-v $(PACKAGE_VERSION) \
--rpm-init package/core-daemon \
--after-install package/after-install.sh \
--after-remove package/after-remove.sh \
-d "ethtool" \
-d "tk" \ -d "tk" \
$2 \
-C $(DESTDIR)
endef
define fpm-daemon-rpm =
fpm -s python -t rpm \
-p NAME_sysv_VERSION_ARCH.rpm \
--rpm-init scripts/core-daemon \
--python-install-bin $(bindir) \
--python-install-data $(prefix) \
--python-install-lib $(pythondir) \
-m "$(PACKAGE_MAINTAINERS)" \
--vendor "$(PACKAGE_VENDOR)" \
-d "procps-ng" \ -d "procps-ng" \
-d "bash >= 3.0" \ -d "bash >= 3.0" \
-d "bridge-utils" \
-d "ebtables" \ -d "ebtables" \
-d "iproute" \ -d "iproute" \
-d "libev" \ -d "libev" \
-d "net-tools" \ -d "net-tools" \
-d "nftables" \ -d "python >= 2.7, python < 3.0" \
netns/vnoded=/usr/bin/ \ netns/setup.py daemon/setup.py
netns/vcmd=/usr/bin/ \
package/etc/core.conf=/etc/core/ \
package/etc/logging.conf=/etc/core/ \
package/examples=/opt/core/ \
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
endef endef
define fpm-deb = define fpm-daemon-deb =
fpm -s dir -t deb -n core \ fpm -s python -t deb \
-p NAME_$1_VERSION_ARCH.deb \
--python-install-bin $(bindir) \
--python-install-data $(prefix) \
--python-install-lib $(pythondir) \
$2 $3 \
-m "$(PACKAGE_MAINTAINERS)" \ -m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "core vnoded/vcmd and system dependencies" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \ --vendor "$(PACKAGE_VENDOR)" \
-p core_VERSION_ARCH.deb \
-v $(PACKAGE_VERSION) \
--deb-systemd package/core-daemon.service \
--deb-no-default-config-files \
--after-install package/after-install.sh \
--after-remove package/after-remove.sh \
-d "ethtool" \
-d "tk" \
-d "libtk-img" \
-d "procps" \ -d "procps" \
-d "libc6 >= 2.14" \ -d "libc6 >= 2.14" \
-d "bash >= 3.0" \ -d "bash >= 3.0" \
-d "bridge-utils" \
-d "ebtables" \ -d "ebtables" \
-d "iproute2" \ -d "iproute2" \
-d "libev4" \ -d "libev4" \
-d "nftables" \ -d "python (>= 2.7), python (<< 3.0)" \
netns/vnoded=/usr/bin/ \ --deb-recommends quagga \
netns/vcmd=/usr/bin/ \ netns/setup.py daemon/setup.py
package/etc/core.conf=/etc/core/ \
package/etc/logging.conf=/etc/core/ \
package/examples=/opt/core/ \
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
endef endef
.PHONY: fpm .PHONY: fpm
fpm: clean-local-fpm fpm: clean-local-fpm
cd daemon && poetry build -f wheel $(MAKE) -C gui install DESTDIR=$(DESTDIR)
$(call fpm-deb) $(call fpm-gui,rpm)
$(call fpm-rpm) $(call fpm-gui,deb,-d "libtk-img")
$(call fpm-distributed-deb) $(call fpm-python,rpm,ns3/setup.py)
$(call fpm-distributed-rpm) $(call fpm-python,deb,ns3/setup.py)
$(call fpm-daemon-rpm)
$(call fpm-daemon-deb,sysv,--deb-init,scripts/core-daemon)
$(call fpm-daemon-deb,systemd,--deb-systemd,scripts/core-daemon.service)
.PHONY: clean-local-fpm .PHONY: clean-local-fpm
clean-local-fpm: clean-local-fpm:
@ -170,12 +136,24 @@ define change-files =
$(info creating file $1 from $1.in) $(info creating file $1 from $1.in)
@$(SED) -e 's,[@]sbindir[@],$(sbindir),g' \ @$(SED) -e 's,[@]sbindir[@],$(sbindir),g' \
-e 's,[@]bindir[@],$(bindir),g' \ -e 's,[@]bindir[@],$(bindir),g' \
-e 's,[@]pythondir[@],$(pythondir),g' \
-e 's,[@]PYTHON[@],$(PYTHON),g' \
-e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \ -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
-e 's,[@]PACKAGE_DATE[@],$(PACKAGE_DATE),g' \ -e 's,[@]PACKAGE_DATE[@],$(PACKAGE_DATE),g' \
-e 's,[@]CORE_LIB_DIR[@],$(CORE_LIB_DIR),g' \ -e 's,[@]CORE_LIB_DIR[@],$(CORE_LIB_DIR),g' \
-e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \ -e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \
-e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \ -e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \
-e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \ -e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \
-e 's,[@]CORE_GUI_CONF_DIR[@],$(CORE_GUI_CONF_DIR),g' \
-e 's,[@]brctl_path[@],$(brctl_path),g' \
-e 's,[@]sysctl_path[@],$(sysctl_path),g' \
-e 's,[@]ip_path[@],$(ip_path),g' \
-e 's,[@]tc_path[@],$(tc_path),g' \
-e 's,[@]ebtables_path[@],$(ebtables_path),g' \
-e 's,[@]mount_path[@],$(mount_path),g' \
-e 's,[@]umount_path[@],$(umount_path),g' \
-e 's,[@]ovs_vs_path[@],$(ovs_vs_path),g' \
-e 's,[@]ovs_of_path[@],$(ovs_of_path),g' \
< $1.in > $1 < $1.in > $1
endef endef
@ -183,8 +161,10 @@ all: change-files
.PHONY: change-files .PHONY: change-files
change-files: change-files:
$(call change-files,gui/core-gui)
$(call change-files,scripts/core-daemon.service)
$(call change-files,scripts/core-daemon)
$(call change-files,daemon/core/constants.py) $(call change-files,daemon/core/constants.py)
$(call change-files,netns/setup.py)
CORE_DOC_SRC = core-python-$(PACKAGE_VERSION) CORE_DOC_SRC = core-python-$(PACKAGE_VERSION)
.PHONY: doc .PHONY: doc

160
README.md
View file

@ -1,107 +1,103 @@
# Index
- CORE
- Docker Setup
- Precompiled container image
- Build container image from source
- Adding extra packages
- Useful commands
- License
# CORE # CORE
CORE: Common Open Research Emulator CORE: Common Open Research Emulator
Copyright (c)2005-2022 the Boeing Company. Copyright (c)2005-2018 the Boeing Company.
See the LICENSE file included in this distribution. See the LICENSE file included in this distribution.
# Docker Setup ## About
Here you have 2 choices The Common Open Research Emulator (CORE) is a tool for emulating
networks on one or more machines. You can connect these emulated
networks to live networks. CORE consists of a GUI for drawing
topologies of lightweight virtual machines, and Python modules for
scripting network emulation.
## Precompiled container image ## Documentation and Examples
```bash * Documentation hosted on GitHub
* http://coreemu.github.io/core/
* Basic Script Examples
* [Examples](daemon/examples/api)
* Custom Service Example
* [sample.py](daemon/examples/myservices/sample.py)
* Custom Emane Model Example
* [examplemodel.py](daemon/examples/myemane/examplemodel.py)
# Start container ## Support
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped git.olympuslab.net/afonso/core-extra:latest
``` We are leveraging Discord for persistent chat rooms, voice chat, and
## Build container image from source GitHub integration. This allows for more dynamic conversations and the
capability to respond faster. Feel free to join us at the link below.
https://discord.gg/AKd7kmP
```bash You can also get help with questions, comments, or trouble, by using
# Clone the repo the CORE mailing lists:
git clone https://gitea.olympuslab.net/afonso/core-extra.git
# cd into the directory * [core-users](https://pf.itd.nrl.navy.mil/mailman/listinfo/core-users) for general comments and questions
cd core-extra * [core-dev](https://pf.itd.nrl.navy.mil/mailman/listinfo/core-dev) for bugs, compile errors, and other development issues
# build the docker image ## Building CORE
sudo docker build -t core-extra .
# start container
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra
```shell
./bootstrap.sh
./configure
make
sudo make install
``` ```
### Adding extra packages Building Documentation
----------------------
To add extra packages you must modify the Dockerfile and then compile the docker image.
If you install it after starting the container it will, by docker nature, be reverted on the next boot of the container.
# Useful commands
I have the following functions on my fish shell
to help me better use core
THIS ONLY WORKS ON FISH, MODIFY FOR BASH OR ZSH
```fish
# RUN CORE GUI
function core
xhost +local:root
sudo docker exec -it core core-gui
end
# RUN BASH INSIDE THE CONTAINER
function core-bash
sudo docker exec -it core /bin/bash
end
# LAUNCH NODE BASH ON THE HOST MACHINE
function launch-term --argument nodename
sudo docker exec -it core xterm -bg black -fg white -fa 'DejaVu Sans Mono' -fs 16 -e vcmd -c /tmp/pycore.1/$nodename -- /bin/bash
end
#TO RUN ANY OTHER COMMAND
sudo docker exec -it core COMAND_GOES_HERE
```shell
./bootstrap.sh
./configure
make doc
``` ```
## LICENSE Building Packages
-----------------
Copyright (c) 2005-2018, the Boeing Company. Install fpm: http://fpm.readthedocs.io/en/latest/installing.html
Redistribution and use in source and binary forms, with or without Build package commands, DESTDIR is used for gui packaging only
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, ```shell
this list of conditions and the following disclaimer. ./bootstrap.sh
2. Redistributions in binary form must reproduce the above copyright notice, ./configure
this list of conditions and the following disclaimer in the documentation make
and/or other materials provided with the distribution. mkdir /tmp/core-gui
make fpm DESTDIR=/tmp/core-gui
```
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" This will produce:
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * CORE GUI rpm/deb files
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * core-gui_$VERSION_$ARCH
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CORE ns3 rpm/deb files
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * python-core-ns3_$VERSION_$ARCH
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * CORE python rpm/deb files for SysV and systemd service types
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * python-core-sysv_$VERSION_$ARCH
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * python-core-systemd_$VERSION_$ARCH
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE. Running CORE
------------
First start the CORE services:
```shell
# sysv
sudo service core-daemon start
# systemd
sudo systemctl start core-daemon
```
This automatically runs the core-daemon program.
Assuming the GUI is in your PATH, run the CORE GUI by typing the following:
```shell
core-gui
```
This launches the CORE GUI. You do not need to run the GUI as root.

View file

@ -1,5 +1,9 @@
#!/bin/sh #!/bin/sh
# #
# (c)2010-2012 the Boeing Company
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# Bootstrap the autoconf system. # Bootstrap the autoconf system.
# #

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script. # Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT # this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 9.0.3) AC_INIT(core, 5.2, core-dev@nrl.navy.mil)
# autoconf and automake initialization # autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in]) AC_CONFIG_SRCDIR([netns/version.h.in])
@ -14,7 +14,7 @@ AM_INIT_AUTOMAKE([tar-ustar])
# define variables used for packaging and date display # define variables used for packaging and date display
PACKAGE_DATE=m4_esyscmd_s([date +%Y%m%d]) PACKAGE_DATE=m4_esyscmd_s([date +%Y%m%d])
PACKAGE_VENDOR="CORE Developers" PACKAGE_VENDOR="CORE Developers"
PACKAGE_MAINTAINERS="$PACKAGE_VENDOR" PACKAGE_MAINTAINERS="$PACKAGE_VENDOR <$PACKAGE_BUGREPORT>"
# core specific variables # core specific variables
CORE_LIB_DIR="\${prefix}/lib/core" CORE_LIB_DIR="\${prefix}/lib/core"
@ -30,25 +30,19 @@ AC_SUBST(CORE_CONF_DIR)
AC_SUBST(CORE_DATA_DIR) AC_SUBST(CORE_DATA_DIR)
AC_SUBST(CORE_STATE_DIR) AC_SUBST(CORE_STATE_DIR)
# documentation option # CORE GUI configuration files and preferences in CORE_GUI_CONF_DIR
AC_ARG_ENABLE([docs], # scenario files in ~/.core/configs/
[AS_HELP_STRING([--enable-docs[=ARG]], AC_ARG_WITH([guiconfdir],
[build python documentation (default is no)])], [AS_HELP_STRING([--with-guiconfdir=dir],
[], [enable_docs=no]) [specify GUI configuration directory])],
AC_SUBST(enable_docs) [CORE_GUI_CONF_DIR="$with_guiconfdir"],
[CORE_GUI_CONF_DIR="\$\${HOME}/.core"])
# python option AC_SUBST(CORE_GUI_CONF_DIR)
AC_ARG_ENABLE([python], AC_ARG_ENABLE([gui],
[AS_HELP_STRING([--enable-python[=ARG]], [AS_HELP_STRING([--enable-gui[=ARG]],
[build and install the python bindings (default is yes)])], [build and install the GUI (default is yes)])],
[], [enable_python=yes]) [], [enable_gui=yes])
AC_SUBST(enable_python) AC_SUBST(enable_gui)
if test "x$enable_python" = "xyes" ; then
want_python=yes
else
want_python=no
fi
AC_ARG_ENABLE([daemon], AC_ARG_ENABLE([daemon],
[AS_HELP_STRING([--enable-daemon[=ARG]], [AS_HELP_STRING([--enable-daemon[=ARG]],
[build and install the daemon with Python modules [build and install the daemon with Python modules
@ -56,13 +50,6 @@ AC_ARG_ENABLE([daemon],
[], [enable_daemon=yes]) [], [enable_daemon=yes])
AC_SUBST(enable_daemon) AC_SUBST(enable_daemon)
AC_ARG_ENABLE([vnodedonly],
[AS_HELP_STRING([--enable-vnodedonly[=ARG]],
[only try to build vnoded and vcmd container utils
(default is no)])],
[enable_vnodedonly=yes], [enable_vnodedonly=no])
AC_SUBST(enable_vnodedonly)
SEARCHPATH="/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin" SEARCHPATH="/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin"
# default compiler flags # default compiler flags
@ -83,62 +70,8 @@ if test "x$enable_daemon" = "xyes"; then
want_python=yes want_python=yes
want_linux_netns=yes want_linux_netns=yes
AM_PATH_PYTHON(3.9) # Checks for libraries.
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])]) AC_CHECK_LIB([netgraph], [NgMkSockNode])
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
if test "x$sysctl_path" = "xno" ; then
AC_MSG_ERROR([Could not locate sysctl (from procps package).])
fi
AC_CHECK_PROG(nftables_path, nft, $as_dir, no, $SEARCHPATH)
if test "x$nftables_path" = "xno" ; then
AC_MSG_ERROR([Could not locate nftables (from nftables package).])
fi
AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH)
if test "x$ip_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ip (from iproute package).])
fi
AC_CHECK_PROG(tc_path, tc, $as_dir, no, $SEARCHPATH)
if test "x$tc_path" = "xno" ; then
AC_MSG_ERROR([Could not locate tc (from iproute package).])
fi
AC_CHECK_PROG(ethtool_path, ethtool, $as_dir, no, $SEARCHPATH)
if test "x$ethtool_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ethtool (from package ethtool)])
fi
AC_CHECK_PROG(mount_path, mount, $as_dir, no, $SEARCHPATH)
if test "x$mount_path" = "xno" ; then
AC_MSG_ERROR([Could not locate mount (from package mount)])
fi
AC_CHECK_PROG(umount_path, umount, $as_dir, no, $SEARCHPATH)
if test "x$umount_path" = "xno" ; then
AC_MSG_ERROR([Could not locate umount (from package mount)])
fi
AC_CHECK_PROG(convert, convert, yes, no, $SEARCHPATH)
if test "x$convert" = "xno" ; then
AC_MSG_WARN([Could not locate ImageMagick convert.])
fi
AC_CHECK_PROG(ovs_vs_path, ovs-vsctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_vs_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS mode])
fi
AC_CHECK_PROG(ovs_of_path, ovs-ofctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_of_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS mode])
fi
fi
if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then
want_linux_netns=yes
# Checks for header files. # Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h]) AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h])
@ -158,6 +91,51 @@ if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ;
AC_FUNC_REALLOC AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
AM_PATH_PYTHON(2.7)
AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH)
if test "x$brctl_path" = "xno" ; then
AC_MSG_ERROR([Could not locate brctl (from bridge-utils package).])
fi
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
AC_CHECK_PROG(ebtables_path, ebtables, $as_dir, no, $SEARCHPATH)
if test "x$ebtables_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ebtables (from ebtables package).])
fi
AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH)
if test "x$ip_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ip (from iproute package).])
fi
AC_CHECK_PROG(tc_path, tc, $as_dir, no, $SEARCHPATH)
if test "x$tc_path" = "xno" ; then
AC_MSG_ERROR([Could not locate tc (from iproute package).])
fi
AC_CHECK_PROG(mount_path, mount, $as_dir, no, $SEARCHPATH)
AC_CHECK_PROG(umount_path, umount, $as_dir, no, $SEARCHPATH)
AC_CHECK_PROG(convert, convert, yes, no, $SEARCHPATH)
if test "x$convert" = "xno" ; then
AC_MSG_WARN([Could not locate ImageMagick convert.])
fi
AC_CHECK_PROG(ovs_vs_path, ovs-vsctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_vs_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS nodes])
fi
AC_CHECK_PROG(ovs_of_path, ovs-ofctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_of_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS nodes])
fi
CFLAGS_save=$CFLAGS
CPPFLAGS_save=$CPPFLAGS
if test "x$PYTHON_INCLUDE_DIR" = "x"; then
PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()"`
fi
CFLAGS="-I$PYTHON_INCLUDE_DIR"
CPPFLAGS="-I$PYTHON_INCLUDE_DIR"
AC_CHECK_HEADERS([Python.h], [],
AC_MSG_ERROR([Python bindings require Python development headers (try installing your 'python-devel' or 'python-dev' package)]))
CFLAGS=$CFLAGS_save
CPPFLAGS=$CPPFLAGS_save
PKG_CHECK_MODULES(libev, libev, PKG_CHECK_MODULES(libev, libev,
AC_MSG_RESULT([found libev using pkgconfig OK]) AC_MSG_RESULT([found libev using pkgconfig OK])
AC_SUBST(libev_CFLAGS) AC_SUBST(libev_CFLAGS)
@ -170,37 +148,52 @@ if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ;
AC_MSG_ERROR([Python bindings require libev (try installing your 'libev-devel' or 'libev-dev' package)]))) AC_MSG_ERROR([Python bindings require libev (try installing your 'libev-devel' or 'libev-dev' package)])))
fi fi
want_docs=no AC_CHECK_PROG(help2man, help2man, yes, no, $SEARCHPATH)
if [test "x$want_python" = "xyes" && test "x$enable_docs" = "xyes"] ; then
AC_CHECK_PROG(help2man, help2man, yes, no, $SEARCHPATH)
if test "x$help2man" = "xno" ; then if test "x$help2man" = "xno" ; then
AC_MSG_WARN([Could not locate help2man.]) AC_MSG_WARN([Could not locate help2man.])
want_docs_missing="$want_docs_missing help2man" want_docs_missing="$want_docs_missing help2man"
fi
if test "x$want_docs_missing" = "x" ; then
want_docs=yes
else
AC_MSG_ERROR([Could not find required helper utilities (${want_docs_missing}) so the CORE documentation will not be built.])
want_docs=no
fi
# check for sphinx required during make
AC_CHECK_PROG(sphinxapi_path, sphinx-apidoc, $as_dir, no, $SEARCHPATH)
if test "x$sphinxapi_path" = "xno" ; then
AC_MSG_ERROR(["Could not locate sphinx-apidoc, install python3 -m pip install sphinx"])
want_docs=no
fi
AS_IF([$PYTHON -c "import sphinx_rtd_theme" &> /dev/null], [], [AC_MSG_ERROR([doc dependency missing, please install python3 -m pip install sphinx-rtd-theme])])
fi fi
if test "x$want_docs_missing" = "x" ; then
want_docs=yes
else
AC_MSG_WARN([Could not find required helper utilities (${want_docs_missing}) so the CORE documentation will not be built.])
want_docs=no
fi
# check for sphinx required during make
AC_CHECK_PROG(sphinxapi_path, sphinx-apidoc, $as_dir, no, $SEARCHPATH)
if test "x$sphinxapi_path" = "xno" ; then
AC_MSG_ERROR(["Could not location sphinx-apidoc, from the python-sphinx package"])
fi
#AC_PATH_PROGS(tcl_path, [tclsh tclsh8.5 tclsh8.4], no)
#if test "x$tcl_path" = "xno" ; then
# AC_MSG_ERROR([Could not locate tclsh. Please install Tcl/Tk.])
#fi
#AC_PATH_PROGS(wish_path, [wish wish8.5 wish8.4], no)
#if test "x$wish_path" = "xno" ; then
# AC_MSG_ERROR([Could not locate wish. Please install Tcl/Tk.])
#fi
AC_ARG_WITH([startup],
[AS_HELP_STRING([--with-startup=option],
[option=systemd,suse,none to install systemd/SUSE init scripts])],
[with_startup=$with_startup],
[with_startup=initd])
AC_SUBST(with_startup)
AC_MSG_RESULT([using startup option $with_startup])
# Variable substitutions # Variable substitutions
AM_CONDITIONAL(WANT_GUI, test x$enable_gui = xyes)
AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes) AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes)
AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes) AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes)
AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes) AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes)
AM_CONDITIONAL(WANT_NETNS, test x$want_linux_netns = xyes) AM_CONDITIONAL(WANT_NETNS, test x$want_linux_netns = xyes)
AM_CONDITIONAL(WANT_VNODEDONLY, test x$enable_vnodedonly = xyes) AM_CONDITIONAL(WANT_INITD, test x$with_startup = xinitd)
AM_CONDITIONAL(WANT_SYSTEMD, test x$with_startup = xsystemd)
if test $cross_compiling = no; then if test $cross_compiling = no; then
AM_MISSING_PROG(HELP2MAN, help2man) AM_MISSING_PROG(HELP2MAN, help2man)
@ -210,14 +203,19 @@ fi
# Output files # Output files
AC_CONFIG_FILES([Makefile AC_CONFIG_FILES([Makefile
gui/version.tcl
gui/Makefile
gui/icons/Makefile
scripts/Makefile
scripts/perf/Makefile
man/Makefile man/Makefile
docs/Makefile docs/Makefile
daemon/Makefile daemon/Makefile
daemon/doc/Makefile daemon/doc/Makefile
daemon/doc/conf.py daemon/doc/conf.py
daemon/proto/Makefile
netns/Makefile netns/Makefile
netns/version.h],) netns/version.h
ns3/Makefile],)
AC_OUTPUT AC_OUTPUT
# Summary text # Summary text
@ -231,12 +229,20 @@ Build:
Prefix: ${prefix} Prefix: ${prefix}
Exec Prefix: ${exec_prefix} Exec Prefix: ${exec_prefix}
GUI:
GUI path: ${CORE_LIB_DIR}
GUI config: ${CORE_GUI_CONF_DIR}
Daemon: Daemon:
Daemon path: ${bindir} Daemon path: ${bindir}
Daemon config: ${CORE_CONF_DIR} Daemon config: ${CORE_CONF_DIR}
Python: ${PYTHON} Python modules: ${pythondir}
Logs: ${CORE_STATE_DIR}/log
Startup: ${with_startup}
Features to build: Features to build:
Build GUI: ${enable_gui}
Build Daemon: ${enable_daemon} Build Daemon: ${enable_daemon}
Documentation: ${want_docs} Documentation: ${want_docs}

110
corefx/pom.xml Normal file
View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.core</groupId>
<artifactId>corefx</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<jung.version>2.1.1</jung.version>
<jackson.version>2.9.6</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-api</artifactId>
<version>${jung.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-graph-impl</artifactId>
<version>${jung.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-algorithms</artifactId>
<version>${jung.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-io</artifactId>
<version>${jung.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jung</groupId>
<artifactId>jung-visualization</artifactId>
<version>${jung.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>io.socket</groupId>
<artifactId>socket.io-client</artifactId>
<version>0.8.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.jfoenix</groupId>
<artifactId>jfoenix</artifactId>
<version>8.0.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>8.8.3</version>
<configuration>
<mainClass>com.core.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,543 @@
package com.core;
import com.core.client.ICoreClient;
import com.core.client.rest.CoreRestClient;
import com.core.data.*;
import com.core.graph.NetworkGraph;
import com.core.ui.*;
import com.core.ui.dialogs.*;
import com.core.utils.ConfigUtils;
import com.core.utils.Configuration;
import com.core.utils.NodeTypeConfig;
import com.core.websocket.CoreWebSocket;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingNode;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.event.ItemEvent;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Data
public class Controller implements Initializable {
private static final Logger logger = LogManager.getLogger();
@FXML private StackPane stackPane;
@FXML private BorderPane borderPane;
@FXML private VBox top;
@FXML private VBox bottom;
@FXML private SwingNode swingNode;
@FXML private MenuItem saveXmlMenuItem;
@FXML private JFXProgressBar progressBar;
@FXML private CheckMenuItem throughputMenuItem;
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final Map<Integer, MobilityConfig> mobilityScripts = new HashMap<>();
private final Map<Integer, MobilityPlayerDialog> mobilityPlayerDialogs = new HashMap<>();
private Application application;
private JFXDecorator decorator;
private Stage window;
private Configuration configuration;
private Map<String, Set<String>> defaultServices = new HashMap<>();
// core client utilities
private ICoreClient coreClient = new CoreRestClient();
private CoreWebSocket coreWebSocket;
// ui elements
private NetworkGraph networkGraph = new NetworkGraph(this);
private AnnotationToolbar annotationToolbar = new AnnotationToolbar(networkGraph);
private NodeDetails nodeDetails = new NodeDetails(this);
private LinkDetails linkDetails = new LinkDetails(this);
private GraphToolbar graphToolbar = new GraphToolbar(this);
// dialogs
private SessionsDialog sessionsDialog = new SessionsDialog(this);
private ServiceDialog serviceDialog = new ServiceDialog(this);
private NodeServicesDialog nodeServicesDialog = new NodeServicesDialog(this);
private NodeEmaneDialog nodeEmaneDialog = new NodeEmaneDialog(this);
private NodeWlanDialog nodeWlanDialog = new NodeWlanDialog(this);
private ConfigDialog configDialog = new ConfigDialog(this);
private HooksDialog hooksDialog = new HooksDialog(this);
private MobilityDialog mobilityDialog = new MobilityDialog(this);
private ChartDialog chartDialog = new ChartDialog(this);
private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this);
private BackgroundDialog backgroundDialog = new BackgroundDialog(this);
private LocationDialog locationDialog = new LocationDialog(this);
private GeoDialog geoDialog = new GeoDialog(this);
private ConnectDialog connectDialog = new ConnectDialog(this);
private GuiPreferencesDialog guiPreferencesDialog = new GuiPreferencesDialog(this);
private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this);
public void connectToCore(String address, int port) {
coreWebSocket.stop();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
try {
coreWebSocket.start(address, port);
coreClient.setConnection(address, port);
initialJoin();
} catch (IOException | URISyntaxException ex) {
Toast.error(String.format("Connection failure: %s", ex.getMessage()), ex);
Platform.runLater(() -> connectDialog.showDialog());
}
});
}
private void initialJoin() throws IOException {
Map<String, List<String>> serviceGroups = coreClient.getServices();
logger.info("core services: {}", serviceGroups);
nodeServicesDialog.setServices(serviceGroups);
nodeTypeCreateDialog.setServices(serviceGroups);
logger.info("initial core session join");
List<SessionOverview> sessions = coreClient.getSessions();
logger.info("existing sessions: {}", sessions);
Integer sessionId;
if (sessions.isEmpty()) {
logger.info("creating initial session");
SessionOverview sessionOverview = coreClient.createSession();
sessionId = sessionOverview.getId();
Toast.info(String.format("Created Session %s", sessionId));
} else {
SessionOverview sessionOverview = sessions.get(0);
sessionId = sessionOverview.getId();
Toast.info(String.format("Joined Session %s", sessionId));
}
joinSession(sessionId);
// set emane models
List<String> emaneModels = coreClient.getEmaneModels();
nodeEmaneDialog.setModels(emaneModels);
}
public void joinSession(Integer sessionId) throws IOException {
// clear graph
networkGraph.reset();
// clear out any previously set information
mobilityPlayerDialogs.clear();
mobilityScripts.clear();
mobilityDialog.setNode(null);
Platform.runLater(() -> borderPane.setRight(null));
// get session to join
Session session = coreClient.getSession(sessionId);
SessionState sessionState = SessionState.get(session.getState());
// update client to use this session
coreClient.updateSession(sessionId);
coreClient.updateState(sessionState);
// display all nodes
logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session);
for (CoreNode node : session.getNodes()) {
NodeType nodeType = NodeType.find(node.getType(), node.getModel());
if (nodeType == null) {
logger.info(String.format("failed to find node type(%s) model(%s): %s",
node.getType(), node.getModel(), node.getName()));
continue;
}
node.setNodeType(nodeType);
networkGraph.addNode(node);
}
// display all links
for (CoreLink link : session.getLinks()) {
if (link.getInterfaceOne() != null || link.getInterfaceTwo() != null) {
link.setType(LinkTypes.WIRED.getValue());
}
networkGraph.addLink(link);
}
// refresh graph
networkGraph.getGraphViewer().repaint();
// update other components for new session
graphToolbar.setRunButton(coreClient.isRunning());
hooksDialog.updateHooks();
// update session default services
setCoreDefaultServices();
// retrieve current mobility script configurations and show dialogs
Map<Integer, MobilityConfig> mobilityConfigMap = coreClient.getMobilityConfigs();
mobilityScripts.putAll(mobilityConfigMap);
showMobilityScriptDialogs();
Platform.runLater(() -> decorator.setTitle(String.format("CORE (Session %s)", sessionId)));
}
public boolean startSession() throws IOException {
// force nodes to get latest positions
networkGraph.updatePositions();
// retrieve items for creation/start
Collection<CoreNode> nodes = networkGraph.getGraph().getVertices();
Collection<CoreLink> links = networkGraph.getGraph().getEdges();
List<Hook> hooks = hooksDialog.getHooks();
// start/create session
progressBar.setVisible(true);
boolean result = coreClient.start(nodes, links, hooks);
progressBar.setVisible(false);
if (result) {
showMobilityScriptDialogs();
saveXmlMenuItem.setDisable(false);
}
return result;
}
public boolean stopSession() throws IOException {
// clear out any drawn wireless links
List<CoreLink> wirelessLinks = networkGraph.getGraph().getEdges().stream()
.filter(CoreLink::isWireless)
.collect(Collectors.toList());
wirelessLinks.forEach(networkGraph::removeWirelessLink);
networkGraph.getGraphViewer().repaint();
// stop session
progressBar.setVisible(true);
boolean result = coreClient.stop();
progressBar.setVisible(false);
if (result) {
saveXmlMenuItem.setDisable(true);
}
return result;
}
public void handleThroughputs(Throughputs throughputs) {
for (InterfaceThroughput interfaceThroughput : throughputs.getInterfaces()) {
int nodeId = interfaceThroughput.getNode();
CoreNode node = networkGraph.getVertex(nodeId);
Collection<CoreLink> links = networkGraph.getGraph().getIncidentEdges(node);
int interfaceId = interfaceThroughput.getNodeInterface();
for (CoreLink link : links) {
if (nodeId == link.getNodeOne()) {
if (interfaceId == link.getInterfaceOne().getId()) {
link.setThroughput(interfaceThroughput.getThroughput());
}
} else {
if (interfaceId == link.getInterfaceTwo().getId()) {
link.setThroughput(interfaceThroughput.getThroughput());
}
}
}
}
networkGraph.getGraphViewer().repaint();
}
private void setCoreDefaultServices() {
try {
coreClient.setDefaultServices(defaultServices);
} catch (IOException ex) {
Toast.error("Error updating core default services", ex);
}
}
public void updateNodeTypes() {
graphToolbar.setupNodeTypes();
setCoreDefaultServices();
try {
ConfigUtils.save(configuration);
} catch (IOException ex) {
Toast.error("Error saving configuration", ex);
}
}
public void deleteNode(CoreNode node) {
networkGraph.removeNode(node);
CoreNode mobilityNode = mobilityDialog.getNode();
if (mobilityNode != null && mobilityNode.getId().equals(node.getId())) {
mobilityDialog.setNode(null);
}
}
void setWindow(Stage window) {
this.window = window;
sessionsDialog.setOwner(window);
hooksDialog.setOwner(window);
nodeServicesDialog.setOwner(window);
serviceDialog.setOwner(window);
nodeWlanDialog.setOwner(window);
nodeEmaneDialog.setOwner(window);
configDialog.setOwner(window);
mobilityDialog.setOwner(window);
nodeTypesDialog.setOwner(window);
backgroundDialog.setOwner(window);
locationDialog.setOwner(window);
connectDialog.setOwner(window);
guiPreferencesDialog.setOwner(window);
nodeTypeCreateDialog.setOwner(window);
}
private void showMobilityScriptDialogs() {
for (Map.Entry<Integer, MobilityConfig> entry : mobilityScripts.entrySet()) {
Integer nodeId = entry.getKey();
CoreNode node = networkGraph.getVertex(nodeId);
MobilityConfig mobilityConfig = entry.getValue();
Platform.runLater(() -> {
MobilityPlayerDialog mobilityPlayerDialog = new MobilityPlayerDialog(this, node);
mobilityPlayerDialog.setOwner(window);
mobilityPlayerDialogs.put(nodeId, mobilityPlayerDialog);
mobilityPlayerDialog.showDialog(mobilityConfig);
});
}
}
@FXML
private void onCoreMenuConnect(ActionEvent event) {
logger.info("showing connect!");
connectDialog.showDialog();
}
@FXML
private void onOptionsMenuNodeTypes(ActionEvent event) {
nodeTypesDialog.showDialog();
}
@FXML
private void onOptionsMenuBackground(ActionEvent event) {
backgroundDialog.showDialog();
}
@FXML
private void onOptionsMenuLocation(ActionEvent event) {
locationDialog.showDialog();
}
@FXML
private void onOptionsMenuPreferences(ActionEvent event) {
guiPreferencesDialog.showDialog();
}
@FXML
private void onHelpMenuWebsite(ActionEvent event) {
application.getHostServices().showDocument("https://github.com/coreemu/core");
}
@FXML
private void onHelpMenuDocumentation(ActionEvent event) {
application.getHostServices().showDocument("http://coreemu.github.io/core/");
}
@FXML
private void onHelpMenuMailingList(ActionEvent event) {
application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users");
}
@FXML
private void onOpenXmlAction() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open Session");
fileChooser.setInitialDirectory(new File(configuration.getXmlPath()));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml"));
try {
File file = fileChooser.showOpenDialog(window);
if (file != null) {
openXml(file);
}
} catch (IllegalArgumentException ex) {
Toast.error(String.format("Invalid XML directory: %s", configuration.getXmlPath()));
}
}
private void openXml(File file) {
logger.info("opening session xml: {}", file.getPath());
try {
SessionOverview sessionOverview = coreClient.openSession(file);
Integer sessionId = sessionOverview.getId();
joinSession(sessionId);
Toast.info(String.format("Joined Session %s", sessionId));
} catch (IOException ex) {
Toast.error("Error opening session xml", ex);
}
}
@FXML
private void onSaveXmlAction() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save Session");
fileChooser.setInitialFileName("session.xml");
fileChooser.setInitialDirectory(new File(configuration.getXmlPath()));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml"));
File file = fileChooser.showSaveDialog(window);
if (file != null) {
logger.info("saving session xml: {}", file.getPath());
try {
coreClient.saveSession(file);
} catch (IOException ex) {
Toast.error("Error saving session xml", ex);
}
}
}
@FXML
private void onSessionMenu(ActionEvent event) {
logger.info("sessions menu clicked");
try {
sessionsDialog.showDialog();
} catch (IOException ex) {
Toast.error("Error retrieving sessions", ex);
}
}
@FXML
private void onSessionNodesMenu(ActionEvent event) {
}
@FXML
private void onSessionHooksMenu(ActionEvent event) {
hooksDialog.showDialog();
}
@FXML
private void onSessionOptionsMenu(ActionEvent event) {
try {
List<ConfigGroup> configGroups = coreClient.getSessionConfig();
configDialog.showDialog("Session Options", configGroups, () -> {
List<ConfigOption> options = configDialog.getOptions();
try {
boolean result = coreClient.setSessionConfig(options);
if (result) {
Toast.info("Session options saved");
} else {
Toast.error("Failure to set session config");
}
} catch (IOException ex) {
logger.error("error getting session config");
}
});
} catch (IOException ex) {
logger.error("error getting session config");
}
}
@FXML
private void onTestMenuCharts(ActionEvent event) {
chartDialog.show();
}
@FXML
private void onTestMenuGeo(ActionEvent event) {
geoDialog.showDialog();
}
@Override
public void initialize(URL location, ResourceBundle resources) {
coreWebSocket = new CoreWebSocket(this);
configuration = ConfigUtils.load();
String address = configuration.getCoreAddress();
int port = configuration.getCorePort();
logger.info("core connection: {}:{}", address, port);
connectDialog.setAddress(address);
connectDialog.setPort(port);
connectToCore(address, port);
logger.info("controller initialize");
swingNode.setContent(networkGraph.getGraphViewer());
// update graph preferences
networkGraph.updatePreferences(configuration);
// set node types / default services
graphToolbar.setupNodeTypes();
defaultServices = configuration.getNodeTypeConfigs().stream()
.collect(Collectors.toMap(NodeTypeConfig::getModel, NodeTypeConfig::getServices));
// set graph toolbar
borderPane.setLeft(graphToolbar);
// setup snackbar
Toast.setSnackbarRoot(stackPane);
// setup throughput menu item
throughputMenuItem.setOnAction(event -> executorService.submit(new ChangeThroughputTask()));
// node details
networkGraph.getGraphViewer().getPickedVertexState().addItemListener(event -> {
CoreNode node = (CoreNode) event.getItem();
logger.info("picked: {}", node.getName());
if (event.getStateChange() == ItemEvent.SELECTED) {
Platform.runLater(() -> {
nodeDetails.setNode(node);
borderPane.setRight(nodeDetails);
});
} else {
Platform.runLater(() -> borderPane.setRight(null));
}
});
// edge details
networkGraph.getGraphViewer().getPickedEdgeState().addItemListener(event -> {
CoreLink link = (CoreLink) event.getItem();
logger.info("picked: {} - {}", link.getNodeOne(), link.getNodeTwo());
if (event.getStateChange() == ItemEvent.SELECTED) {
Platform.runLater(() -> {
linkDetails.setLink(link);
borderPane.setRight(linkDetails);
});
} else {
Platform.runLater(() -> borderPane.setRight(null));
}
});
}
private class ChangeThroughputTask extends Task<Boolean> {
@Override
protected Boolean call() throws Exception {
if (throughputMenuItem.isSelected()) {
return coreClient.startThroughput();
} else {
return coreClient.stopThroughput();
}
}
@Override
protected void succeeded() {
if (getValue()) {
if (throughputMenuItem.isSelected()) {
networkGraph.setShowThroughput(true);
} else {
networkGraph.setShowThroughput(false);
networkGraph.getGraph().getEdges().forEach(edge -> edge.setThroughput(0));
networkGraph.getGraphViewer().repaint();
}
} else {
Toast.error("Failure changing throughput");
}
}
@Override
protected void failed() {
Toast.error("Error changing throughput", new RuntimeException(getException()));
}
}
}

View file

@ -0,0 +1,72 @@
package com.core;
import com.core.utils.ConfigUtils;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.svg.SVGGlyphLoader;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Main extends Application {
private static final Path LOG_FILE = Paths.get(System.getProperty("user.home"), ".core", "core.log");
@Override
public void start(Stage window) throws Exception {
// set core dir property for logging
System.setProperty("core_log", LOG_FILE.toString());
// check for and create gui home directory
ConfigUtils.checkHomeDirectory();
// load svg icons
SVGGlyphLoader.loadGlyphsFont(getClass().getResourceAsStream("/icons/icomoon_material.svg"),
"icomoon.svg");
// load font
Font.loadFont(getClass().getResourceAsStream("/font/roboto/Roboto-Regular.ttf"), 10);
// load main fxml
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"));
Parent root = loader.load();
// window decorator
JFXDecorator decorator = new JFXDecorator(window, root);
decorator.setCustomMaximize(true);
decorator.setMaximized(true);
decorator.setTitle("CORE");
Image coreIcon = new Image(getClass().getResourceAsStream("/core-icon.png"));
decorator.setGraphic(new ImageView(coreIcon));
window.getIcons().add(coreIcon);
// create scene and set as current scene within window
Scene scene = new Scene(decorator);
scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm());
window.setScene(scene);
// update controller
Controller controller = loader.getController();
controller.setApplication(this);
controller.setWindow(window);
controller.setDecorator(decorator);
// configure window
window.setOnCloseRequest(event -> {
Platform.exit();
System.exit(0);
});
window.show();
}
public static void main(String[] args) {
launch(args);
}
}

View file

@ -0,0 +1,118 @@
package com.core.client;
import com.core.client.rest.ServiceFile;
import com.core.client.rest.WlanConfig;
import com.core.data.*;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ICoreClient {
void setConnection(String address, int port);
boolean isLocalConnection();
Integer currentSession();
boolean startThroughput() throws IOException;
boolean stopThroughput() throws IOException;
void updateSession(Integer sessionId);
void updateState(SessionState state);
SessionOverview createSession() throws IOException;
boolean deleteSession(Integer sessionId) throws IOException;
List<SessionOverview> getSessions() throws IOException;
Session getSession(Integer sessionId) throws IOException;
boolean start(Collection<CoreNode> nodes, Collection<CoreLink> links, List<Hook> hooks) throws IOException;
boolean stop() throws IOException;
boolean setState(SessionState state) throws IOException;
Map<String, List<String>> getServices() throws IOException;
Map<String, List<String>> getDefaultServices() throws IOException;
boolean setDefaultServices(Map<String, Set<String>> defaults) throws IOException;
CoreService getService(CoreNode node, String serviceName) throws IOException;
boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException;
String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException;
boolean startService(CoreNode node, String serviceName) throws IOException;
boolean stopService(CoreNode node, String serviceName) throws IOException;
boolean restartService(CoreNode node, String serviceName) throws IOException;
boolean validateService(CoreNode node, String serviceName) throws IOException;
boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException;
List<ConfigGroup> getEmaneConfig(CoreNode node) throws IOException;
List<String> getEmaneModels() throws IOException;
boolean setEmaneConfig(CoreNode node, List<ConfigOption> options) throws IOException;
List<ConfigGroup> getEmaneModelConfig(Integer id, String model) throws IOException;
boolean setEmaneModelConfig(Integer id, String model, List<ConfigOption> options) throws IOException;
boolean isRunning();
void saveSession(File file) throws IOException;
SessionOverview openSession(File file) throws IOException;
List<ConfigGroup> getSessionConfig() throws IOException;
boolean setSessionConfig(List<ConfigOption> configOptions) throws IOException;
boolean createNode(CoreNode node) throws IOException;
String nodeCommand(CoreNode node, String command) throws IOException;
boolean editNode(CoreNode node) throws IOException;
boolean deleteNode(CoreNode node) throws IOException;
boolean createLink(CoreLink link) throws IOException;
boolean editLink(CoreLink link) throws IOException;
boolean createHook(Hook hook) throws IOException;
List<Hook> getHooks() throws IOException;
WlanConfig getWlanConfig(CoreNode node) throws IOException;
boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException;
String getTerminalCommand(CoreNode node) throws IOException;
Map<Integer, MobilityConfig> getMobilityConfigs() throws IOException;
boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException;
MobilityConfig getMobilityConfig(CoreNode node) throws IOException;
boolean mobilityAction(CoreNode node, String action) throws IOException;
LocationConfig getLocationConfig() throws IOException;
boolean setLocationConfig(LocationConfig config) throws IOException;
}

View file

@ -0,0 +1,415 @@
package com.core.client.rest;
import com.core.client.ICoreClient;
import com.core.data.*;
import com.core.utils.WebUtils;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.util.*;
@Data
public class CoreRestClient implements ICoreClient {
private static final Logger logger = LogManager.getLogger();
private String address;
private int port;
private Integer sessionId;
private SessionState sessionState;
@Override
public void setConnection(String address, int port) {
this.address = address;
this.port = port;
}
@Override
public boolean isLocalConnection() {
return address.equals("127.0.0.1") || address.equals("localhost");
}
@Override
public Integer currentSession() {
return sessionId;
}
@Override
public void updateState(SessionState state) {
sessionState = state;
}
@Override
public void updateSession(Integer sessionId) {
this.sessionId = sessionId;
}
private String getUrl(String path) {
return String.format("http://%s:%s/%s", address, port, path);
}
@Override
public SessionOverview createSession() throws IOException {
String url = getUrl("sessions");
return WebUtils.post(url, SessionOverview.class);
}
@Override
public boolean deleteSession(Integer sessionId) throws IOException {
String path = String.format("sessions/%s", sessionId);
String url = getUrl(path);
return WebUtils.delete(url);
}
public Map<String, List<String>> getServices() throws IOException {
String url = getUrl("services");
GetServices getServices = WebUtils.getJson(url, GetServices.class);
return getServices.getGroups();
}
@Override
public Session getSession(Integer sessionId) throws IOException {
String path = String.format("sessions/%s", sessionId);
String url = getUrl(path);
return WebUtils.getJson(url, Session.class);
}
@Override
public List<SessionOverview> getSessions() throws IOException {
String url = getUrl("sessions");
GetSessions getSessions = WebUtils.getJson(url, GetSessions.class);
return getSessions.getSessions();
}
@Override
public boolean start(Collection<CoreNode> nodes, Collection<CoreLink> links, List<Hook> hooks) throws IOException {
boolean result = setState(SessionState.DEFINITION);
if (!result) {
return false;
}
result = setState(SessionState.CONFIGURATION);
if (!result) {
return false;
}
for (Hook hook : hooks) {
if (!createHook(hook)) {
return false;
}
}
for (CoreNode node : nodes) {
// must pre-configure wlan nodes, if not already
if (node.getNodeType().getValue() == NodeType.WLAN) {
WlanConfig config = getWlanConfig(node);
setWlanConfig(node, config);
}
if (!createNode(node)) {
return false;
}
}
for (CoreLink link : links) {
if (!createLink(link)) {
return false;
}
}
return setState(SessionState.INSTANTIATION);
}
@Override
public boolean stop() throws IOException {
return setState(SessionState.SHUTDOWN);
}
@Override
public boolean setState(SessionState state) throws IOException {
String url = getUrl(String.format("sessions/%s/state", sessionId));
Map<String, Integer> data = new HashMap<>();
data.put("state", state.getValue());
boolean result = WebUtils.putJson(url, data);
if (result) {
sessionState = state;
}
return result;
}
private boolean uploadFile(File file) throws IOException {
String url = getUrl("upload");
return WebUtils.postFile(url, file);
}
@Override
public boolean startThroughput() throws IOException {
String url = getUrl("throughput/start");
return WebUtils.putJson(url);
}
@Override
public boolean stopThroughput() throws IOException {
String url = getUrl("throughput/stop");
return WebUtils.putJson(url);
}
@Override
public Map<String, List<String>> getDefaultServices() throws IOException {
String url = getUrl(String.format("sessions/%s/services/default", sessionId));
GetDefaultServices getDefaultServices = WebUtils.getJson(url, GetDefaultServices.class);
return getDefaultServices.getDefaults();
}
@Override
public boolean setDefaultServices(Map<String, Set<String>> defaults) throws IOException {
String url = getUrl(String.format("sessions/%s/services/default", sessionId));
return WebUtils.postJson(url, defaults);
}
@Override
public CoreService getService(CoreNode node, String serviceName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName));
return WebUtils.getJson(url, CoreService.class);
}
@Override
public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName));
return WebUtils.putJson(url, service);
}
@Override
public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(),
serviceName));
Map<String, String> args = new HashMap<>();
args.put("file", fileName);
return WebUtils.getJson(url, String.class, args);
}
@Override
public boolean startService(CoreNode node, String serviceName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/start", sessionId, node.getId(),
serviceName));
return WebUtils.putJson(url);
}
@Override
public boolean stopService(CoreNode node, String serviceName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/stop", sessionId, node.getId(),
serviceName));
return WebUtils.putJson(url);
}
@Override
public boolean restartService(CoreNode node, String serviceName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/restart", sessionId, node.getId(),
serviceName));
return WebUtils.putJson(url);
}
@Override
public boolean validateService(CoreNode node, String serviceName) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/validate", sessionId, node.getId(),
serviceName));
return WebUtils.putJson(url);
}
@Override
public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(),
serviceName));
return WebUtils.putJson(url, serviceFile);
}
@Override
public List<String> getEmaneModels() throws IOException {
String url = getUrl(String.format("sessions/%s/emane/models", sessionId));
GetEmaneModels getEmaneModels = WebUtils.getJson(url, GetEmaneModels.class);
return getEmaneModels.getModels();
}
@Override
public List<ConfigGroup> getEmaneModelConfig(Integer id, String model) throws IOException {
String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId));
Map<String, String> args = new HashMap<>();
args.put("node", id.toString());
args.put("name", model);
GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args);
return getConfig.getGroups();
}
@Override
public List<ConfigGroup> getEmaneConfig(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/emane/config", sessionId));
Map<String, String> args = new HashMap<>();
args.put("node", node.getId().toString());
GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args);
return getConfig.getGroups();
}
@Override
public boolean setEmaneConfig(CoreNode node, List<ConfigOption> options) throws IOException {
String url = getUrl(String.format("sessions/%s/emane/config", sessionId));
SetEmaneConfig setEmaneConfig = new SetEmaneConfig();
setEmaneConfig.setNode(node.getId());
setEmaneConfig.setValues(options);
return WebUtils.putJson(url, setEmaneConfig);
}
@Override
public boolean setEmaneModelConfig(Integer id, String model, List<ConfigOption> options) throws IOException {
String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId));
SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig();
setEmaneModelConfig.setNode(id);
setEmaneModelConfig.setName(model);
setEmaneModelConfig.setValues(options);
return WebUtils.putJson(url, setEmaneModelConfig);
}
@Override
public boolean isRunning() {
return sessionState == SessionState.RUNTIME;
}
@Override
public void saveSession(File file) throws IOException {
String path = String.format("sessions/%s/xml", sessionId);
String url = getUrl(path);
WebUtils.getFile(url, file);
}
@Override
public SessionOverview openSession(File file) throws IOException {
String url = getUrl("sessions/xml");
return WebUtils.postFile(url, file, SessionOverview.class);
}
@Override
public List<ConfigGroup> getSessionConfig() throws IOException {
String url = getUrl(String.format("sessions/%s/options", sessionId));
GetConfig getConfig = WebUtils.getJson(url, GetConfig.class);
return getConfig.getGroups();
}
@Override
public boolean setSessionConfig(List<ConfigOption> configOptions) throws IOException {
String url = getUrl(String.format("sessions/%s/options", sessionId));
SetConfig setConfig = new SetConfig(configOptions);
return WebUtils.putJson(url, setConfig);
}
@Override
public LocationConfig getLocationConfig() throws IOException {
String url = getUrl(String.format("sessions/%s/location", sessionId));
return WebUtils.getJson(url, LocationConfig.class);
}
@Override
public boolean setLocationConfig(LocationConfig config) throws IOException {
String url = getUrl(String.format("sessions/%s/location", sessionId));
return WebUtils.putJson(url, config);
}
@Override
public String nodeCommand(CoreNode node, String command) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/command", sessionId, node.getId()));
return WebUtils.putJson(url, command, String.class);
}
@Override
public boolean createNode(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes", sessionId));
return WebUtils.postJson(url, node);
}
@Override
public boolean editNode(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s", sessionId, node.getId()));
return WebUtils.putJson(url, node);
}
@Override
public boolean deleteNode(CoreNode node) throws IOException {
String url = getUrl(String.format("/sessions/%s/nodes/%s", sessionId, node.getId()));
return WebUtils.delete(url);
}
@Override
public boolean createLink(CoreLink link) throws IOException {
String url = getUrl(String.format("sessions/%s/links", sessionId));
return WebUtils.postJson(url, link);
}
@Override
public boolean editLink(CoreLink link) throws IOException {
String url = getUrl(String.format("sessions/%s/links", sessionId));
return WebUtils.putJson(url, link);
}
@Override
public boolean createHook(Hook hook) throws IOException {
String url = getUrl(String.format("sessions/%s/hooks", sessionId));
return WebUtils.postJson(url, hook);
}
@Override
public List<Hook> getHooks() throws IOException {
String url = getUrl(String.format("sessions/%s/hooks", sessionId));
GetHooks getHooks = WebUtils.getJson(url, GetHooks.class);
return getHooks.getHooks();
}
@Override
public WlanConfig getWlanConfig(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId()));
return WebUtils.getJson(url, WlanConfig.class);
}
@Override
public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId()));
return WebUtils.putJson(url, config);
}
@Override
public String getTerminalCommand(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", sessionId, node.getId()));
return WebUtils.getJson(url, String.class);
}
@Override
public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException {
boolean uploaded = uploadFile(config.getScriptFile());
if (!uploaded) {
throw new IOException("failed to upload mobility script");
}
String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId()));
config.setFile(config.getScriptFile().getName());
return WebUtils.postJson(url, config);
}
@Override
public Map<Integer, MobilityConfig> getMobilityConfigs() throws IOException {
String url = getUrl(String.format("sessions/%s/mobility/configs", sessionId));
GetMobilityConfigs getMobilityConfigs = WebUtils.getJson(url, GetMobilityConfigs.class);
return getMobilityConfigs.getConfigurations();
}
@Override
public MobilityConfig getMobilityConfig(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId()));
return WebUtils.getJson(url, MobilityConfig.class);
}
@Override
public boolean mobilityAction(CoreNode node, String action) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action));
return WebUtils.putJson(url);
}
}

View file

@ -0,0 +1,12 @@
package com.core.client.rest;
import com.core.data.ConfigGroup;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class GetConfig {
private List<ConfigGroup> groups = new ArrayList<>();
}

View file

@ -0,0 +1,16 @@
package com.core.client.rest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GetDefaultServices {
private Map<String, List<String>> defaults = new HashMap<>();
}

View file

@ -0,0 +1,11 @@
package com.core.client.rest;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class GetEmaneModels {
private List<String> models = new ArrayList<>();
}

View file

@ -0,0 +1,12 @@
package com.core.client.rest;
import com.core.data.Hook;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class GetHooks {
private List<Hook> hooks = new ArrayList<>();
}

View file

@ -0,0 +1,12 @@
package com.core.client.rest;
import com.core.data.MobilityConfig;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class GetMobilityConfigs {
private Map<Integer, MobilityConfig> configurations = new HashMap<>();
}

View file

@ -0,0 +1,13 @@
package com.core.client.rest;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@NoArgsConstructor
public class GetServices {
private Map<String, List<String>> groups;
}

View file

@ -0,0 +1,14 @@
package com.core.client.rest;
import com.core.data.SessionOverview;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class GetSessions {
private List<SessionOverview> sessions = new ArrayList<>();
}

View file

@ -0,0 +1,13 @@
package com.core.client.rest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceFile {
private String name;
private String data;
}

View file

@ -0,0 +1,16 @@
package com.core.client.rest;
import com.core.data.ConfigOption;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SetConfig {
private List<ConfigOption> values = new ArrayList<>();
}

View file

@ -0,0 +1,13 @@
package com.core.client.rest;
import com.core.data.ConfigOption;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class SetEmaneConfig {
private Integer node;
private List<ConfigOption> values = new ArrayList<>();
}

View file

@ -0,0 +1,14 @@
package com.core.client.rest;
import com.core.data.ConfigOption;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class SetEmaneModelConfig {
private Integer node;
private String name;
private List<ConfigOption> values = new ArrayList<>();
}

View file

@ -0,0 +1,12 @@
package com.core.client.rest;
import lombok.Data;
@Data
public class WlanConfig {
private String range;
private String bandwidth;
private String jitter;
private String delay;
private String error;
}

View file

@ -0,0 +1,9 @@
package com.core.data;
import lombok.Data;
@Data
public class BridgeThroughput {
private int node;
private Double throughput;
}

View file

@ -0,0 +1,40 @@
package com.core.data;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public enum ConfigDataType {
UINT8(1),
UINT16(2),
UINT32(3),
UINT64(4),
INT8(5),
INT16(6),
INT32(7),
INT64(8),
FLOAT(9),
STRING(10),
BOOL(11);
private static final Map<Integer, ConfigDataType> LOOKUP = new HashMap<>();
static {
Arrays.stream(ConfigDataType.values()).forEach(x -> LOOKUP.put(x.getValue(), x));
}
private final int value;
ConfigDataType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static ConfigDataType get(int value) {
return LOOKUP.get(value);
}
}

View file

@ -0,0 +1,12 @@
package com.core.data;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ConfigGroup {
private String name;
private List<ConfigOption> options = new ArrayList<>();
}

View file

@ -0,0 +1,15 @@
package com.core.data;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ConfigOption {
private String label;
private String name;
private String value;
private Integer type;
private List<String> select = new ArrayList<>();
}

View file

@ -0,0 +1,19 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonSetter;
import lombok.Data;
@Data
public class CoreEvent {
private Integer session;
private Integer node;
private String name;
private Double time;
private EventType eventType;
private String data;
@JsonSetter("event_type")
public void setEventType(int value) {
eventType = EventType.get(value);
}
}

View file

@ -0,0 +1,19 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CoreInterface {
private Integer id;
private String name;
private String mac;
private String ip4;
@JsonProperty("ip4mask")
private Integer ip4Mask;
private String ip6;
@JsonProperty("ip6mask")
private String ip6Mask;
}

View file

@ -0,0 +1,56 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class CoreLink {
@EqualsAndHashCode.Include
private Integer id;
@JsonIgnore
private Float weight = 1.0f;
@JsonIgnore
private boolean loaded = true;
@JsonIgnore
private double throughput;
@JsonIgnore
private boolean visible = true;
@JsonProperty("message_type")
private Integer messageType;
private Integer type = 1;
@JsonProperty("node_one")
private Integer nodeOne;
@JsonProperty("node_two")
private Integer nodeTwo;
@JsonProperty("interface_one")
private CoreInterface interfaceOne;
@JsonProperty("interface_two")
private CoreInterface interfaceTwo;
private CoreLinkOptions options = new CoreLinkOptions();
public CoreLink(Integer id) {
this.id = id;
this.weight = (float) id;
this.loaded = false;
}
public boolean isWireless() {
return interfaceOne == null && interfaceTwo == null;
}
}

View file

@ -0,0 +1,21 @@
package com.core.data;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CoreLinkOptions {
private String opaque;
private Integer session;
private Double jitter;
private Integer key;
private Double mburst;
private Double mer;
private Double per;
private Double bandwidth;
private Double burst;
private Double delay;
private Double dup;
private Integer unidirectional;
}

View file

@ -0,0 +1,59 @@
package com.core.data;
import com.core.graph.RadioIcon;
import com.core.utils.IconUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import edu.uci.ics.jung.visualization.LayeredIcon;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.Set;
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class CoreNode {
private static final Logger logger = LogManager.getLogger();
@EqualsAndHashCode.Include
private Integer id;
private String name;
private Integer type;
private String model;
private Position position = new Position();
private Set<String> services = new HashSet<>();
private String emane;
private String url;
@JsonIgnore
private NodeType nodeType;
@JsonIgnore
private String icon;
@JsonIgnore
private boolean loaded = true;
@JsonIgnore
private LayeredIcon graphIcon;
@JsonIgnore
private RadioIcon radioIcon = new RadioIcon();
public CoreNode(Integer id) {
this.id = id;
this.name = String.format("Node%s", this.id);
this.loaded = false;
}
public void setNodeType(NodeType nodeType) {
type = nodeType.getValue();
model = nodeType.getModel();
icon = nodeType.getIcon();
if (icon.startsWith("file:")) {
graphIcon = IconUtils.getExternalLayeredIcon(icon);
} else {
graphIcon = IconUtils.getLayeredIcon(icon);
}
graphIcon.add(radioIcon);
this.nodeType = nodeType;
}
}

View file

@ -0,0 +1,23 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class CoreService {
private List<String> executables = new ArrayList<>();
private List<String> dependencies = new ArrayList<>();
private List<String> dirs = new ArrayList<>();
private List<String> configs = new ArrayList<>();
private List<String> startup = new ArrayList<>();
private List<String> validate = new ArrayList<>();
@JsonProperty("validation_mode")
private String validationMode;
@JsonProperty("validation_timer")
private String validationTimer;
private List<String> shutdown = new ArrayList<>();
private String meta;
}

View file

@ -0,0 +1,45 @@
package com.core.data;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public enum EventType {
NONE(0),
DEFINITION_STATE(1),
CONFIGURATION_STATE(2),
INSTANTIATION_STATE(3),
RUNTIME_STATE(4),
DATACOLLECT_STATE(5),
SHUTDOWN_STATE(6),
START(7),
STOP(8),
PAUSE(9),
RESTART(10),
FILE_OPEN(11),
FILE_SAVE(12),
SCHEDULED(13),
RECONFIGURE(14),
INSTANTIATION_COMPLETE(15);
private static final Map<Integer, EventType> LOOKUP = new HashMap<>();
static {
Arrays.stream(EventType.values()).forEach(x -> LOOKUP.put(x.getValue(), x));
}
private final int value;
EventType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static EventType get(int value) {
return LOOKUP.get(value);
}
}

View file

@ -0,0 +1,13 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
public class Hook {
private String file;
private Integer state;
@JsonIgnore
private String stateDisplay;
private String data;
}

View file

@ -0,0 +1,12 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class InterfaceThroughput {
private int node;
@JsonProperty("interface")
private int nodeInterface;
private double throughput;
}

View file

@ -0,0 +1,31 @@
package com.core.data;
import java.util.HashMap;
import java.util.Map;
public enum LinkTypes {
WIRELESS(0),
WIRED(1);
private static final Map<Integer, LinkTypes> LOOKUP = new HashMap<>();
static {
for (LinkTypes state : LinkTypes.values()) {
LOOKUP.put(state.getValue(), state);
}
}
private final int value;
LinkTypes(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public static LinkTypes get(int value) {
return LOOKUP.get(value);
}
}

View file

@ -0,0 +1,10 @@
package com.core.data;
import lombok.Data;
@Data
public class Location {
private Double latitude = 0.0;
private Double longitude = 0.0;
private Double altitude = 0.0;
}

View file

@ -0,0 +1,10 @@
package com.core.data;
import lombok.Data;
@Data
public class LocationConfig {
private Position position = new Position();
private Location location = new Location();
private Double scale;
}

View file

@ -0,0 +1,36 @@
package com.core.data;
import java.util.HashMap;
import java.util.Map;
public enum MessageFlags {
ADD(1),
DELETE(2),
CRI(4),
LOCAL(8),
STRING(16),
TEXT(32),
TTY(64);
private static final Map<Integer, MessageFlags> LOOKUP = new HashMap<>();
static {
for (MessageFlags state : MessageFlags.values()) {
LOOKUP.put(state.getValue(), state);
}
}
private final int value;
MessageFlags(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public static MessageFlags get(int value) {
return LOOKUP.get(value);
}
}

View file

@ -0,0 +1,25 @@
package com.core.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.File;
@Data
public class MobilityConfig {
private String file;
@JsonIgnore
private File scriptFile;
@JsonProperty("refresh_ms")
private Integer refresh;
private String loop;
private String autostart;
private String map;
@JsonProperty("script_start")
private String startScript;
@JsonProperty("script_pause")
private String pauseScript;
@JsonProperty("script_stop")
private String stopScript;
}

View file

@ -0,0 +1,86 @@
package com.core.data;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class NodeType {
private static final Logger logger = LogManager.getLogger();
private static final AtomicInteger idGenerator = new AtomicInteger(0);
private static final Map<Integer, NodeType> ID_LOOKUP = new HashMap<>();
public static final int DEFAULT = 0;
public static final int SWITCH = 4;
public static final int HUB = 5;
public static final int WLAN = 6;
public static final int EMANE = 10;
@EqualsAndHashCode.Include
private final int id;
private final int value;
private final Set<String> services = new TreeSet<>();
private String display;
private String model;
private String icon;
// PHYSICAL = 1
// RJ45 = 7
// TUNNEL = 8
// KTUNNEL = 9
// EMANE = 10
// TAP_BRIDGE = 11
// PEER_TO_PEER = 12
// CONTROL_NET = 13
// EMANE_NET = 14;
static {
add(new NodeType(SWITCH, "lanswitch", "Switch", "/icons/switch-100.png"));
add(new NodeType(HUB, "hub", "Hub", "/icons/hub-100.png"));
add(new NodeType(WLAN, "wlan", "WLAN", "/icons/wlan-100.png"));
add(new NodeType(EMANE, "wlan", "EMANE", "/icons/emane-100.png"));
}
public NodeType(int value, String model, String display, String icon) {
this.id = idGenerator.incrementAndGet();
this.value = value;
this.model = model;
this.display = display;
this.icon = icon;
}
public static void add(NodeType nodeType) {
ID_LOOKUP.put(nodeType.getId(), nodeType);
}
public static void remove(NodeType nodeType) {
ID_LOOKUP.remove(nodeType.getId());
}
public static NodeType get(Integer id) {
return ID_LOOKUP.get(id);
}
public static Collection<NodeType> getAll() {
return ID_LOOKUP.values();
}
public static NodeType find(Integer type, String model) {
return ID_LOOKUP.values().stream()
.filter(nodeType -> {
boolean sameType = nodeType.getValue() == type;
boolean sameModel;
if (model != null) {
sameModel = model.equals(nodeType.getModel());
} else {
sameModel = nodeType.getModel() == null;
}
return sameType && sameModel;
})
.findFirst().orElse(null);
}
}

View file

@ -0,0 +1,12 @@
package com.core.data;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Position {
private Double x;
private Double y;
private Double z;
}

View file

@ -0,0 +1,15 @@
package com.core.data;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class Session {
private Integer state;
private List<CoreNode> nodes = new ArrayList<>();
private List<CoreLink> links = new ArrayList<>();
}

View file

@ -0,0 +1,13 @@
package com.core.data;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class SessionOverview {
private Integer id;
private Integer state;
private Integer nodes = 0;
private String url;
}

View file

@ -0,0 +1,38 @@
package com.core.data;
import java.util.HashMap;
import java.util.Map;
public enum SessionState {
DEFINITION(1),
CONFIGURATION(2),
INSTANTIATION(3),
RUNTIME(4),
DATA_COLLECT(5),
SHUTDOWN(6),
START(7),
STOP(8),
PAUSE(9);
private static final Map<Integer, SessionState> LOOKUP = new HashMap<>();
static {
for (SessionState state : SessionState.values()) {
LOOKUP.put(state.getValue(), state);
}
}
private final int value;
SessionState(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public static SessionState get(int value) {
return LOOKUP.get(value);
}
}

View file

@ -0,0 +1,12 @@
package com.core.data;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class Throughputs {
private List<InterfaceThroughput> interfaces = new ArrayList<>();
private List<BridgeThroughput> bridges = new ArrayList<>();
}

View file

@ -0,0 +1,11 @@
package com.core.datavis;
import lombok.Data;
@Data
public class CoreGraph {
private String title;
private CoreGraphAxis xAxis;
private CoreGraphAxis yAxis;
private GraphType graphType;
}

View file

@ -0,0 +1,15 @@
package com.core.datavis;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CoreGraphAxis {
private String label;
private Double lower;
private Double upper;
private Double tick;
}

View file

@ -0,0 +1,15 @@
package com.core.datavis;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CoreGraphData {
private String name;
private Double x;
private Double y;
private Double weight;
}

View file

@ -0,0 +1,157 @@
package com.core.datavis;
import javafx.scene.chart.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class CoreGraphWrapper {
private static final Logger logger = LogManager.getLogger();
private final GraphType graphType;
private PieChart pieChart;
private final Map<String, PieChart.Data> pieData = new HashMap<>();
private BarChart<String, Number> barChart;
private final Map<String, XYChart.Data<String, Number>> barMap = new HashMap<>();
private XYChart<Number, Number> xyChart;
private final XYChart.Series<Number, Number> series = new XYChart.Series<>();
private final XYChart.Series<String, Number> barSeries = new XYChart.Series<>();
private AtomicInteger timeValue = new AtomicInteger(0);
public CoreGraphWrapper(CoreGraph coreGraph) {
graphType = coreGraph.getGraphType();
createChart(coreGraph);
}
public Chart getChart() {
switch (graphType) {
case PIE:
return pieChart;
case BAR:
return barChart;
default:
return xyChart;
}
}
public void add(CoreGraphData coreGraphData) {
switch (graphType) {
case PIE:
case BAR:
add(coreGraphData.getName(), coreGraphData.getY());
break;
case TIME:
add(coreGraphData.getY());
break;
case BUBBLE:
add(coreGraphData.getX(), coreGraphData.getY(), coreGraphData.getWeight());
break;
default:
add(coreGraphData.getX(), coreGraphData.getY());
}
}
public void add(String name, double value) {
if (GraphType.PIE == graphType) {
PieChart.Data data = pieData.computeIfAbsent(name, x -> {
PieChart.Data newData = new PieChart.Data(x, value);
pieChart.getData().add(newData);
return newData;
});
data.setPieValue(value);
} else {
XYChart.Data<String, Number> data = barMap.computeIfAbsent(name, x -> {
XYChart.Data<String, Number> newData = new XYChart.Data<>(name, value);
barSeries.getData().add(newData);
return newData;
});
data.setYValue(value);
}
}
public void add(Number y) {
series.getData().add(new XYChart.Data<>(timeValue.getAndIncrement(), y));
}
public void add(Number x, Number y) {
series.getData().add(new XYChart.Data<>(x, y));
}
public void add(Number x, Number y, Number weight) {
series.getData().add(new XYChart.Data<>(x, y, weight));
}
private NumberAxis getAxis(CoreGraphAxis graphAxis) {
return new NumberAxis(graphAxis.getLabel(), graphAxis.getLower(),
graphAxis.getUpper(), graphAxis.getTick());
}
private void createChart(CoreGraph coreGraph) {
NumberAxis xAxis;
NumberAxis yAxis;
switch (coreGraph.getGraphType()) {
case AREA:
xAxis = getAxis(coreGraph.getXAxis());
yAxis = getAxis(coreGraph.getYAxis());
xyChart = new AreaChart<>(xAxis, yAxis);
xyChart.setTitle(coreGraph.getTitle());
xyChart.setLegendVisible(false);
xyChart.getData().add(series);
break;
case TIME:
xAxis = new NumberAxis();
xAxis.setLabel(coreGraph.getXAxis().getLabel());
xAxis.setTickUnit(1);
xAxis.setLowerBound(0);
yAxis = getAxis(coreGraph.getYAxis());
xyChart = new LineChart<>(xAxis, yAxis);
xyChart.setTitle(coreGraph.getTitle());
xyChart.setLegendVisible(false);
xyChart.getData().add(series);
break;
case LINE:
xAxis = getAxis(coreGraph.getXAxis());
yAxis = getAxis(coreGraph.getYAxis());
xyChart = new LineChart<>(xAxis, yAxis);
xyChart.setTitle(coreGraph.getTitle());
xyChart.setLegendVisible(false);
xyChart.getData().add(series);
break;
case BUBBLE:
xAxis = getAxis(coreGraph.getXAxis());
yAxis = getAxis(coreGraph.getYAxis());
xyChart = new BubbleChart<>(xAxis, yAxis);
xyChart.setTitle(coreGraph.getTitle());
xyChart.setLegendVisible(false);
xyChart.getData().add(series);
break;
case SCATTER:
xAxis = getAxis(coreGraph.getXAxis());
yAxis = getAxis(coreGraph.getYAxis());
xyChart = new ScatterChart<>(xAxis, yAxis);
xyChart.setTitle(coreGraph.getTitle());
xyChart.setLegendVisible(false);
xyChart.getData().add(series);
break;
case PIE:
pieChart = new PieChart();
pieChart.setTitle(coreGraph.getTitle());
break;
case BAR:
CategoryAxis categoryAxis = new CategoryAxis();
categoryAxis.setLabel(coreGraph.getXAxis().getLabel());
yAxis = getAxis(coreGraph.getYAxis());
barChart = new BarChart<>(categoryAxis, yAxis);
barChart.setLegendVisible(false);
barChart.setTitle(coreGraph.getTitle());
barChart.getData().add(barSeries);
break;
default:
throw new IllegalArgumentException(String.format("unknown graph type: %s",
coreGraph.getGraphType()));
}
}
}

View file

@ -0,0 +1,11 @@
package com.core.datavis;
public enum GraphType {
PIE,
LINE,
TIME,
AREA,
BAR,
SCATTER,
BUBBLE
}

View file

@ -0,0 +1,22 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreNode;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
abstract class AbstractNodeContextMenu extends GraphContextMenu {
final CoreNode coreNode;
AbstractNodeContextMenu(Controller controller, CoreNode coreNode) {
super(controller);
this.coreNode = coreNode;
}
void addMenuItem(String text, EventHandler<ActionEvent> handler) {
MenuItem menuItem = new MenuItem(text);
menuItem.setOnAction(handler);
getItems().add(menuItem);
}
}

View file

@ -0,0 +1,51 @@
package com.core.graph;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.nio.file.Paths;
public class BackgroundPaintable<V, E> implements VisualizationViewer.Paintable {
private final ImageIcon imageIcon;
private final VisualizationViewer<V, E> vv;
private final String imagePath;
public BackgroundPaintable(String imagePath, VisualizationViewer<V, E> vv) throws IOException {
this.imagePath = imagePath;
Image image = ImageIO.read(Paths.get(imagePath).toFile());
imageIcon = new ImageIcon(image);
this.vv = vv;
}
public String getImage() {
return imagePath;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform oldXform = g2d.getTransform();
AffineTransform lat =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform();
AffineTransform vat =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform();
AffineTransform at = new AffineTransform();
at.concatenate(g2d.getTransform());
at.concatenate(vat);
at.concatenate(lat);
g2d.setTransform(at);
g.drawImage(imageIcon.getImage(), 0, 0,
imageIcon.getIconWidth(), imageIcon.getIconHeight(), vv);
g2d.setTransform(oldXform);
}
@Override
public boolean useTransform() {
return false;
}
}

View file

@ -0,0 +1,41 @@
package com.core.graph;
import com.core.data.CoreInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
public class CoreAddresses {
private static final Logger logger = LogManager.getLogger();
public static final int IP4_MASK = 24;
public static final int IP4_INDEX = (IP4_MASK / 8) - 1;
private String ip4Base;
public CoreAddresses(String ip4Base) {
this.ip4Base = ip4Base;
}
public int getSubnet(Collection<CoreInterface> nodeOneInterfaces, Collection<CoreInterface> nodeTwoInterfaces) {
int subOne = getMaxSubnet(nodeOneInterfaces);
int subTwo = getMaxSubnet(nodeTwoInterfaces);
logger.info("next subnet: {} - {}", subOne, subTwo);
return Math.max(subOne, subTwo) + 1;
}
private int getMaxSubnet(Collection<CoreInterface> coreInterfaces) {
int sub = 0;
for (CoreInterface coreInterface : coreInterfaces) {
String[] values = coreInterface.getIp4().split("\\.");
int currentSub = Integer.parseInt(values[IP4_INDEX]);
logger.info("checking {} value {}", coreInterface.getIp4(), currentSub);
sub = Math.max(currentSub, sub);
}
return sub;
}
public String getIp4Address(int sub, int id) {
return String.format("%s.%s.%s", ip4Base, sub, id);
}
}

View file

@ -0,0 +1,57 @@
package com.core.graph;
import com.core.Controller;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin;
import edu.uci.ics.jung.visualization.annotations.Annotation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.RectangularShape;
public class CoreAnnotatingGraphMousePlugin<V, E> extends AnnotatingGraphMousePlugin<V, E> {
private static final Logger logger = LogManager.getLogger();
private final Controller controller;
private JFrame frame = new JFrame();
public CoreAnnotatingGraphMousePlugin(Controller controller, RenderContext<V, E> renderContext) {
super(renderContext);
this.controller = controller;
frame.setVisible(false);
frame.setAlwaysOnTop(true);
}
@Override
public void mouseReleased(MouseEvent e) {
VisualizationViewer<V, E> vv = (VisualizationViewer) e.getSource();
if (e.isPopupTrigger()) {
frame.setLocationRelativeTo(vv);
String annotationString = JOptionPane.showInputDialog(frame, "Annotation:",
"Annotation Label", JOptionPane.PLAIN_MESSAGE);
if (annotationString != null && annotationString.length() > 0) {
Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(this.down);
Annotation<String> annotation = new Annotation(annotationString, this.layer,
this.annotationColor, this.fill, p);
this.annotationManager.add(this.layer, annotation);
}
} else if (e.getModifiers() == this.modifiers && this.down != null) {
Point2D out = e.getPoint();
RectangularShape arect = (RectangularShape) this.rectangularShape.clone();
arect.setFrameFromDiagonal(this.down, out);
Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect);
Annotation<Shape> annotation = new Annotation(s, this.layer, this.annotationColor, this.fill, out);
this.annotationManager.add(this.layer, annotation);
}
this.down = null;
vv.removePostRenderPaintable(this.lensPaintable);
vv.repaint();
}
}

View file

@ -0,0 +1,17 @@
package com.core.graph;
import com.core.Controller;
import com.google.common.base.Supplier;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
public class CoreEditingModalGraphMouse<V, E> extends EditingModalGraphMouse<V, E> {
public CoreEditingModalGraphMouse(Controller controller, NetworkGraph networkGraph,
RenderContext<V, E> rc, Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
super(rc, vertexFactory, edgeFactory);
remove(annotatingPlugin);
remove(popupEditingPlugin);
annotatingPlugin = new CoreAnnotatingGraphMousePlugin<>(controller, rc);
popupEditingPlugin = new CorePopupGraphMousePlugin<>(controller, networkGraph, vertexFactory, edgeFactory);
}
}

View file

@ -0,0 +1,31 @@
package com.core.graph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.ObservableGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class CoreObservableGraph<V, E> extends ObservableGraph<V, E> {
private static final Logger logger = LogManager.getLogger();
public CoreObservableGraph(Graph<V, E> graph) {
super(graph);
}
@Override
public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
if (v1 == null || v2 == null) {
return false;
}
return super.addEdge(e, v1, v2, edgeType);
}
@Override
public boolean addEdge(E e, V v1, V v2) {
if (v1 == null || v2 == null) {
return false;
}
return super.addEdge(e, v1, v2);
}
}

View file

@ -0,0 +1,78 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreLink;
import com.core.data.CoreNode;
import com.core.data.NodeType;
import com.google.common.base.Supplier;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.control.EditingPopupGraphMousePlugin;
import javafx.application.Platform;
import javafx.scene.control.ContextMenu;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
public class CorePopupGraphMousePlugin<V, E> extends EditingPopupGraphMousePlugin<V, E> {
private static final Logger logger = LogManager.getLogger();
private final Controller controller;
private final NetworkGraph networkGraph;
private final Layout<CoreNode, CoreLink> graphLayout;
private final GraphElementAccessor<CoreNode, CoreLink> pickSupport;
public CorePopupGraphMousePlugin(Controller controller, NetworkGraph networkGraph,
Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
super(vertexFactory, edgeFactory);
this.controller = controller;
this.networkGraph = networkGraph;
graphLayout = this.networkGraph.getGraphLayout();
pickSupport = this.networkGraph.getGraphViewer().getPickSupport();
}
@Override
protected void handlePopup(MouseEvent e) {
logger.info("showing popup!");
final Point2D p = e.getPoint();
final CoreNode node = pickSupport.getVertex(graphLayout, p.getX(), p.getY());
final CoreLink link = pickSupport.getEdge(graphLayout, p.getX(), p.getY());
final ContextMenu contextMenu;
if (node != null) {
contextMenu = handleNodeContext(node);
} else if (link != null) {
contextMenu = new LinkContextMenu(controller, link);
} else {
contextMenu = new ContextMenu();
}
if (!contextMenu.getItems().isEmpty()) {
logger.info("showing context menu");
Platform.runLater(() -> contextMenu.show(controller.getWindow(),
e.getXOnScreen(), e.getYOnScreen()));
}
}
private ContextMenu handleNodeContext(final CoreNode node) {
ContextMenu contextMenu = new ContextMenu();
switch (node.getType()) {
case NodeType.DEFAULT:
contextMenu = new NodeContextMenu(controller, node);
break;
case NodeType.WLAN:
contextMenu = new WlanContextMenu(controller, node);
break;
case NodeType.EMANE:
contextMenu = new EmaneContextMenu(controller, node);
break;
default:
logger.warn("no context menu for node: {}", node.getType());
break;
}
return contextMenu;
}
}

View file

@ -0,0 +1,43 @@
package com.core.graph;
import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
public class CoreVertexLabelRenderer extends DefaultVertexLabelRenderer {
private Color foregroundColor = Color.WHITE;
private Color backgroundColor = Color.BLACK;
CoreVertexLabelRenderer() {
super(Color.YELLOW);
}
public void setColors(Color foregroundColor, Color backgroundColor) {
this.foregroundColor = foregroundColor;
this.backgroundColor = backgroundColor;
}
@Override
public <V> Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex) {
super.setForeground(foregroundColor);
if (isSelected) {
this.setForeground(this.pickedVertexLabelColor);
}
super.setBackground(backgroundColor);
if (font != null) {
this.setFont(font);
} else {
this.setFont(vv.getFont());
}
this.setIcon(null);
EmptyBorder padding = new EmptyBorder(5, 5, 5, 5);
this.setBorder(padding);
this.setValue(value);
setFont(getFont().deriveFont(Font.BOLD));
return this;
}
}

View file

@ -0,0 +1,26 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.ui.dialogs.MobilityPlayerDialog;
class EmaneContextMenu extends AbstractNodeContextMenu {
EmaneContextMenu(Controller controller, CoreNode coreNode) {
super(controller, coreNode);
setup();
}
private void setup() {
addMenuItem("EMANE Settings", event -> controller.getNodeEmaneDialog().showDialog(coreNode));
if (controller.getCoreClient().isRunning()) {
MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId());
if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) {
addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show());
}
} else {
addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode));
addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode));
addMenuItem("Delete Node", event -> controller.deleteNode(coreNode));
}
}
}

View file

@ -0,0 +1,22 @@
package com.core.graph;
import com.core.Controller;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
abstract class GraphContextMenu extends ContextMenu {
final Controller controller;
GraphContextMenu(Controller controller) {
super();
this.controller = controller;
}
void addMenuItem(String text, EventHandler<ActionEvent> handler) {
MenuItem menuItem = new MenuItem(text);
menuItem.setOnAction(handler);
getItems().add(menuItem);
}
}

View file

@ -0,0 +1,21 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreLink;
class LinkContextMenu extends GraphContextMenu {
final CoreLink coreLink;
LinkContextMenu(Controller controller, CoreLink coreLink) {
super(controller);
this.coreLink = coreLink;
setup();
}
private void setup() {
if (!controller.getCoreClient().isRunning()) {
addMenuItem("Delete Link",
event -> controller.getNetworkGraph().removeLink(coreLink));
}
}
}

View file

@ -0,0 +1,492 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.*;
import com.core.ui.Toast;
import com.core.ui.dialogs.TerminalDialog;
import com.core.utils.Configuration;
import com.core.utils.IconUtils;
import com.google.common.base.Supplier;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.ObservableGraph;
import edu.uci.ics.jung.graph.event.GraphEvent;
import edu.uci.ics.jung.graph.event.GraphEventListener;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.annotations.AnnotationControls;
import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
import edu.uci.ics.jung.visualization.control.GraphMouseListener;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import javafx.application.Platform;
import lombok.Data;
import org.apache.commons.net.util.SubnetUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Data
public class NetworkGraph {
private static final Logger logger = LogManager.getLogger();
private static final int EDGE_LABEL_OFFSET = -5;
private static final int EDGE_WIDTH = 5;
private Controller controller;
private ObservableGraph<CoreNode, CoreLink> graph;
private StaticLayout<CoreNode, CoreLink> graphLayout;
private VisualizationViewer<CoreNode, CoreLink> graphViewer;
private EditingModalGraphMouse<CoreNode, CoreLink> graphMouse;
private AnnotationControls<CoreNode, CoreLink> annotationControls;
private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24");
private CoreAddresses coreAddresses = new CoreAddresses("10.0");
private NodeType nodeType;
private Map<Integer, CoreNode> nodeMap = new ConcurrentHashMap<>();
private int vertexId = 1;
private int linkId = 1;
private Supplier<CoreNode> vertexFactory = () -> new CoreNode(vertexId++);
private Supplier<CoreLink> linkFactory = () -> new CoreLink(linkId++);
private CorePopupGraphMousePlugin customPopupPlugin;
private CoreAnnotatingGraphMousePlugin<CoreNode, CoreLink> customAnnotatingPlugin;
private BackgroundPaintable<CoreNode, CoreLink> backgroundPaintable;
private CoreVertexLabelRenderer nodeLabelRenderer = new CoreVertexLabelRenderer();
// display options
private boolean showThroughput = false;
private Double throughputLimit = null;
private int throughputWidth = 10;
public NetworkGraph(Controller controller) {
this.controller = controller;
graph = new CoreObservableGraph<>(new UndirectedSimpleGraph<>());
graph.addGraphEventListener(graphEventListener);
graphLayout = new StaticLayout<>(graph);
graphViewer = new VisualizationViewer<>(graphLayout);
graphViewer.setBackground(Color.WHITE);
graphViewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S);
RenderContext<CoreNode, CoreLink> renderContext = graphViewer.getRenderContext();
// node render properties
renderContext.setVertexLabelTransformer(CoreNode::getName);
renderContext.setVertexLabelRenderer(nodeLabelRenderer);
renderContext.setVertexShapeTransformer(node -> {
double offset = -(IconUtils.ICON_SIZE / 2.0);
return new Ellipse2D.Double(offset, offset, IconUtils.ICON_SIZE, IconUtils.ICON_SIZE);
});
renderContext.setVertexIconTransformer(vertex -> {
long wirelessLinks = wirelessLinkCount(vertex);
vertex.getRadioIcon().setWiressLinks(wirelessLinks);
return vertex.getGraphIcon();
});
// link render properties
renderContext.setEdgeLabelTransformer(edge -> {
if (!showThroughput || edge == null) {
return null;
}
double kbps = edge.getThroughput() / 1000.0;
return String.format("%.2f kbps", kbps);
});
renderContext.setLabelOffset(EDGE_LABEL_OFFSET);
renderContext.setEdgeStrokeTransformer(edge -> {
// determine edge width
int width = EDGE_WIDTH;
if (throughputLimit != null && edge.getThroughput() > throughputLimit) {
width = throughputWidth;
}
LinkTypes linkType = LinkTypes.get(edge.getType());
if (LinkTypes.WIRELESS == linkType) {
float[] dash = {15.0f};
return new BasicStroke(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND,
0, dash, 0);
} else {
return new BasicStroke(width);
}
});
renderContext.setEdgeShapeTransformer(EdgeShape.line(graph));
renderContext.setEdgeDrawPaintTransformer(edge -> {
LinkTypes linkType = LinkTypes.get(edge.getType());
if (LinkTypes.WIRELESS == linkType) {
return Color.BLUE;
} else {
return Color.BLACK;
}
});
renderContext.setEdgeIncludePredicate(predicate -> predicate.element.isVisible());
graphViewer.setVertexToolTipTransformer(renderContext.getVertexLabelTransformer());
graphMouse = new CoreEditingModalGraphMouse<>(controller, this, renderContext,
vertexFactory, linkFactory);
graphViewer.setGraphMouse(graphMouse);
// mouse events
graphViewer.addGraphMouseListener(new GraphMouseListener<CoreNode>() {
@Override
public void graphClicked(CoreNode node, MouseEvent mouseEvent) {
// double click
logger.info("click count: {}, running?: {}", mouseEvent.getClickCount(),
controller.getCoreClient().isRunning());
if (mouseEvent.getClickCount() == 2 && controller.getCoreClient().isRunning()) {
if (controller.getCoreClient().isLocalConnection()) {
try {
String shellCommand = controller.getConfiguration().getShellCommand();
String terminalCommand = controller.getCoreClient().getTerminalCommand(node);
terminalCommand = String.format("%s %s", shellCommand, terminalCommand);
logger.info("launching node terminal: {}", terminalCommand);
String[] commands = terminalCommand.split("\\s+");
logger.info("launching node terminal: {}", Arrays.toString(commands));
Process p = new ProcessBuilder(commands).start();
try {
if (!p.waitFor(5, TimeUnit.SECONDS)) {
Toast.error("Node terminal command failed");
}
} catch (InterruptedException ex) {
logger.error("error waiting for terminal to start", ex);
}
} catch (IOException ex) {
logger.error("error launching terminal", ex);
Toast.error("Node terminal failed to start");
}
} else {
Platform.runLater(() -> {
TerminalDialog terminalDialog = new TerminalDialog(controller);
terminalDialog.setOwner(controller.getWindow());
terminalDialog.showDialog(node);
});
}
}
}
@Override
public void graphPressed(CoreNode node, MouseEvent mouseEvent) {
logger.debug("graph pressed: {} - {}", node, mouseEvent);
}
@Override
public void graphReleased(CoreNode node, MouseEvent mouseEvent) {
if (SwingUtilities.isLeftMouseButton(mouseEvent)) {
Double newX = graphLayout.getX(node);
Double newY = graphLayout.getY(node);
Double oldX = node.getPosition().getX();
Double oldY = node.getPosition().getY();
if (newX.equals(oldX) && newY.equals(oldY)) {
return;
}
logger.debug("graph moved node({}): {},{}", node.getName(), newX, newY);
node.getPosition().setX(newX);
node.getPosition().setY(newY);
// upate node when session is active
if (controller.getCoreClient().isRunning()) {
try {
controller.getCoreClient().editNode(node);
} catch (IOException ex) {
Toast.error("failed to update node location");
}
}
}
}
});
}
private Color convertJfxColor(String hexValue) {
javafx.scene.paint.Color color = javafx.scene.paint.Color.web(hexValue);
return new Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue());
}
public void updatePreferences(Configuration configuration) {
Color nodeLabelColor = convertJfxColor(configuration.getNodeLabelColor());
Color nodeLabelBackgroundColor = convertJfxColor(configuration.getNodeLabelBackgroundColor());
nodeLabelRenderer.setColors(nodeLabelColor, nodeLabelBackgroundColor);
throughputLimit = configuration.getThroughputLimit();
if (configuration.getThroughputWidth() != null) {
throughputWidth = configuration.getThroughputWidth();
}
graphViewer.repaint();
}
public void setBackground(String imagePath) {
try {
backgroundPaintable = new BackgroundPaintable<>(imagePath, graphViewer);
graphViewer.addPreRenderPaintable(backgroundPaintable);
graphViewer.repaint();
} catch (IOException ex) {
logger.error("error setting background", ex);
}
}
public void removeBackground() {
if (backgroundPaintable != null) {
graphViewer.removePreRenderPaintable(backgroundPaintable);
graphViewer.repaint();
backgroundPaintable = null;
}
}
public void setMode(ModalGraphMouse.Mode mode) {
graphMouse.setMode(mode);
}
public void reset() {
logger.info("network graph reset");
vertexId = 1;
linkId = 1;
for (CoreNode node : nodeMap.values()) {
graph.removeVertex(node);
}
nodeMap.clear();
graphViewer.repaint();
}
public void updatePositions() {
for (CoreNode node : graph.getVertices()) {
Double x = graphLayout.getX(node);
Double y = graphLayout.getY(node);
node.getPosition().setX(x);
node.getPosition().setY(y);
logger.debug("updating node position node({}): {},{}", node, x, y);
}
}
public CoreNode getVertex(int id) {
return nodeMap.get(id);
}
private GraphEventListener<CoreNode, CoreLink> graphEventListener = graphEvent -> {
logger.info("graph event: {}", graphEvent.getType());
switch (graphEvent.getType()) {
case EDGE_ADDED:
handleEdgeAdded((GraphEvent.Edge<CoreNode, CoreLink>) graphEvent);
break;
case EDGE_REMOVED:
handleEdgeRemoved((GraphEvent.Edge<CoreNode, CoreLink>) graphEvent);
break;
case VERTEX_ADDED:
handleVertexAdded((GraphEvent.Vertex<CoreNode, CoreLink>) graphEvent);
break;
case VERTEX_REMOVED:
handleVertexRemoved((GraphEvent.Vertex<CoreNode, CoreLink>) graphEvent);
break;
}
};
private void handleEdgeAdded(GraphEvent.Edge<CoreNode, CoreLink> edgeEvent) {
CoreLink link = edgeEvent.getEdge();
if (!link.isLoaded()) {
Pair<CoreNode> endpoints = graph.getEndpoints(link);
CoreNode nodeOne = endpoints.getFirst();
CoreNode nodeTwo = endpoints.getSecond();
// create interfaces for nodes
int sub = coreAddresses.getSubnet(
getInterfaces(nodeOne),
getInterfaces(nodeTwo)
);
link.setNodeOne(nodeOne.getId());
if (isNode(nodeOne)) {
int interfaceOneId = nextInterfaceId(nodeOne);
CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId);
link.setInterfaceOne(interfaceOne);
}
link.setNodeTwo(nodeTwo.getId());
if (isNode(nodeTwo)) {
int interfaceTwoId = nextInterfaceId(nodeTwo);
CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId);
link.setInterfaceTwo(interfaceTwo);
}
boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo);
link.setVisible(isVisible);
logger.info("adding user created edge: {}", link);
}
}
public List<CoreInterface> getInterfaces(CoreNode node) {
return graph.getIncidentEdges(node).stream()
.map(link -> {
if (node.getId().equals(link.getNodeOne())) {
return link.getInterfaceOne();
} else {
return link.getInterfaceTwo();
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private int nextInterfaceId(CoreNode node) {
Set<Integer> interfaceIds = graph.getIncidentEdges(node).stream()
.map(link -> {
if (node.getId().equals(link.getNodeOne())) {
return link.getInterfaceOne();
} else {
return link.getInterfaceTwo();
}
})
.filter(Objects::nonNull)
.map(CoreInterface::getId)
.collect(Collectors.toSet());
int i = 0;
while (true) {
if (!interfaceIds.contains(i)) {
return i;
}
i += 1;
}
}
private boolean isNode(CoreNode node) {
return node.getType() == NodeType.DEFAULT;
}
private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) {
CoreInterface coreInterface = new CoreInterface();
coreInterface.setId(interfaceId);
coreInterface.setName(String.format("eth%s", interfaceId));
String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId());
coreInterface.setIp4(nodeOneIp4);
coreInterface.setIp4Mask(CoreAddresses.IP4_MASK);
return coreInterface;
}
private void handleEdgeRemoved(GraphEvent.Edge<CoreNode, CoreLink> edgeEvent) {
CoreLink link = edgeEvent.getEdge();
logger.info("removed edge: {}", link);
}
private void handleVertexAdded(GraphEvent.Vertex<CoreNode, CoreLink> vertexEvent) {
CoreNode node = vertexEvent.getVertex();
if (!node.isLoaded()) {
node.setNodeType(nodeType);
if (node.getType() == NodeType.EMANE) {
String emaneModel = controller.getNodeEmaneDialog().getModels().get(0);
node.setEmane(emaneModel);
}
logger.info("adding user created node: {}", node);
nodeMap.put(node.getId(), node);
}
}
private void handleVertexRemoved(GraphEvent.Vertex<CoreNode, CoreLink> vertexEvent) {
CoreNode node = vertexEvent.getVertex();
logger.info("removed vertex: {}", node);
nodeMap.remove(node.getId());
}
public void addNode(CoreNode node) {
vertexId = Math.max(node.getId() + 1, node.getId());
double x = Math.abs(node.getPosition().getX());
double y = Math.abs(node.getPosition().getY());
logger.info("adding session node: {}", node);
graph.addVertex(node);
graphLayout.setLocation(node, x, y);
nodeMap.put(node.getId(), node);
}
public void setNodeLocation(CoreNode nodeData) {
// update actual graph node
CoreNode node = nodeMap.get(nodeData.getId());
node.getPosition().setX(nodeData.getPosition().getX());
node.getPosition().setY(nodeData.getPosition().getY());
// set graph node location
double x = Math.abs(node.getPosition().getX());
double y = Math.abs(node.getPosition().getY());
graphLayout.setLocation(node, x, y);
graphViewer.repaint();
}
public void removeNode(CoreNode node) {
try {
controller.getCoreClient().deleteNode(node);
} catch (IOException ex) {
logger.error("error deleting node");
Toast.error(String.format("Error deleting node: %s", node.getName()));
}
graphViewer.getPickedVertexState().pick(node, false);
graph.removeVertex(node);
graphViewer.repaint();
}
private boolean isWirelessNode(CoreNode node) {
return node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN;
}
private boolean checkForWirelessNode(CoreNode nodeOne, CoreNode nodeTwo) {
boolean result = isWirelessNode(nodeOne);
return result || isWirelessNode(nodeTwo);
}
private long wirelessLinkCount(CoreNode node) {
return graph.getNeighbors(node).stream()
.filter(this::isWirelessNode)
.count();
}
public void addLink(CoreLink link) {
logger.info("adding session link: {}", link);
link.setId(linkId++);
CoreNode nodeOne = nodeMap.get(link.getNodeOne());
CoreNode nodeTwo = nodeMap.get(link.getNodeTwo());
boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo);
link.setVisible(isVisible);
graph.addEdge(link, nodeOne, nodeTwo);
}
public void removeWirelessLink(CoreLink link) {
logger.info("deleting link: {}", link);
CoreNode nodeOne = nodeMap.get(link.getNodeOne());
CoreNode nodeTwo = nodeMap.get(link.getNodeTwo());
CoreLink existingLink = graph.findEdge(nodeOne, nodeTwo);
if (existingLink != null) {
graph.removeEdge(existingLink);
}
}
public void removeLink(CoreLink link) {
graphViewer.getPickedEdgeState().pick(link, false);
graph.removeEdge(link);
graphViewer.repaint();
}
public void linkMdrs(CoreNode node) {
for (CoreNode currentNode : graph.getVertices()) {
if (!"mdr".equals(currentNode.getModel())) {
continue;
}
// only links mdrs we have not already linked
Collection<CoreLink> links = graph.findEdgeSet(node, currentNode);
if (links.isEmpty()) {
CoreLink link = linkFactory.get();
graph.addEdge(link, currentNode, node);
graphViewer.repaint();
}
}
}
}

View file

@ -0,0 +1,116 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.ui.Toast;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
class NodeContextMenu extends AbstractNodeContextMenu {
private static final Logger logger = LogManager.getLogger();
NodeContextMenu(Controller controller, CoreNode coreNode) {
super(controller, coreNode);
setup();
}
private MenuItem createStartItem(String service) {
MenuItem menuItem = new MenuItem("Start");
menuItem.setOnAction(event -> {
try {
boolean result = controller.getCoreClient().startService(coreNode, service);
if (result) {
Toast.success("Started " + service);
} else {
Toast.error("Failure to start " + service);
}
} catch (IOException ex) {
Toast.error("Error starting " + service, ex);
}
});
return menuItem;
}
private MenuItem createStopItem(String service) {
MenuItem menuItem = new MenuItem("Stop");
menuItem.setOnAction(event -> {
try {
boolean result = controller.getCoreClient().stopService(coreNode, service);
if (result) {
Toast.success("Stopped " + service);
} else {
Toast.error("Failure to stop " + service);
}
} catch (IOException ex) {
Toast.error("Error stopping " + service, ex);
}
});
return menuItem;
}
private MenuItem createRestartItem(String service) {
MenuItem menuItem = new MenuItem("Restart");
menuItem.setOnAction(event -> {
try {
boolean result = controller.getCoreClient().restartService(coreNode, service);
if (result) {
Toast.success("Restarted " + service);
} else {
Toast.error("Failure to restart " + service);
}
} catch (IOException ex) {
Toast.error("Error restarting " + service, ex);
}
});
return menuItem;
}
private MenuItem createValidateItem(String service) {
MenuItem menuItem = new MenuItem("Validate");
menuItem.setOnAction(event -> {
try {
boolean result = controller.getCoreClient().validateService(coreNode, service);
if (result) {
Toast.success("Validated " + service);
} else {
Toast.error("Validation failed for " + service);
}
} catch (IOException ex) {
Toast.error("Error validating " + service, ex);
}
});
return menuItem;
}
private void setup() {
if (controller.getCoreClient().isRunning()) {
Set<String> services = coreNode.getServices();
if (services.isEmpty()) {
services = controller.getDefaultServices().getOrDefault(coreNode.getModel(), Collections.emptySet());
}
if (!services.isEmpty()) {
Menu menu = new Menu("Manage Services");
for (String service : services) {
Menu serviceMenu = new Menu(service);
MenuItem startItem = createStartItem(service);
MenuItem stopItem = createStopItem(service);
MenuItem restartItem = createRestartItem(service);
MenuItem validateItem = createValidateItem(service);
serviceMenu.getItems().addAll(startItem, stopItem, restartItem, validateItem);
menu.getItems().add(serviceMenu);
}
getItems().add(menu);
}
} else {
addMenuItem("Services", event -> controller.getNodeServicesDialog().showDialog(coreNode));
addMenuItem("Delete Node", event -> controller.deleteNode(coreNode));
}
}
}

View file

@ -0,0 +1,31 @@
package com.core.graph;
import com.core.utils.IconUtils;
import lombok.Data;
import javax.swing.*;
import java.awt.*;
@Data
public class RadioIcon implements Icon {
private long wiressLinks = 0;
@Override
public int getIconHeight() {
return IconUtils.ICON_SIZE;
}
@Override
public int getIconWidth() {
return IconUtils.ICON_SIZE;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(Color.black);
for (int i = 0; i < wiressLinks; i++) {
g.fillOval(x, y, 10, 10);
x += 15;
}
}
}

View file

@ -0,0 +1,24 @@
package com.core.graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
public class UndirectedSimpleGraph<V, E> extends UndirectedSparseGraph<V, E> {
@Override
public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) {
Pair<V> newEndpoints = getValidatedEndpoints(edge, endpoints);
if (newEndpoints == null) {
return false;
}
V first = newEndpoints.getFirst();
V second = newEndpoints.getSecond();
if (first.equals(second)) {
return false;
} else {
return super.addEdge(edge, endpoints, edgeType);
}
}
}

View file

@ -0,0 +1,26 @@
package com.core.graph;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.ui.dialogs.MobilityPlayerDialog;
class WlanContextMenu extends AbstractNodeContextMenu {
WlanContextMenu(Controller controller, CoreNode coreNode) {
super(controller, coreNode);
setup();
}
private void setup() {
addMenuItem("WLAN Settings", event -> controller.getNodeWlanDialog().showDialog(coreNode));
if (controller.getCoreClient().isRunning()) {
MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId());
if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) {
addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show());
}
} else {
addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode));
addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode));
addMenuItem("Delete Node", event -> controller.deleteNode(coreNode));
}
}
}

View file

@ -0,0 +1,104 @@
package com.core.ui;
import com.core.graph.NetworkGraph;
import com.core.utils.FxmlUtils;
import com.jfoenix.controls.JFXColorPicker;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXToggleButton;
import edu.uci.ics.jung.visualization.annotations.Annotation;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
public class AnnotationToolbar extends GridPane {
private static final Logger logger = LogManager.getLogger();
private static final String RECTANGLE = "Rectangle";
private static final String ROUND_RECTANGLE = "RoundRectangle";
private static final String ELLIPSE = "Ellipse";
private static final String UPPER_LAYER = "Upper";
private static final String LOWER_LAYER = "Lower";
private NetworkGraph graph;
@FXML private JFXComboBox<String> shapeCombo;
@FXML private JFXColorPicker colorPicker;
@FXML private JFXComboBox<String> layerCombo;
@FXML private JFXToggleButton fillToggle;
public AnnotationToolbar(NetworkGraph graph) {
this.graph = graph;
FxmlUtils.loadRootController(this, "/fxml/annotation_toolbar.fxml");
// setup annotation shape combo
shapeCombo.getItems().addAll(RECTANGLE, ROUND_RECTANGLE, ELLIPSE);
shapeCombo.setOnAction(this::shapeChange);
shapeCombo.getSelectionModel().selectFirst();
// setup annotation layer combo
layerCombo.getItems().addAll(LOWER_LAYER, UPPER_LAYER);
layerCombo.setOnAction(this::layerChange);
layerCombo.getSelectionModel().selectFirst();
// setup annotation color picker
colorPicker.setOnAction(this::colorChange);
colorPicker.setValue(javafx.scene.paint.Color.AQUA);
colorPicker.fireEvent(new ActionEvent());
// setup annotation toggle fill
fillToggle.setOnAction(this::fillChange);
}
private void fillChange(ActionEvent event) {
boolean selected = fillToggle.isSelected();
graph.getGraphMouse().getAnnotatingPlugin().setFill(selected);
}
private void colorChange(ActionEvent event) {
javafx.scene.paint.Color fxColor = colorPicker.getValue();
java.awt.Color color = new java.awt.Color(
(float) fxColor.getRed(),
(float) fxColor.getGreen(),
(float) fxColor.getBlue(),
(float) fxColor.getOpacity()
);
logger.info("color selected: {}", fxColor);
graph.getGraphMouse().getAnnotatingPlugin().setAnnotationColor(color);
}
private void layerChange(ActionEvent event) {
String selected = layerCombo.getSelectionModel().getSelectedItem();
logger.info("annotation layer selected: {}", selected);
Annotation.Layer layer;
if (LOWER_LAYER.equals(selected)) {
layer = Annotation.Layer.LOWER;
} else {
layer = Annotation.Layer.UPPER;
}
graph.getGraphMouse().getAnnotatingPlugin().setLayer(layer);
}
private void shapeChange(ActionEvent event) {
String selected = shapeCombo.getSelectionModel().getSelectedItem();
logger.info("annotation shape selected: {}", selected);
RectangularShape shape = new Rectangle();
switch (selected) {
case RECTANGLE:
shape = new Rectangle();
break;
case ROUND_RECTANGLE:
shape = new RoundRectangle2D.Double(0, 0, 0, 0, 50.0, 50.0);
break;
case ELLIPSE:
shape = new Ellipse2D.Double();
break;
default:
Toast.error("Unknown annotation shape " + selected);
}
graph.getGraphMouse().getAnnotatingPlugin().setRectangularShape(shape);
}
}

View file

@ -0,0 +1,297 @@
package com.core.ui;
import com.core.Controller;
import com.core.data.NodeType;
import com.core.utils.FxmlUtils;
import com.core.utils.IconUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.svg.SVGGlyph;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import javafx.application.Platform;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class GraphToolbar extends VBox {
private static final Logger logger = LogManager.getLogger();
private static final int ICON_SIZE = 40;
private static final int NODES_ICON_SIZE = 20;
private static final PseudoClass START_CLASS = PseudoClass.getPseudoClass("start");
private static final PseudoClass STOP_CLASS = PseudoClass.getPseudoClass("stop");
private static final PseudoClass SELECTED_CLASS = PseudoClass.getPseudoClass("selected");
private final Controller controller;
private final Map<Integer, Label> labelMap = new HashMap<>();
private SVGGlyph startIcon;
private SVGGlyph stopIcon;
private JFXListView<Label> nodesList = new JFXListView<>();
private JFXListView<Label> devicesList = new JFXListView<>();
private JFXButton selectedEditButton;
private NodeType selectedNodeType;
private boolean isEditing = false;
@FXML private JFXButton runButton;
@FXML private JFXButton pickingButton;
@FXML private JFXButton drawingButton;
@FXML private ComboBox<String> graphModeCombo;
@FXML private JFXButton nodesButton;
@FXML private JFXButton devicesButton;
public GraphToolbar(Controller controller) {
this.controller = controller;
FxmlUtils.loadRootController(this, "/fxml/graph_toolbar.fxml");
startIcon = IconUtils.get("play_circle_filled");
startIcon.setSize(ICON_SIZE);
stopIcon = IconUtils.get("stop");
stopIcon.setSize(ICON_SIZE);
setupPickingButton();
setupDrawingButton();
setupNodesButton();
setupDevicesButton();
// initial state
setSelected(true, pickingButton);
controller.getNetworkGraph().setMode(ModalGraphMouse.Mode.PICKING);
runButton.setGraphic(startIcon);
}
private void setupPickingButton() {
SVGGlyph pickingIcon = IconUtils.get("call_made");
pickingIcon.setSize(ICON_SIZE);
pickingButton.setGraphic(pickingIcon);
pickingButton.setTooltip(new Tooltip("Pick/Move Nodes"));
pickingButton.setOnAction(event -> {
controller.getNetworkGraph().setMode(ModalGraphMouse.Mode.PICKING);
controller.getBottom().getChildren().remove(controller.getAnnotationToolbar());
controller.getBorderPane().setRight(null);
setSelected(true, pickingButton);
setSelected(false, drawingButton, selectedEditButton);
isEditing = false;
});
}
private void setEditMode() {
controller.getNetworkGraph().setMode(ModalGraphMouse.Mode.EDITING);
controller.getBottom().getChildren().remove(controller.getAnnotationToolbar());
controller.getBorderPane().setRight(null);
if (selectedEditButton != null) {
setSelected(true, selectedEditButton);
}
setSelected(false, drawingButton, pickingButton);
isEditing = true;
}
private void setupDrawingButton() {
SVGGlyph pencilIcon = IconUtils.get("brush");
pencilIcon.setSize(ICON_SIZE);
drawingButton.setGraphic(pencilIcon);
drawingButton.setTooltip(new Tooltip("Annotate Graph"));
drawingButton.setOnAction(event -> {
controller.getNetworkGraph().setMode(ModalGraphMouse.Mode.ANNOTATING);
controller.getBottom().getChildren().add(controller.getAnnotationToolbar());
controller.getBorderPane().setRight(null);
setSelected(true, drawingButton);
setSelected(false, pickingButton, selectedEditButton);
isEditing = false;
});
}
public void setupNodeTypes() {
// clear existing configuration
labelMap.clear();
nodesList.getItems().clear();
devicesList.getItems().clear();
for (NodeType nodeType : NodeType.getAll()) {
ImageView icon = new ImageView(nodeType.getIcon());
icon.setFitWidth(NODES_ICON_SIZE);
icon.setFitHeight(NODES_ICON_SIZE);
Label label = new Label(nodeType.getDisplay(), icon);
label.setUserData(nodeType.getId());
labelMap.put(nodeType.getId(), label);
if (nodeType.getValue() == NodeType.DEFAULT) {
nodesList.getItems().add(label);
} else {
devicesList.getItems().add(label);
}
}
Comparator<Label> comparator = Comparator.comparing(Label::getText);
nodesList.getItems().sort(comparator);
devicesList.getItems().sort(comparator);
// initial node
nodesList.getSelectionModel().selectFirst();
Label selectedNodeLabel = nodesList.getSelectionModel().getSelectedItem();
selectedNodeType = NodeType.get((int) selectedNodeLabel.getUserData());
selectedEditButton = nodesButton;
controller.getNetworkGraph().setNodeType(selectedNodeType);
updateButtonValues(nodesButton, selectedNodeLabel);
// initial device
updateButtonValues(devicesButton, devicesList.getItems().get(0));
}
private void updateButtonValues(JFXButton button, Label label) {
ImageView icon = new ImageView(((ImageView) label.getGraphic()).getImage());
icon.setFitHeight(ICON_SIZE);
icon.setFitWidth(ICON_SIZE);
button.setGraphic(icon);
}
private void setSelectedEditButton(JFXButton button) {
JFXButton previous = selectedEditButton;
selectedEditButton = button;
if (isEditing) {
if (previous != null) {
setSelected(false, previous);
}
setSelected(true, selectedEditButton);
}
}
private void setupNodesButton() {
nodesButton.setTooltip(new Tooltip("Network Nodes (host, pc, etc)"));
nodesList.setOnMouseClicked(event -> {
Label selectedLabel = nodesList.getSelectionModel().getSelectedItem();
if (selectedLabel == null) {
return;
}
updateButtonValues(nodesButton, selectedLabel);
selectedNodeType = NodeType.get((int) selectedLabel.getUserData());
logger.info("selected node type: {}", selectedNodeType);
setSelectedEditButton(nodesButton);
devicesList.getSelectionModel().clearSelection();
controller.getNetworkGraph().setNodeType(selectedNodeType);
logger.info("node selected: {} - type: {}", selectedLabel, selectedNodeType);
setEditMode();
});
JFXPopup popup = new JFXPopup(nodesList);
nodesButton.setOnAction(event -> popup.show(nodesButton, JFXPopup.PopupVPosition.TOP,
JFXPopup.PopupHPosition.LEFT, nodesButton.getWidth(), 0));
}
private void setupDevicesButton() {
devicesButton.setTooltip(new Tooltip("Device Nodes (WLAN, EMANE, Switch, etc)"));
devicesList.setOnMouseClicked(event -> {
Label selectedLabel = devicesList.getSelectionModel().getSelectedItem();
if (selectedLabel == null) {
return;
}
updateButtonValues(devicesButton, selectedLabel);
selectedNodeType = NodeType.get((int) selectedLabel.getUserData());
logger.info("selected node type: {}", selectedNodeType);
controller.getNetworkGraph().setNodeType(selectedNodeType);
setSelectedEditButton(devicesButton);
nodesList.getSelectionModel().clearSelection();
logger.info("device selected: {} - type: {}", selectedLabel, selectedNodeType);
setEditMode();
});
JFXPopup popup = new JFXPopup(devicesList);
devicesButton.setOnAction(event -> popup.show(devicesButton, JFXPopup.PopupVPosition.TOP,
JFXPopup.PopupHPosition.LEFT, devicesButton.getWidth(), 0));
}
@FXML
private void onRunButtonAction(ActionEvent event) {
if (runButton.getGraphic() == startIcon) {
startSession();
} else {
stopSession();
}
}
public void updateNodeType(int id, String uri) {
Label label = labelMap.get(id);
ImageView icon = new ImageView(uri);
icon.setFitWidth(NODES_ICON_SIZE);
icon.setFitHeight(NODES_ICON_SIZE);
label.setGraphic(icon);
if (selectedNodeType.getId() == id) {
updateButtonValues(nodesButton, label);
}
}
private void setSelected(boolean isSelected, JFXButton... others) {
Arrays.stream(others)
.forEach(x -> x.pseudoClassStateChanged(SELECTED_CLASS, isSelected));
}
private void startSession() {
runButton.setDisable(true);
new Thread(() -> {
try {
boolean result = controller.startSession();
if (result) {
Toast.success("Session Started");
setRunButton(true);
}
} catch (IOException ex) {
Toast.error("Failure Starting Session", ex);
}
}).start();
}
private void stopSession() {
runButton.setDisable(true);
new Thread(() -> {
try {
boolean result = controller.stopSession();
if (result) {
Toast.success("Session Stopped");
setRunButton(false);
}
} catch (IOException ex) {
Toast.error("Failure Stopping Session", ex);
}
}).start();
}
public void setRunButton(boolean isRunning) {
if (isRunning) {
Platform.runLater(() -> {
pickingButton.fire();
devicesButton.setDisable(true);
nodesButton.setDisable(true);
runButton.pseudoClassStateChanged(START_CLASS, false);
runButton.pseudoClassStateChanged(STOP_CLASS, true);
if (runButton.getGraphic() != stopIcon) {
runButton.setGraphic(stopIcon);
}
runButton.setDisable(false);
});
} else {
Platform.runLater(() -> {
devicesButton.setDisable(false);
nodesButton.setDisable(false);
runButton.pseudoClassStateChanged(START_CLASS, true);
runButton.pseudoClassStateChanged(STOP_CLASS, false);
if (runButton.getGraphic() != startIcon) {
runButton.setGraphic(startIcon);
}
runButton.setDisable(false);
});
}
}
}

View file

@ -0,0 +1,178 @@
package com.core.ui;
import com.core.Controller;
import com.core.client.ICoreClient;
import com.core.data.CoreInterface;
import com.core.data.CoreLink;
import com.core.data.CoreLinkOptions;
import com.core.data.CoreNode;
import com.core.graph.NetworkGraph;
import com.core.ui.textfields.DoubleFilter;
import com.core.utils.FxmlUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.GridPane;
import javafx.util.converter.DoubleStringConverter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class LinkDetails extends ScrollPane {
private static final Logger logger = LogManager.getLogger();
private static final int START_INDEX = 1;
private final Controller controller;
private int index = START_INDEX;
@FXML private GridPane gridPane;
public LinkDetails(Controller controller) {
this.controller = controller;
FxmlUtils.loadRootController(this, "/fxml/link_details.fxml");
setPrefWidth(400);
}
public void setLink(CoreLink link) {
NetworkGraph graph = controller.getNetworkGraph();
ICoreClient coreClient = controller.getCoreClient();
clear();
addSeparator();
CoreNode nodeOne = graph.getVertex(link.getNodeOne());
CoreInterface interfaceOne = link.getInterfaceOne();
addLabel(nodeOne.getName());
if (interfaceOne != null) {
addInterface(interfaceOne);
}
addSeparator();
CoreNode nodeTwo = graph.getVertex(link.getNodeTwo());
CoreInterface interfaceTwo = link.getInterfaceTwo();
addLabel(nodeTwo.getName());
if (interfaceTwo != null) {
addInterface(interfaceTwo);
}
addSeparator();
addLabel("Properties");
JFXTextField bandwidthField = addRow("Bandwidth (bps)", link.getOptions().getBandwidth());
JFXTextField delayField = addRow("Delay (us)", link.getOptions().getDelay());
JFXTextField jitterField = addRow("Jitter (us)", link.getOptions().getJitter());
JFXTextField lossField = addRow("Loss (%)", link.getOptions().getPer());
JFXTextField dupsField = addRow("Duplicate (%)", link.getOptions().getDup());
addButton("Update", event -> {
CoreLinkOptions options = link.getOptions();
options.setBandwidth(getDouble(bandwidthField));
options.setDelay(getDouble(delayField));
options.setJitter(getDouble(jitterField));
options.setPer(getDouble(lossField));
options.setDup(getDouble(dupsField));
if (coreClient.isRunning()) {
try {
coreClient.editLink(link);
Toast.info("Link updated!");
} catch (IOException ex) {
Toast.error("Failure to update link", ex);
}
}
});
}
private Double getDouble(JFXTextField textField) {
if (textField.getText() == null) {
return null;
}
Double value = null;
try {
logger.info("double field text: {}", textField.getText());
value = Double.parseDouble(textField.getText());
} catch (NumberFormatException ex) {
logger.error("error getting double value", ex);
}
return value;
}
private void addButton(String text, EventHandler<ActionEvent> handler) {
JFXButton button = new JFXButton(text);
button.getStyleClass().add("core-button");
button.setMaxWidth(Double.MAX_VALUE);
button.setOnAction(handler);
gridPane.add(button, 0, index++, 2, 1);
GridPane.setMargin(button, new Insets(10, 0, 0, 0));
}
private void addLabel(String text) {
Label label = new Label(text);
label.getStyleClass().add("details-label");
gridPane.add(label, 0, index++, 2, 1);
}
private void addSeparator() {
Separator separator = new Separator(Orientation.HORIZONTAL);
gridPane.add(separator, 0, index++, 2, 1);
GridPane.setMargin(separator, new Insets(10, 0, 0, 0));
}
private void addInterface(CoreInterface coreInterface) {
addRow("Interface", coreInterface.getName(), true);
if (coreInterface.getMac() != null) {
addRow("MAC", coreInterface.getMac(), true);
}
addIp4Address(coreInterface.getIp4(), coreInterface.getIp4Mask());
addIp6Address(coreInterface.getIp6(), coreInterface.getIp6Mask());
}
private void addRow(String labelText, String value, boolean disabled) {
Label label = new Label(labelText);
JFXTextField textField = new JFXTextField(value);
textField.setDisable(disabled);
gridPane.addRow(index++, label, textField);
}
private JFXTextField addRow(String labelText, Double value) {
Label label = new Label(labelText);
String doubleString = null;
if (value != null) {
doubleString = value.toString();
}
JFXTextField textField = new JFXTextField();
TextFormatter<Double> formatter = new TextFormatter<>(
new DoubleStringConverter(), null, new DoubleFilter());
textField.setTextFormatter(formatter);
textField.setText(doubleString);
gridPane.addRow(index++, label, textField);
return textField;
}
private void addIp4Address(String ip, Integer mask) {
if (ip == null) {
return;
}
addRow("IP4", String.format("%s/%s", ip, mask), true);
}
private void addIp6Address(String ip, String mask) {
if (ip == null) {
return;
}
addRow("IP6", String.format("%s/%s", ip, mask), true);
}
private void clear() {
if (gridPane.getChildren().size() > START_INDEX) {
gridPane.getChildren().remove(START_INDEX, gridPane.getChildren().size());
}
index = START_INDEX;
}
}

View file

@ -0,0 +1,181 @@
package com.core.ui;
import com.core.Controller;
import com.core.data.CoreInterface;
import com.core.data.CoreLink;
import com.core.data.CoreNode;
import com.core.data.NodeType;
import com.core.utils.FxmlUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXScrollPane;
import com.jfoenix.controls.JFXTextField;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Separator;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collections;
import java.util.Set;
public class NodeDetails extends ScrollPane {
private static final Logger logger = LogManager.getLogger();
private static final int START_INDEX = 1;
private final Controller controller;
@FXML private Label title;
@FXML private ScrollPane scrollPane;
@FXML private GridPane gridPane;
private int index = START_INDEX;
public NodeDetails(Controller controller) {
this.controller = controller;
FxmlUtils.loadRootController(this, "/fxml/node_details.fxml");
setPrefWidth(400);
}
public void setNode(CoreNode node) {
clear();
title.setText(node.getName());
addSeparator();
addLabel("Properties");
if (node.getType() == NodeType.DEFAULT) {
addRow("Model", node.getModel(), true);
} else {
addRow("Type", node.getNodeType().getDisplay(), true);
}
if (node.getEmane() != null) {
addRow("EMANE", node.getEmane(), true);
}
addSeparator();
addLabel("Position");
if (node.getPosition().getX() != null) {
addRow("X", node.getPosition().getX().toString(), true);
}
if (node.getPosition().getY() != null) {
addRow("Y", node.getPosition().getY().toString(), true);
}
addSeparator();
addLabel("Interfaces");
for (CoreLink link : controller.getNetworkGraph().getGraph().getIncidentEdges(node)) {
CoreNode linkedNode;
CoreInterface coreInterface;
if (node.getId().equals(link.getNodeOne())) {
coreInterface = link.getInterfaceOne();
linkedNode = controller.getNetworkGraph().getNodeMap().get(link.getNodeTwo());
} else {
coreInterface = link.getInterfaceTwo();
linkedNode = controller.getNetworkGraph().getNodeMap().get(link.getNodeOne());
}
if (coreInterface == null) {
continue;
}
addSeparator();
if (linkedNode.getType() == NodeType.EMANE) {
String emaneModel = linkedNode.getEmane();
String linkedLabel = String.format("%s - %s", linkedNode.getName(), emaneModel);
addButton(linkedLabel, event -> controller.getNodeEmaneDialog()
.displayEmaneModelConfig(linkedNode.getId(), emaneModel));
String nodeLabel = String.format("%s - %s", node.getName(), emaneModel);
addButton(nodeLabel, event -> controller.getNodeEmaneDialog()
.displayEmaneModelConfig(node.getId(), emaneModel));
String interfaceLabel = String.format("%s - %s", coreInterface.getName(), emaneModel);
Integer interfaceId = 1000 * node.getId() + coreInterface.getId();
addButton(interfaceLabel, event -> controller.getNodeEmaneDialog()
.displayEmaneModelConfig(interfaceId, emaneModel));
}
if (linkedNode.getType() == NodeType.WLAN) {
addButton(linkedNode.getName(), event -> controller.getNodeWlanDialog().showDialog(linkedNode));
}
addInterface(coreInterface, linkedNode);
}
// display custom or default node services
Set<String> services = node.getServices();
if (services.isEmpty()) {
services = controller.getDefaultServices().getOrDefault(node.getModel(), Collections.emptySet());
}
if (!services.isEmpty()) {
addSeparator();
addLabel("Services");
JFXListView<String> listView = new JFXListView<>();
listView.setMouseTransparent(true);
listView.setFocusTraversable(false);
listView.getItems().setAll(services);
gridPane.add(listView, 0, index++, 2, 1);
}
JFXScrollPane.smoothScrolling(scrollPane);
}
private void addButton(String text, EventHandler<ActionEvent> handler) {
JFXButton emaneButton = new JFXButton(text);
emaneButton.getStyleClass().add("core-button");
emaneButton.setMaxWidth(Double.MAX_VALUE);
emaneButton.setOnAction(handler);
gridPane.add(emaneButton, 0, index++, 2, 1);
}
private void addLabel(String text) {
Label label = new Label(text);
label.getStyleClass().add("details-label");
gridPane.add(label, 0, index++, 2, 1);
}
private void addSeparator() {
Separator separator = new Separator(Orientation.HORIZONTAL);
gridPane.add(separator, 0, index++, 2, 1);
GridPane.setMargin(separator, new Insets(10, 0, 0, 0));
}
private void addInterface(CoreInterface coreInterface, CoreNode linkedNode) {
addRow("Linked To", linkedNode.getName(), true);
addRow("Interface", coreInterface.getName(), true);
if (coreInterface.getMac() != null) {
addRow("MAC", coreInterface.getMac(), true);
}
addIp4Address(coreInterface.getIp4(), coreInterface.getIp4Mask());
addIp6Address(coreInterface.getIp6(), coreInterface.getIp6Mask());
}
private void addRow(String labelText, String value, boolean disabled) {
Label label = new Label(labelText);
JFXTextField textField = new JFXTextField(value);
textField.setDisable(disabled);
gridPane.addRow(index++, label, textField);
}
private void addIp4Address(String ip, Integer mask) {
if (ip == null) {
return;
}
addRow("IP4", String.format("%s/%s", ip, mask), true);
}
private void addIp6Address(String ip, String mask) {
if (ip == null) {
return;
}
addRow("IP6", String.format("%s/%s", ip, mask), true);
}
private void clear() {
if (gridPane.getChildren().size() > START_INDEX) {
gridPane.getChildren().remove(START_INDEX, gridPane.getChildren().size());
}
index = START_INDEX;
}
}

View file

@ -0,0 +1,15 @@
package com.core.ui;
import com.jfoenix.controls.JFXCheckBox;
import lombok.Data;
@Data
public class ServiceItem {
private String service;
private JFXCheckBox checkBox;
public ServiceItem(String service) {
this.service = service;
checkBox = new JFXCheckBox(service);
}
}

View file

@ -0,0 +1,52 @@
package com.core.ui;
import com.jfoenix.controls.JFXSnackbar;
import javafx.scene.layout.StackPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public final class Toast {
private static final Logger logger = LogManager.getLogger();
private static final long TIMEOUT = 3000;
private static JFXSnackbar snackbar;
private Toast() {
}
public static void setSnackbarRoot(StackPane stackPane) {
snackbar = new JFXSnackbar(stackPane);
}
private static void toast(String message, String className) {
JFXSnackbar.SnackbarEvent snackbarEvent = new JFXSnackbar.SnackbarEvent(message,
className, null, TIMEOUT, false, null);
snackbar.enqueue(snackbarEvent);
}
public static void info(String message) {
toast(message, "toast-info");
}
public static void success(String message) {
toast(message, "toast-success");
}
public static void warning(String message) {
toast(message, "toast-warning");
}
public static void error(String message) {
error(message, null);
}
public static void error(String message, Exception ex) {
if (ex != null) {
logger.error(message, ex);
}
JFXSnackbar.SnackbarEvent snackbarEvent = new JFXSnackbar.SnackbarEvent(message,
"toast-error", "X", TIMEOUT, true, event -> snackbar.close());
snackbar.close();
snackbar.enqueue(snackbarEvent);
}
}

View file

@ -0,0 +1,19 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import lombok.Data;
@Data
public abstract class BaseConfigItem implements IConfigItem {
private final Stage stage;
private final Label label;
private final ConfigOption option;
public BaseConfigItem(Stage stage, ConfigOption option) {
this.stage = stage;
this.option = option;
this.label = new Label(option.getLabel());
}
}

View file

@ -0,0 +1,32 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import com.jfoenix.controls.JFXToggleButton;
import javafx.scene.Node;
import javafx.stage.Stage;
public class BooleanConfigItem extends BaseConfigItem {
private JFXToggleButton button = new JFXToggleButton();
public BooleanConfigItem(Stage stage, ConfigOption option) {
super(stage, option);
button.setMaxWidth(Double.MAX_VALUE);
if ("1".equals(option.getValue())) {
button.setSelected(true);
}
button.selectedProperty().addListener(((observable, oldValue, newValue) -> {
String value;
if (newValue) {
value = "1";
} else {
value = "0";
}
getOption().setValue(value);
}));
}
@Override
public Node getNode() {
return button;
}
}

View file

@ -0,0 +1,29 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import com.core.data.ConfigDataType;
import javafx.stage.Stage;
public final class ConfigItemUtils {
private ConfigItemUtils() {
}
public static IConfigItem get(Stage stage, ConfigOption option) {
IConfigItem configItem;
ConfigDataType dataType = ConfigDataType.get(option.getType());
if (dataType == ConfigDataType.BOOL) {
configItem = new BooleanConfigItem(stage, option);
} else {
if (!option.getSelect().isEmpty()) {
configItem = new SelectConfigItem(stage, option);
} else if (option.getLabel().endsWith(" file")) {
configItem = new FileConfigItem(stage, option);
} else {
configItem = new DefaultConfigItem(stage, option);
}
}
return configItem;
}
}

View file

@ -0,0 +1,24 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.Node;
import javafx.stage.Stage;
public class DefaultConfigItem extends BaseConfigItem {
private JFXTextField textField;
public DefaultConfigItem(Stage stage, ConfigOption option) {
super(stage, option);
textField = new JFXTextField(option.getValue());
textField.setMaxWidth(Double.MAX_VALUE);
textField.textProperty().addListener(((observable, oldValue, newValue) -> {
getOption().setValue(newValue);
}));
}
@Override
public Node getNode() {
return textField;
}
}

View file

@ -0,0 +1,56 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.Node;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
public class FileConfigItem extends BaseConfigItem {
private GridPane gridPane;
public FileConfigItem(Stage stage, ConfigOption option) {
super(stage, option);
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setMaxWidth(Double.MAX_VALUE);
RowConstraints rowConstraints = new RowConstraints();
rowConstraints.setVgrow(Priority.SOMETIMES);
ColumnConstraints textFieldConstraints = new ColumnConstraints();
textFieldConstraints.setHgrow(Priority.SOMETIMES);
textFieldConstraints.setPercentWidth(60);
ColumnConstraints buttonConstraints = new ColumnConstraints();
buttonConstraints.setHgrow(Priority.SOMETIMES);
buttonConstraints.setPercentWidth(40);
gridPane.getColumnConstraints().addAll(textFieldConstraints, buttonConstraints);
JFXTextField textField = new JFXTextField();
textField.setMaxWidth(Double.MAX_VALUE);
textField.textProperty().addListener(((observable, oldValue, newValue) -> getOption().setValue(newValue)));
JFXButton button = new JFXButton("Select File");
button.getStyleClass().add("core-button");
button.setMaxWidth(Double.MAX_VALUE);
button.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select File");
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
File file = fileChooser.showOpenDialog(stage);
if (file != null) {
textField.setText(file.getPath());
}
});
gridPane.addRow(0, textField, button);
}
@Override
public Node getNode() {
return gridPane;
}
}

View file

@ -0,0 +1,13 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import javafx.scene.Node;
import javafx.scene.control.Label;
public interface IConfigItem {
Label getLabel();
Node getNode();
ConfigOption getOption();
}

View file

@ -0,0 +1,29 @@
package com.core.ui.config;
import com.core.data.ConfigOption;
import com.jfoenix.controls.JFXComboBox;
import javafx.scene.Node;
import javafx.stage.Stage;
public class SelectConfigItem extends BaseConfigItem {
private JFXComboBox<String> comboBox = new JFXComboBox<>();
public SelectConfigItem(Stage stage, ConfigOption option) {
super(stage, option);
comboBox.setMaxWidth(Double.MAX_VALUE);
comboBox.getItems().addAll(option.getSelect());
comboBox.getSelectionModel().select(option.getValue());
comboBox.getSelectionModel().selectedItemProperty().addListener(((observable, oldValue, newValue) -> {
if (newValue == null) {
return;
}
getOption().setValue(newValue);
}));
}
@Override
public Node getNode() {
return comboBox;
}
}

View file

@ -0,0 +1,86 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.CoreLink;
import com.core.data.CoreNode;
import com.core.graph.BackgroundPaintable;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Paths;
public class BackgroundDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private ImageView imageView;
@FXML private JFXTextField fileTextField;
@FXML private JFXButton fileButton;
private JFXButton saveButton;
private JFXButton clearButton;
public BackgroundDialog(Controller controller) {
super(controller, "/fxml/background_dialog.fxml");
setTitle("Background Configuration");
saveButton = createButton("Save");
saveButton.setOnAction(this::saveAction);
clearButton = createButton("Clear");
clearButton.setOnAction(this::clearAction);
addCancelButton();
HBox parent = (HBox) imageView.getParent();
imageView.fitHeightProperty().bind(parent.heightProperty());
fileButton.setOnAction(this::fileAction);
}
private void fileAction(ActionEvent event) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Background");
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PNG", "*.png"));
File file = fileChooser.showOpenDialog(getStage());
if (file != null) {
String uri = file.toURI().toString();
imageView.setImage(new Image(uri));
fileTextField.setText(file.getPath());
saveButton.setDisable(false);
}
}
private void saveAction(ActionEvent event) {
getController().getNetworkGraph().setBackground(fileTextField.getText());
close();
}
private void clearAction(ActionEvent event) {
getController().getNetworkGraph().removeBackground();
close();
}
public void showDialog() {
BackgroundPaintable<CoreNode, CoreLink> backgroundPaintable = getController().getNetworkGraph()
.getBackgroundPaintable();
saveButton.setDisable(true);
fileTextField.setText(null);
imageView.setImage(null);
if (backgroundPaintable == null) {
clearButton.setDisable(true);
} else {
String imagePath = backgroundPaintable.getImage();
fileTextField.setText(imagePath);
imageView.setImage(new Image(Paths.get(imagePath).toUri().toString()));
clearButton.setDisable(false);
}
show();
}
}

View file

@ -0,0 +1,217 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.datavis.*;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.chart.Chart;
import javafx.scene.layout.Pane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
public class ChartDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private final AtomicBoolean running = new AtomicBoolean(false);
private final Random numbers = new Random();
private final List<String> chartNames = Arrays.asList("Name 1", "Name 2", "Name 3", "Name 4", "Name 5");
@FXML private JFXComboBox<String> chartCombo;
@FXML private Pane chartPane;
@FXML private JFXButton stopButton;
private CoreGraph coreGraph;
public ChartDialog(Controller controller) {
super(controller, "/fxml/chart_dialog.fxml");
addCancelButton();
coreGraph = new CoreGraph();
coreGraph.setTitle("My Graph");
coreGraph.setXAxis(new CoreGraphAxis("X Label", 0.0, 100.0, 1.0));
coreGraph.setYAxis(new CoreGraphAxis("Y Label", 0.0, 100.0, 1.0));
chartCombo.getItems().addAll("pie", "line", "area", "bar", "scatter", "bubble", "time");
chartCombo.getSelectionModel().selectedItemProperty().addListener((ov, prev, curr) -> {
if (curr == null) {
return;
}
running.set(false);
switch (curr) {
case "pie":
pieChart();
break;
case "line":
lineChart();
break;
case "area":
areaChart();
break;
case "bar":
barChart();
break;
case "scatter":
scatterChart();
break;
case "bubble":
bubbleChart();
break;
case "time":
timeChart();
break;
}
});
stopButton.setOnAction(event -> running.set(false));
chartCombo.getSelectionModel().selectFirst();
}
private void timeChart() {
coreGraph.setGraphType(GraphType.TIME);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
double y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(null, null, y, null)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void bubbleChart() {
coreGraph.setGraphType(GraphType.BUBBLE);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
double x = numbers.nextInt(100);
double y = numbers.nextInt(100);
double weight = numbers.nextInt(10);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(null, x, y, weight)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void scatterChart() {
coreGraph.setGraphType(GraphType.SCATTER);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
double x = numbers.nextInt(100);
double y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(null, x, y, null)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void areaChart() {
coreGraph.setGraphType(GraphType.AREA);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
double x = numbers.nextInt(100);
double y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(null, x, y, null)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void setChart(Chart chart) {
chart.prefHeightProperty().bind(chartPane.heightProperty());
chart.prefWidthProperty().bind(chartPane.widthProperty());
chartPane.getChildren().clear();
chartPane.getChildren().add(chart);
running.set(true);
}
private void lineChart() {
coreGraph.setGraphType(GraphType.LINE);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
double x = numbers.nextInt(100);
double y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(null, x, y, null)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void pieChart() {
coreGraph.setGraphType(GraphType.PIE);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
String name = chartNames.get(numbers.nextInt(chartNames.size()));
double y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(new CoreGraphData(name, null, y, null)));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
private void barChart() {
coreGraph.setGraphType(GraphType.BAR);
CoreGraphWrapper graphWrapper = new CoreGraphWrapper(coreGraph);
setChart(graphWrapper.getChart());
new Thread(() -> {
while (running.get()) {
try {
String name = chartNames.get(numbers.nextInt(chartNames.size()));
Integer y = numbers.nextInt(100);
Platform.runLater(() -> graphWrapper.add(name, y));
Thread.sleep(1000);
} catch (Exception ex) {
logger.error("error adding data", ex);
}
}
}).start();
}
public void showDialog() {
chartCombo.getSelectionModel().selectFirst();
}
}

View file

@ -0,0 +1,91 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.ConfigGroup;
import com.core.data.ConfigOption;
import com.core.ui.config.ConfigItemUtils;
import com.core.ui.config.IConfigItem;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXScrollPane;
import com.jfoenix.controls.JFXTabPane;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ConfigDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private List<IConfigItem> configItems = new ArrayList<>();
private JFXButton saveButton;
@FXML private JFXTabPane tabPane;
public ConfigDialog(Controller controller) {
super(controller, "/fxml/config_dialog.fxml");
saveButton = createButton("Save");
addCancelButton();
}
public List<ConfigOption> getOptions() {
return configItems.stream().map(IConfigItem::getOption).collect(Collectors.toList());
}
private void setDisabled(boolean isDisabled) {
saveButton.setDisable(isDisabled);
}
public void showDialog(String title, List<ConfigGroup> configGroups, Runnable runnable) {
setTitle(title);
boolean sessionRunning = getCoreClient().isRunning();
setDisabled(sessionRunning);
configItems.clear();
tabPane.getTabs().clear();
for (ConfigGroup group : configGroups) {
String groupName = group.getName();
Tab tab = new Tab(groupName);
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToWidth(true);
tab.setContent(scrollPane);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10));
scrollPane.setContent(gridPane);
gridPane.setPrefWidth(Double.MAX_VALUE);
ColumnConstraints labelConstraints = new ColumnConstraints(10);
labelConstraints.setPercentWidth(50);
ColumnConstraints valueConstraints = new ColumnConstraints(10);
valueConstraints.setPercentWidth(50);
gridPane.getColumnConstraints().addAll(labelConstraints, valueConstraints);
gridPane.setHgap(10);
gridPane.setVgap(10);
int index = 0;
tabPane.getTabs().add(tab);
for (ConfigOption option : group.getOptions()) {
IConfigItem configItem = ConfigItemUtils.get(getStage(), option);
Node node = configItem.getNode();
node.setDisable(sessionRunning);
gridPane.addRow(index, configItem.getLabel(), node);
configItems.add(configItem);
index += 1;
}
JFXScrollPane.smoothScrolling(scrollPane);
}
saveButton.setOnAction(event -> {
runnable.run();
close();
});
show();
}
}

View file

@ -0,0 +1,39 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Data
public class ConnectDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private String address;
private int port;
private JFXButton saveButton;
@FXML JFXTextField addressTextField;
@FXML JFXTextField portTextField;
public ConnectDialog(Controller controller) {
super(controller, "/fxml/connect_dialog.fxml");
saveButton = createButton("Connect");
saveButton.setOnAction(event -> {
address = addressTextField.getText();
port = Integer.parseInt(portTextField.getText());
controller.connectToCore(address, port);
close();
});
addCancelButton();
setTitle("CORE Connection");
getStage().sizeToScene();
}
public void showDialog() {
addressTextField.setText(address);
portTextField.setText(Integer.toString(port));
show();
}
}

View file

@ -0,0 +1,58 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.client.ICoreClient;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialogLayout;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
@Data
public class CoreFoenixDialog extends JFXDialog {
private static final Logger logger = LogManager.getLogger();
private final Controller controller;
private final JFXDialog dialog;
private final JFXDialogLayout dialogLayout = new JFXDialogLayout();
private final Text heading = new Text();
public CoreFoenixDialog(Controller controller, String fxmlPath) {
this.controller = controller;
FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
loader.setController(this);
try {
Parent parent = loader.load();
dialogLayout.setBody(parent);
} catch (IOException ex) {
logger.error("error loading fxml: {}", fxmlPath, ex);
throw new RuntimeException(ex);
}
dialogLayout.setHeading(heading);
dialog = new JFXDialog(controller.getStackPane(), dialogLayout, DialogTransition.CENTER);
dialogLayout.setPrefWidth(800);
dialogLayout.setPrefHeight(600);
}
public void setOwner(Stage window) {
}
public ICoreClient getCoreClient() {
return controller.getCoreClient();
}
public JFXButton createButton(String text) {
JFXButton button = new JFXButton(text);
button.getStyleClass().add("core-button");
return button;
}
}

View file

@ -0,0 +1,28 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.scene.web.WebView;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class GeoDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private WebView webView;
@FXML JFXButton button;
public GeoDialog(Controller controller) {
super(controller, "/fxml/geo_dialog.fxml");
setTitle("Geo Display");
addCancelButton();
webView.getEngine().load(getClass().getResource("/html/geo.html").toExternalForm());
button.setOnAction(event -> {
webView.getEngine().executeScript("randomMarker();");
});
}
public void showDialog() {
show();
}
}

View file

@ -0,0 +1,80 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.ui.Toast;
import com.core.utils.ConfigUtils;
import com.core.utils.Configuration;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXColorPicker;
import com.jfoenix.controls.JFXTextField;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.paint.Color;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class GuiPreferencesDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private JFXTextField xmlFilePathTextField;
@FXML private JFXTextField mobilityFilePathTextField;
@FXML private JFXTextField shellCommandTextField;
@FXML private JFXTextField iconPathTextField;
@FXML private JFXColorPicker nodeLabelColorPicker;
@FXML private JFXColorPicker nodeLabelBackgroundColorPicker;
@FXML private JFXTextField throughputLimitTextField;
@FXML private JFXTextField throughputWidthTextField;
@FXML private JFXButton saveButton;
public GuiPreferencesDialog(Controller controller) {
super(controller, "/fxml/gui_preferences.fxml");
setTitle("GUI Preferences");
saveButton = createButton("Save");
saveButton.setOnAction(onSave);
addCancelButton();
}
private EventHandler<ActionEvent> onSave = event -> {
Configuration configuration = getController().getConfiguration();
configuration.setXmlPath(xmlFilePathTextField.getText());
configuration.setMobilityPath(mobilityFilePathTextField.getText());
configuration.setShellCommand(shellCommandTextField.getText());
configuration.setIconPath(iconPathTextField.getText());
configuration.setNodeLabelColor(nodeLabelColorPicker.getValue().toString());
configuration.setNodeLabelBackgroundColor(nodeLabelBackgroundColorPicker.getValue().toString());
configuration.setThroughputLimit(Double.parseDouble(throughputLimitTextField.getText()));
configuration.setThroughputWidth(Integer.parseInt(throughputWidthTextField.getText()));
getController().getNetworkGraph().updatePreferences(configuration);
try {
ConfigUtils.save(configuration);
Toast.success("Updated preferences");
} catch (IOException ex) {
Toast.error("Failure to update preferences", ex);
}
close();
};
public void showDialog() {
Configuration configuration = getController().getConfiguration();
xmlFilePathTextField.setText(configuration.getXmlPath());
mobilityFilePathTextField.setText(configuration.getMobilityPath());
shellCommandTextField.setText(configuration.getShellCommand());
iconPathTextField.setText(configuration.getIconPath());
nodeLabelColorPicker.setValue(Color.web(configuration.getNodeLabelColor()));
nodeLabelBackgroundColorPicker.setValue(Color.web(configuration.getNodeLabelBackgroundColor()));
String throughputLimit = null;
if (configuration.getThroughputLimit() != null) {
throughputLimit = configuration.getThroughputLimit().toString();
}
throughputLimitTextField.setText(throughputLimit);
String throughputWidth = null;
if (configuration.getThroughputWidth() != null) {
throughputWidth = configuration.getThroughputWidth().toString();
}
throughputWidthTextField.setText(throughputWidth);
show();
}
}

View file

@ -0,0 +1,77 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.Hook;
import com.core.data.SessionState;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXTextArea;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.stream.Collectors;
public class HookDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private static final String DEFAULT_DATA = "#!/bin/sh\n" +
"# session hook script; write commands here to execute\n" +
"# on the host at the specified state\n";
@FXML private JFXComboBox<String> stateCombo;
@FXML private JFXTextField fileTextField;
@FXML private JFXTextArea fileData;
private JFXButton saveButton;
public HookDialog(Controller controller) {
super(controller, "/fxml/hook_dialog.fxml");
setTitle("Hook");
saveButton = createButton("Save");
addCancelButton();
stateCombo.getItems()
.addAll(Arrays.stream(SessionState.values()).map(Enum::name).sorted().collect(Collectors.toList()));
stateCombo.getSelectionModel().select(SessionState.RUNTIME.name());
}
public Hook getHook() {
Hook hook = new Hook();
hook.setFile(fileTextField.getText());
hook.setData(fileData.getText());
SessionState state = SessionState.valueOf(stateCombo.getSelectionModel().getSelectedItem());
hook.setState(state.getValue());
hook.setStateDisplay(state.name());
return hook;
}
public void showEditDialog(Hook hook, Runnable editHandler, Runnable cancelHandler) {
fileData.setText(hook.getData());
stateCombo.getSelectionModel().select(hook.getState());
fileTextField.setText(hook.getFile());
fileTextField.setDisable(true);
saveButton.setOnAction(event -> {
logger.info("create hook");
editHandler.run();
close();
});
show();
}
public void showDialog(String fileName, Runnable saveHandler, Runnable cancelHandler) {
fileData.setText(DEFAULT_DATA);
stateCombo.getSelectionModel().select(SessionState.RUNTIME.name());
fileTextField.setText(fileName);
fileTextField.setDisable(false);
saveButton.setOnAction(event -> {
logger.info("create hook");
saveHandler.run();
close();
});
show();
}
}

View file

@ -0,0 +1,117 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.Hook;
import com.core.data.SessionState;
import com.core.ui.Toast;
import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.List;
public class HooksDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private HookDialog hookDialog;
private int fileCount = 0;
@FXML private TableView<Hook> hooksTable;
@FXML private TableColumn<Hook, Integer> fileColumn;
@FXML private TableColumn<Hook, Integer> stateColumn;
public HooksDialog(Controller controller) {
super(controller, "/fxml/hooks_dialog.fxml");
hookDialog = new HookDialog(controller);
setTitle("Hooks");
JFXButton createButton = createButton("Create");
createButton.setOnAction(event -> {
logger.info("showing create hook");
hookDialog.showDialog(nextFile(), saveHandler, cancelHandler);
});
JFXButton editButton = createButton("Edit");
editButton.setDisable(true);
editButton.setOnAction(event -> {
logger.info("edit hook");
Hook hook = hooksTable.getSelectionModel().getSelectedItem();
hookDialog.showEditDialog(hook, editHandler, cancelHandler);
});
JFXButton deleteButton = createButton("Delete");
deleteButton.setDisable(true);
deleteButton.setOnAction(event -> {
logger.info("delete hook");
Hook hook = hooksTable.getSelectionModel().getSelectedItem();
hooksTable.getItems().remove(hook);
});
addCancelButton();
hooksTable.getSelectionModel().selectedItemProperty().addListener((ov, old, current) -> {
boolean hasNoSelection = current == null;
editButton.setDisable(hasNoSelection);
deleteButton.setDisable(hasNoSelection);
});
fileColumn.setCellValueFactory(new PropertyValueFactory<>("file"));
stateColumn.setCellValueFactory(new PropertyValueFactory<>("stateDisplay"));
}
@Override
public void setOwner(Stage window) {
super.setOwner(window);
hookDialog.setOwner(window);
}
private Runnable saveHandler = () -> {
Hook hook = hookDialog.getHook();
hooksTable.getItems().addAll(hook);
};
private Runnable editHandler = () -> {
Hook hook = hooksTable.getSelectionModel().getSelectedItem();
Hook update = hookDialog.getHook();
SessionState state = SessionState.valueOf(update.getStateDisplay());
hook.setState(state.getValue());
hook.setData(update.getData());
};
private Runnable cancelHandler = this::showDialog;
private String nextFile() {
return String.format("file%s.sh", ++fileCount);
}
public List<Hook> getHooks() {
return hooksTable.getItems();
}
public void updateHooks() {
logger.info("updating hooks");
hooksTable.getItems().clear();
// update hooks
try {
List<Hook> hooks = getCoreClient().getHooks();
for (Hook hook : hooks) {
SessionState state = SessionState.get(hook.getState());
hook.setStateDisplay(state.name());
hooksTable.getItems().add(hook);
}
} catch (IOException ex) {
logger.error("error getting current hooks", ex);
Toast.error("Error getting current hooks");
}
}
public void showDialog() {
// clear current selection
hooksTable.getSelectionModel().clearSelection();
show();
}
}

View file

@ -0,0 +1,73 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.LocationConfig;
import com.core.ui.Toast;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.DoubleValidator;
import javafx.fxml.FXML;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class LocationDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private JFXTextField scaleTextField;
@FXML private JFXTextField xTextField;
@FXML private JFXTextField yTextField;
@FXML private JFXTextField latTextField;
@FXML private JFXTextField lonTextField;
@FXML private JFXTextField altTextField;
private JFXButton saveButton;
public LocationDialog(Controller controller) {
super(controller, "/fxml/location_dialog.fxml");
setTitle("Location Configuration");
saveButton = createButton("Save");
saveButton.setOnAction(event -> {
boolean result = scaleTextField.validate();
if (!result) {
return;
}
LocationConfig config = new LocationConfig();
config.setScale(getDouble(scaleTextField));
config.getPosition().setX(getDouble(xTextField));
config.getPosition().setY(getDouble(yTextField));
config.getLocation().setLatitude(getDouble(latTextField));
config.getLocation().setLongitude(getDouble(lonTextField));
config.getLocation().setAltitude(getDouble(altTextField));
try {
getCoreClient().setLocationConfig(config);
close();
} catch (IOException ex) {
Toast.error("error setting location config", ex);
}
});
addCancelButton();
DoubleValidator validator = new DoubleValidator();
scaleTextField.getValidators().add(validator);
}
public Double getDouble(JFXTextField textField) {
return Double.parseDouble(textField.getText());
}
public void showDialog() {
try {
LocationConfig config = getCoreClient().getLocationConfig();
scaleTextField.setText(config.getScale().toString());
xTextField.setText(config.getPosition().getX().toString());
yTextField.setText(config.getPosition().getY().toString());
latTextField.setText(config.getLocation().getLatitude().toString());
lonTextField.setText(config.getLocation().getLongitude().toString());
altTextField.setText(config.getLocation().getAltitude().toString());
show();
} catch (IOException ex) {
Toast.error("error getting location config", ex);
}
}
}

View file

@ -0,0 +1,113 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.data.MobilityConfig;
import com.core.ui.Toast;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.controls.JFXToggleButton;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
@Data
public class MobilityDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private JFXTextField fileTextField;
@FXML private JFXTextField refreshTextField;
@FXML private JFXToggleButton loopToggleButton;
@FXML private JFXTextField nodeMappingTextField;
@FXML private JFXTextField autoStartTextField;
@FXML private JFXTextField startTextField;
@FXML private JFXTextField pauseTextField;
@FXML private JFXTextField stopTextField;
private CoreNode node;
public MobilityDialog(Controller controller) {
super(controller, "/fxml/mobility_dialog.fxml");
setTitle("Mobility Script");
JFXButton saveButton = createButton("Save");
saveButton.setOnAction(event -> {
MobilityConfig mobilityConfig = new MobilityConfig();
mobilityConfig.setFile(fileTextField.getText());
mobilityConfig.setScriptFile(new File(mobilityConfig.getFile()));
mobilityConfig.setAutostart(autoStartTextField.getText());
String loop = loopToggleButton.isSelected() ? "1" : "";
mobilityConfig.setLoop(loop);
mobilityConfig.setRefresh(Integer.parseInt(refreshTextField.getText()));
mobilityConfig.setMap(nodeMappingTextField.getText());
mobilityConfig.setStartScript(startTextField.getText());
mobilityConfig.setPauseScript(pauseTextField.getText());
mobilityConfig.setStopScript(stopTextField.getText());
try {
boolean result = controller.getCoreClient().setMobilityConfig(node, mobilityConfig);
if (result) {
getController().getMobilityScripts().put(node.getId(), mobilityConfig);
Toast.info(String.format("Set mobility configuration for %s", node.getName()));
} else {
Toast.error(String.format("Error setting mobility configuration for %s", node.getName()));
}
} catch (IOException ex) {
Toast.error("error setting mobility configuration", ex);
}
close();
});
addCancelButton();
}
@FXML
private void onSelectAction(ActionEvent event) {
JFXButton button = (JFXButton) event.getSource();
GridPane gridPane = (GridPane) button.getParent();
JFXTextField textField = (JFXTextField) gridPane.getChildren().get(0);
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select File");
String mobilityPath = getController().getConfiguration().getMobilityPath();
fileChooser.setInitialDirectory(new File(mobilityPath));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Mobility",
"*.scen"));
try {
File file = fileChooser.showOpenDialog(getController().getWindow());
if (file != null) {
logger.info("opening session xml: {}", file.getPath());
textField.setText(file.getPath());
}
} catch (IllegalArgumentException ex) {
Toast.error(String.format("Invalid mobility directory: %s",
getController().getConfiguration().getMobilityPath()));
}
}
public void showDialog(CoreNode node) {
this.node = node;
try {
MobilityConfig mobilityConfig = getController().getCoreClient().getMobilityConfig(this.node);
fileTextField.setText(mobilityConfig.getFile());
autoStartTextField.setText(mobilityConfig.getAutostart());
boolean loop = "1".equals(mobilityConfig.getLoop());
loopToggleButton.setSelected(loop);
refreshTextField.setText(mobilityConfig.getRefresh().toString());
nodeMappingTextField.setText(mobilityConfig.getMap());
startTextField.setText(mobilityConfig.getStartScript());
pauseTextField.setText(mobilityConfig.getPauseScript());
stopTextField.setText(mobilityConfig.getStopScript());
} catch (IOException ex) {
Toast.error("error getting mobility config", ex);
}
show();
}
}

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