Merge pull request #255 from coreemu/develop

Merging latest from develop
This commit is contained in:
bharnden 2019-06-10 22:21:52 -07:00 committed by GitHub
commit 1d1eba84fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
293 changed files with 21888 additions and 6838 deletions

9
.gitignore vendored
View file

@ -8,7 +8,7 @@ Makefile
Makefile.in
aclocal.m4
autom4te.cache
config
/config
config.h
config.h.in
config.log
@ -17,6 +17,10 @@ configure
debian
stamp-h1
# generated protobuf files
daemon/core/api/grpc/core_pb2.py
daemon/core/api/grpc/core_pb2_grpc.py
# python build directory
dist
@ -51,3 +55,6 @@ coverage.xml
netns/setup.py
daemon/setup.py
ns3/setup.py
# ignore corefx build
corefx/target

371
CHANGELOG.md Normal file
View file

@ -0,0 +1,371 @@
## 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

342
Changelog
View file

@ -1,342 +0,0 @@
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

View file

@ -28,7 +28,7 @@ EXTRA_DIST = bootstrap.sh \
LICENSE \
README.md \
ASSIGNMENT_OF_COPYRIGHT.pdf \
Changelog \
CHANGELOG.md \
.version \
.version.date
@ -44,38 +44,25 @@ DISTCLEANFILES = aclocal.m4 \
MAINTAINERCLEANFILES = .version \
.version.date
define fpm-python =
fpm -s python -t $1 \
-m "$(PACKAGE_MAINTAINERS)" \
--vendor "$(PACKAGE_VENDOR)" \
$2
endef
define fpm-gui =
fpm -s dir -t $1 -n core-gui \
if PYTHON3
PYTHON_DEP = python3 >= 3.0
else
PYTHON_DEP = python >= 2.7, python < 3.0
endif
define fpm-rpm =
fpm -s dir -t rpm -n core \
-m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "Common Open Research Emulator GUI front-end" \
--url http://www.nrl.navy.mil/itd/ncs/products/core \
--description "Common Open Research Emulator" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core-gui_VERSION_ARCH.$1 \
-p core_$(PYTHON)_VERSION_ARCH.rpm \
-v $(PACKAGE_VERSION) \
-d "bash" \
--rpm-init scripts/core-daemon \
-d "tcl" \
-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 "bash >= 3.0" \
-d "bridge-utils" \
@ -83,19 +70,23 @@ fpm -s python -t rpm \
-d "iproute" \
-d "libev" \
-d "net-tools" \
-d "python >= 2.7, python < 3.0" \
netns/setup.py daemon/setup.py
-d "$(PYTHON_DEP)" \
-C $(DESTDIR)
endef
define fpm-daemon-deb =
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 \
define fpm-deb =
fpm -s dir -t deb -n core \
-m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "Common Open Research Emulator" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_$(PYTHON)_VERSION_ARCH.deb \
-v $(PACKAGE_VERSION) \
--deb-systemd scripts/core-daemon.service \
-d "tcl" \
-d "tk" \
-d "libtk-img" \
-d "procps" \
-d "libc6 >= 2.14" \
-d "bash >= 3.0" \
@ -103,21 +94,15 @@ fpm -s python -t deb \
-d "ebtables" \
-d "iproute2" \
-d "libev4" \
-d "python (>= 2.7), python (<< 3.0)" \
--deb-recommends quagga \
netns/setup.py daemon/setup.py
-d "$(PYTHON_DEP)" \
-C $(DESTDIR)
endef
.PHONY: fpm
fpm: clean-local-fpm
$(MAKE) -C gui install DESTDIR=$(DESTDIR)
$(call fpm-gui,rpm)
$(call fpm-gui,deb,-d "libtk-img")
$(call fpm-python,rpm,ns3/setup.py)
$(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)
$(MAKE) install DESTDIR=$(DESTDIR)
$(call fpm-deb)
$(call fpm-rpm)
.PHONY: clean-local-fpm
clean-local-fpm:

View file

@ -2,7 +2,7 @@
CORE: Common Open Research Emulator
Copyright (c)2005-2018 the Boeing Company.
Copyright (c)2005-2019 the Boeing Company.
See the LICENSE file included in this distribution.
@ -17,13 +17,13 @@ scripting network emulation.
## Documentation and Examples
* Documentation hosted on GitHub
* <http://coreemu.github.io/core/>
* <http://coreemu.github.io/core/>
* Basic Script Examples
* [Examples](daemon/examples/api)
* [Examples](daemon/examples/api)
* Custom Service Example
* [sample.py](daemon/examples/myservices/sample.py)
* [sample.py](daemon/examples/myservices/sample.py)
* Custom Emane Model Example
* [examplemodel.py](daemon/examples/myemane/examplemodel.py)
* [examplemodel.py](daemon/examples/myemane/examplemodel.py)
## Support

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 5.2.2, core-dev@nrl.navy.mil)
AC_INIT(core, 5.3.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])
@ -14,7 +14,7 @@ AM_INIT_AUTOMAKE([tar-ustar])
# define variables used for packaging and date display
PACKAGE_DATE=m4_esyscmd_s([date +%Y%m%d])
PACKAGE_VENDOR="CORE Developers"
PACKAGE_MAINTAINERS="$PACKAGE_VENDOR <$PACKAGE_BUGREPORT>"
PACKAGE_MAINTAINERS="$PACKAGE_VENDOR"
# core specific variables
CORE_LIB_DIR="\${prefix}/lib/core"
@ -110,6 +110,8 @@ if test "x$enable_daemon" = "xyes"; then
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
AM_PATH_PYTHON(2.7)
AM_CONDITIONAL([PYTHON3], [test "x$PYTHON" == "xpython3"])
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH)
if test "x$brctl_path" = "xno" ; then
@ -146,7 +148,7 @@ if test "x$enable_daemon" = "xyes"; then
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()"`
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"
@ -240,6 +242,7 @@ AC_CONFIG_FILES([Makefile
daemon/Makefile
daemon/doc/Makefile
daemon/doc/conf.py
daemon/proto/Makefile
netns/Makefile
netns/version.h
ns3/Makefile],)
@ -263,7 +266,7 @@ GUI:
Daemon:
Daemon path: ${bindir}
Daemon config: ${CORE_CONF_DIR}
Python modules: ${pythondir}
Python: ${PYTHON}
Logs: ${CORE_STATE_DIR}/log
Startup: ${with_startup}

156
corefx/pom.xml Normal file
View file

@ -0,0 +1,156 @@
<?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>
<grpc.version>1.20.0</grpc.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>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>8.8.3</version>
<configuration>
<mainClass>com.core.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,529 @@
package com.core;
import com.core.client.ICoreClient;
import com.core.client.grpc.CoreGrpcClient;
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.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.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 CoreGrpcClient();
// 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) {
executorService.submit(() -> {
try {
coreClient.setConnection(address, port);
initialJoin();
} catch (IOException 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();
logger.info("emane models: {}", emaneModels);
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);
// setup event handlers
coreClient.setupEventHandlers(this);
// 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 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 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) {
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(Controller.this);
} 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,121 @@
package com.core.client;
import com.core.Controller;
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(Controller controller) 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;
void setupEventHandlers(Controller controller) throws IOException;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,430 @@
package com.core.client.rest;
import com.core.Controller;
import com.core.client.ICoreClient;
import com.core.data.*;
import com.core.utils.WebUtils;
import com.core.websocket.CoreWebSocket;
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.net.URISyntaxException;
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;
private CoreWebSocket coreWebSocket;
@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(Controller controller) 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);
}
@Override
public void setupEventHandlers(Controller controller) throws IOException {
coreWebSocket.stop();
coreWebSocket = new CoreWebSocket(controller);
try {
coreWebSocket.start(address, port);
} catch (URISyntaxException ex) {
throw new IOException("error starting web socket", ex);
}
}
}

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();
}
}

View file

@ -0,0 +1,89 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.data.MobilityConfig;
import com.core.data.SessionState;
import com.core.ui.Toast;
import com.core.utils.IconUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.stage.Modality;
import javafx.util.Duration;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
@Data
public class MobilityPlayerDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private static final int ICON_SIZE = 20;
private static final String ICON_FILL = "white";
@FXML private Label label;
@FXML private JFXButton playButton;
@FXML private JFXButton pauseButton;
@FXML private JFXButton stopButton;
@FXML private JFXProgressBar progressBar;
private final CoreNode node;
private MobilityConfig mobilityConfig;
public MobilityPlayerDialog(Controller controller, CoreNode node) {
super(controller, "/fxml/mobility_player.fxml", Modality.NONE);
this.node = node;
playButton.setGraphic(IconUtils.get("play_arrow", ICON_SIZE, ICON_FILL));
playButton.setOnAction(event -> action("start"));
pauseButton.setGraphic(IconUtils.get("pause", ICON_SIZE, ICON_FILL));
pauseButton.setOnAction(event -> action("pause"));
stopButton.setGraphic(IconUtils.get("stop", ICON_SIZE, ICON_FILL));
stopButton.setOnAction(event -> action("stop"));
addCancelButton();
setTitle(String.format("%s Mobility Script", node.getName()));
getStage().sizeToScene();
}
public void event(SessionState state, Integer start, Integer end) {
Platform.runLater(() -> {
playButton.setDisable(false);
stopButton.setDisable(false);
switch (state) {
case START:
playButton.setDisable(true);
progressBar.setProgress(0);
Timeline timeline = new Timeline();
KeyValue keyValue = new KeyValue(progressBar.progressProperty(), 1.0);
KeyFrame keyFrame = new KeyFrame(new Duration(end * 1000), keyValue);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
break;
case STOP:
stopButton.setDisable(true);
break;
}
});
}
private void action(String action) {
try {
getCoreClient().mobilityAction(node, action);
} catch (IOException ex) {
Toast.error(String.format("mobility error: %s", action), ex);
}
}
public void showDialog(MobilityConfig mobilityConfig) {
this.label.setText(mobilityConfig.getFile());
this.mobilityConfig = mobilityConfig;
show();
}
}

View file

@ -0,0 +1,103 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.ConfigGroup;
import com.core.data.ConfigOption;
import com.core.data.CoreNode;
import com.core.ui.Toast;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.List;
public class NodeEmaneDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private final JFXButton saveButton;
private CoreNode coreNode;
@FXML private JFXComboBox<String> modelCombo;
@FXML private JFXButton modelButton;
@FXML private JFXButton emaneButton;
public NodeEmaneDialog(Controller controller) {
super(controller, "/fxml/node_emane_dialog.fxml");
saveButton = createButton("Save");
saveButton.setOnAction(event -> {
String model = modelCombo.getSelectionModel().getSelectedItem();
coreNode.setEmane(model);
close();
});
addCancelButton();
emaneButton.setOnAction(this::emaneButtonHandler);
modelButton.setOnAction(this::emaneModelButtonHandler);
}
public void setModels(List<String> models) {
models.sort(String::compareTo);
modelCombo.getItems().setAll(models);
modelCombo.getSelectionModel().selectFirst();
}
public List<String> getModels() {
return modelCombo.getItems();
}
private void emaneButtonHandler(ActionEvent event) {
try {
List<ConfigGroup> configGroups = getCoreClient().getEmaneConfig(coreNode);
logger.debug("emane model config: {}", configGroups);
String title = String.format("%s EMANE Config", coreNode.getName());
getController().getConfigDialog().showDialog(title, configGroups, () -> {
List<ConfigOption> options = getController().getConfigDialog().getOptions();
try {
getCoreClient().setEmaneConfig(coreNode, options);
} catch (IOException ex) {
logger.error("set emane config error", ex);
}
});
} catch (IOException ex) {
Toast.error("error getting emane model config", ex);
}
}
private void emaneModelButtonHandler(ActionEvent event) {
String model = modelCombo.getSelectionModel().getSelectedItem();
displayEmaneModelConfig(coreNode.getId(), model);
}
public void displayEmaneModelConfig(Integer id, String model) {
try {
List<ConfigGroup> configGroups = getCoreClient().getEmaneModelConfig(id, model);
logger.debug("emane model config: {}", configGroups);
String title = String.format("EMANE(%s) %s Config", id, model);
getController().getConfigDialog().showDialog(title, configGroups, () -> {
List<ConfigOption> options = getController().getConfigDialog().getOptions();
try {
getCoreClient().setEmaneModelConfig(id, model, options);
} catch (IOException ex) {
Toast.error("set emane model config error", ex);
}
});
} catch (IOException ex) {
Toast.error("error getting emane model config", ex);
}
}
private void setDisabled(boolean isDisabled) {
saveButton.setDisable(isDisabled);
modelCombo.setDisable(isDisabled);
}
public void showDialog(CoreNode node) {
coreNode = node;
setTitle(String.format("%s - EMANE", node.getName()));
setDisabled(getCoreClient().isRunning());
show();
}
}

View file

@ -0,0 +1,148 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.ui.ServiceItem;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXScrollPane;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class NodeServicesDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private final Map<String, List<ServiceItem>> serviceItemGroups = new HashMap<>();
private final Map<String, ServiceItem> serviceItemMap = new HashMap<>();
private CoreNode node;
private int index = 0;
@FXML private GridPane gridPane;
@FXML private ScrollPane scrollPane;
@FXML private JFXListView<String> groupListView;
@FXML private JFXListView<String> activeListView;
@FXML private JFXButton removeButton;
@FXML private JFXButton editButton;
public NodeServicesDialog(Controller controller) {
super(controller, "/fxml/node_services_dialog.fxml");
JFXButton saveButton = createButton("Save");
saveButton.setOnAction(event -> {
for (List<ServiceItem> items : serviceItemGroups.values()) {
for (ServiceItem item : items) {
if (item.getCheckBox().isSelected()) {
logger.info("setting service for node({}): {}", node.getName(), item.getService());
node.getServices().add(item.getService());
}
}
}
close();
});
addCancelButton();
groupListView.getSelectionModel().selectedItemProperty().addListener((ov, previous, current) -> {
if (current == null) {
return;
}
updateItems(current);
});
activeListView.getSelectionModel().selectedItemProperty().addListener((ov, previous, current) -> {
boolean isDisabled = current == null;
removeButton.setDisable(isDisabled);
editButton.setDisable(isDisabled);
});
removeButton.setOnAction(event -> {
String service = activeListView.getSelectionModel().getSelectedItem();
activeListView.getItems().remove(service);
ServiceItem serviceItem = serviceItemMap.get(service);
serviceItem.getCheckBox().setSelected(false);
});
editButton.setOnAction(event -> {
String service = activeListView.getSelectionModel().getSelectedItem();
getController().getServiceDialog().showDialog(node, service);
});
}
public void setServices(Map<String, List<String>> serviceGroups) {
serviceItemGroups.clear();
serviceGroups.keySet().stream()
.sorted()
.forEach(group -> {
groupListView.getItems().add(group);
serviceGroups.get(group).stream()
.sorted()
.forEach(service -> {
ServiceItem serviceItem = new ServiceItem(service);
List<ServiceItem> items = serviceItemGroups.computeIfAbsent(
group, k -> new ArrayList<>());
items.add(serviceItem);
if (serviceItem.getCheckBox().isSelected()) {
activeListView.getItems().add(serviceItem.getService());
}
serviceItem.getCheckBox().setOnAction(event -> {
if (serviceItem.getCheckBox().isSelected()) {
activeListView.getItems().add(service);
FXCollections.sort(activeListView.getItems());
} else {
activeListView.getItems().remove(service);
}
});
serviceItemMap.put(service, serviceItem);
});
});
groupListView.getSelectionModel().selectFirst();
JFXScrollPane.smoothScrolling(scrollPane);
}
private void updateItems(String group) {
logger.debug("updating services for group: {}", group);
clearAvailableServices();
List<ServiceItem> items = serviceItemGroups.get(group);
for (ServiceItem item : items) {
gridPane.addRow(index++, item.getCheckBox());
}
}
private void clearAvailableServices() {
gridPane.getChildren().clear();
index = 0;
}
public void showDialog(CoreNode node) {
this.node = node;
setTitle(String.format("%s - Services", node.getName()));
groupListView.getSelectionModel().selectFirst();
activeListView.getItems().clear();
Set<String> nodeServices = node.getServices();
if (nodeServices.isEmpty()) {
nodeServices = getController().getDefaultServices().get(node.getModel());
}
for (List<ServiceItem> items : serviceItemGroups.values()) {
for (ServiceItem item : items) {
boolean selected = nodeServices.contains(item.getService());
item.getCheckBox().setSelected(selected);
if (item.getCheckBox().isSelected()) {
activeListView.getItems().add(item.getService());
}
}
}
FXCollections.sort(activeListView.getItems());
show();
}
}

View file

@ -0,0 +1,74 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.NodeType;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.SelectionMode;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
public class NodeTypeCreateDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML private JFXListView<String> servicesListView;
@FXML private JFXTextField modelTextField;
@FXML private JFXTextField displayTextField;
private Runnable onCreateHandler;
public NodeTypeCreateDialog(Controller controller) {
super(controller, "/fxml/node_type_create_dialog.fxml");
setTitle("Create Node Configuration");
JFXButton saveButton = createButton("Create");
saveButton.setOnAction(event -> {
onCreateHandler.run();
close();
});
addCancelButton();
servicesListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
displayTextField.focusedProperty().addListener((obs, prev, current) -> {
if (!current) {
return;
}
String model = modelTextField.getText();
if (!model.isEmpty()) {
displayTextField.setText(model.substring(0, 1).toUpperCase() + model.substring(1));
}
});
}
public NodeType getCreatedNodeType() {
NodeType nodeType = new NodeType(NodeType.DEFAULT, modelTextField.getText(), displayTextField.getText(),
"/icons/host-100.png");
nodeType.getServices().addAll(servicesListView.getSelectionModel().getSelectedItems());
return nodeType;
}
public void setServices(Map<String, List<String>> serviceGroups) {
List<String> services = new ArrayList<>();
for (List<String> groupServices : serviceGroups.values()) {
services.addAll(groupServices);
}
services.sort(String::compareTo);
servicesListView.getItems().setAll(services);
}
public void showDialog(Runnable runnable) {
onCreateHandler = runnable;
modelTextField.setText("");
displayTextField.setText("");
servicesListView.getSelectionModel().clearSelection();
show();
}
}

View file

@ -0,0 +1,136 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.data.CoreNode;
import com.core.data.NodeType;
import com.core.ui.Toast;
import com.core.utils.NodeTypeConfig;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class NodeTypesDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private final Map<String, NodeType> nodeTypeMap = new HashMap<>();
private NodeType selectedNodeType;
@FXML private JFXListView<String> listView;
@FXML private JFXTextField modelTextField;
@FXML private JFXTextField displayTextField;
@FXML private JFXTextField iconTextField;
@FXML private JFXButton iconButton;
@FXML private ImageView iconImage;
@FXML private JFXButton saveButton;
@FXML private JFXButton addButton;
@FXML private JFXButton deleteButton;
@FXML private JFXListView<String> nodeServicesListView;
public NodeTypesDialog(Controller controller) {
super(controller, "/fxml/node_types_dialog.fxml");
setTitle("Node Configuration");
JFXButton closeButton = createButton("Close");
closeButton.setOnAction(event -> close());
listView.getSelectionModel().selectedItemProperty().addListener((ov, prev, current) -> {
if (current == null) {
return;
}
NodeType nodeType = nodeTypeMap.get(current);
modelTextField.setText(nodeType.getModel());
displayTextField.setText(nodeType.getDisplay());
iconTextField.setText(nodeType.getIcon());
iconImage.setImage(new Image(nodeType.getIcon()));
selectedNodeType = nodeType;
Set<String> services = nodeType.getServices();
nodeServicesListView.getItems().setAll(services);
});
iconButton.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Icon");
fileChooser.setInitialDirectory(new File(getController().getConfiguration().getIconPath()));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PNG", "*.png"));
File file = fileChooser.showOpenDialog(controller.getWindow());
if (file != null) {
String uri = file.toURI().toString();
iconImage.setImage(new Image(uri));
iconTextField.setText(uri);
}
});
saveButton.setOnAction(event -> {
String iconPath = iconTextField.getText();
selectedNodeType.setIcon(iconPath);
for (CoreNode node : controller.getNetworkGraph().getGraph().getVertices()) {
if (selectedNodeType != node.getNodeType()) {
continue;
}
node.setNodeType(selectedNodeType);
}
controller.getNetworkGraph().getGraphViewer().repaint();
controller.getGraphToolbar().updateNodeType(selectedNodeType.getId(), iconPath);
Toast.info(String.format("Node %s Updated", selectedNodeType.getDisplay()));
});
deleteButton.setOnAction(event -> {
String display = listView.getSelectionModel().getSelectedItem();
NodeType nodeType = nodeTypeMap.get(display);
NodeType.remove(nodeType);
listView.getItems().remove(display);
NodeTypeConfig nodeTypeConfig = createNodeTypeConfig(nodeType);
getController().getDefaultServices().remove(nodeTypeConfig.getModel());
getController().getConfiguration().getNodeTypeConfigs().remove(nodeTypeConfig);
getController().updateNodeTypes();
});
addButton.setOnAction(event -> {
NodeTypeCreateDialog nodeTypeCreateDialog = getController().getNodeTypeCreateDialog();
nodeTypeCreateDialog.showDialog(() -> {
NodeType nodeType = nodeTypeCreateDialog.getCreatedNodeType();
NodeType.add(nodeType);
nodeTypeMap.put(nodeType.getDisplay(), nodeType);
listView.getItems().add(nodeType.getDisplay());
NodeTypeConfig nodeTypeConfig = createNodeTypeConfig(nodeType);
getController().getDefaultServices().put(nodeTypeConfig.getModel(), nodeTypeConfig.getServices());
getController().getConfiguration().getNodeTypeConfigs().add(nodeTypeConfig);
getController().updateNodeTypes();
});
});
}
private NodeTypeConfig createNodeTypeConfig(NodeType nodeType) {
return new NodeTypeConfig(
nodeType.getModel(),
nodeType.getDisplay(),
nodeType.getIcon(),
nodeType.getServices()
);
}
public void showDialog() {
listView.getItems().clear();
nodeTypeMap.clear();
for (NodeType nodeType : NodeType.getAll()) {
if (nodeType.getValue() != NodeType.DEFAULT) {
continue;
}
nodeTypeMap.put(nodeType.getDisplay(), nodeType);
listView.getItems().add(nodeType.getDisplay());
}
listView.getSelectionModel().selectFirst();
show();
}
}

View file

@ -0,0 +1,76 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.client.rest.WlanConfig;
import com.core.data.CoreNode;
import com.core.ui.Toast;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class NodeWlanDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private final JFXButton saveButton;
private CoreNode coreNode;
@FXML private JFXTextField rangeTextField;
@FXML private JFXTextField bandwidthTextField;
@FXML private JFXTextField delayTextField;
@FXML private JFXTextField lossTextField;
@FXML private JFXTextField jitterTextField;
public NodeWlanDialog(Controller controller) {
super(controller, "/fxml/wlan_dialog.fxml");
saveButton = createButton("Save");
saveButton.setOnAction(event -> {
try {
WlanConfig config = new WlanConfig();
config.setRange(rangeTextField.getText());
config.setBandwidth(bandwidthTextField.getText());
config.setJitter(jitterTextField.getText());
config.setDelay(delayTextField.getText());
config.setError(lossTextField.getText());
getCoreClient().setWlanConfig(coreNode, config);
} catch (IOException ex) {
logger.error("error setting wlan config", ex);
Toast.error("Error setting wlan config");
}
close();
});
addCancelButton();
}
private void setDisabled(boolean isDisabled) {
rangeTextField.setDisable(isDisabled);
bandwidthTextField.setDisable(isDisabled);
jitterTextField.setDisable(isDisabled);
delayTextField.setDisable(isDisabled);
lossTextField.setDisable(isDisabled);
saveButton.setDisable(isDisabled);
}
public void showDialog(CoreNode node) {
coreNode = node;
setTitle(String.format("%s - WLAN", node.getName()));
setDisabled(getCoreClient().isRunning());
try {
WlanConfig wlanConfig = getCoreClient().getWlanConfig(coreNode);
rangeTextField.setText(wlanConfig.getRange());
bandwidthTextField.setText(wlanConfig.getBandwidth());
jitterTextField.setText(wlanConfig.getJitter());
delayTextField.setText(wlanConfig.getDelay());
lossTextField.setText(wlanConfig.getError());
} catch (IOException ex) {
logger.error("error getting wlan config", ex);
Toast.error("Error getting wlan config");
}
show();
}
}

View file

@ -0,0 +1,119 @@
package com.core.ui.dialogs;
import com.core.Controller;
import com.core.client.rest.ServiceFile;
import com.core.data.CoreNode;
import com.core.data.CoreService;
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.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ServiceDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
private CoreNode coreNode;
private CoreService coreService;
private String serviceName;
@FXML private JFXComboBox<String> executablesComboBox;
@FXML private JFXComboBox<String> dependenciesComboBox;
@FXML private JFXTextField validationModeTextField;
@FXML private JFXTextField validationTimerTextField;
@FXML private JFXComboBox<String> directoriesComboBox;
@FXML private JFXComboBox<String> filesComboBox;
@FXML private JFXTextArea fileTextArea;
@FXML private JFXTextArea startupTextArea;
@FXML private JFXTextArea validateTextArea;
@FXML private JFXTextArea shutdownTextArea;
public ServiceDialog(Controller controller) {
super(controller, "/fxml/service_dialog.fxml");
JFXButton saveButton = createButton("Save");
saveButton.setOnAction(event -> {
// retrieve service data
coreService.setStartup(textToList(startupTextArea.getText()));
coreService.setValidate(textToList(validateTextArea.getText()));
coreService.setShutdown(textToList(shutdownTextArea.getText()));
// service file data
String fileName = filesComboBox.getSelectionModel().getSelectedItem();
String data = fileTextArea.getText();
ServiceFile serviceFile = new ServiceFile(fileName, data);
try {
getCoreClient().setService(coreNode, serviceName, coreService);
getCoreClient().setServiceFile(coreNode, serviceName, serviceFile);
} catch (IOException ex) {
logger.error("error setting node service", ex);
}
close();
});
addCancelButton();
filesComboBox.valueProperty().addListener((ov, previous, current) -> {
if (current == null) {
return;
}
try {
String file = controller.getCoreClient().getServiceFile(coreNode, serviceName, current);
fileTextArea.setText(file);
} catch (IOException ex) {
logger.error("error getting file data", ex);
}
});
}
private List<String> textToList(String text) {
return Arrays.stream(text.split("\\n"))
.filter(x -> !x.isEmpty())
.collect(Collectors.toList());
}
public void showDialog(CoreNode node, String service) {
setTitle(String.format("%s - %s", node.getName(), service));
try {
coreNode = node;
// node must exist to get file data
getCoreClient().createNode(node);
coreService = getCoreClient().getService(node, service);
logger.info("service dialog: {}", coreService);
serviceName = service;
directoriesComboBox.getItems().setAll(coreService.getDirs());
directoriesComboBox.getSelectionModel().selectFirst();
executablesComboBox.getItems().setAll(coreService.getExecutables());
executablesComboBox.getSelectionModel().selectFirst();
dependenciesComboBox.getItems().setAll(coreService.getDependencies());
dependenciesComboBox.getSelectionModel().selectFirst();
validationModeTextField.setText(coreService.getValidationMode());
validationTimerTextField.setText(coreService.getValidationTimer());
filesComboBox.getItems().setAll(coreService.getConfigs());
filesComboBox.getSelectionModel().selectFirst();
startupTextArea.setText(String.join("\n", coreService.getStartup()));
validateTextArea.setText(String.join("\n", coreService.getValidate()));
shutdownTextArea.setText(String.join("\n", coreService.getShutdown()));
} catch (IOException ex) {
logger.error("error getting service data", ex);
}
show();
}
}

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