From bdd469d38681518e6285111a906dd6faa8a40154 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Tue, 11 Sep 2018 09:52:14 -0700 Subject: [PATCH] initial commit for core javafx gui --- .gitignore | 3 + corefx/pom.xml | 110 +++ corefx/src/main/java/com/core/Controller.java | 250 +++++ corefx/src/main/java/com/core/CoreClient.java | 223 +++++ corefx/src/main/java/com/core/Main.java | 66 ++ .../java/com/core/data/ConfigDataType.java | 40 + .../main/java/com/core/data/CoreEvent.java | 19 + .../java/com/core/data/CoreInterface.java | 19 + .../src/main/java/com/core/data/CoreLink.java | 41 + .../java/com/core/data/CoreLinkOptions.java | 21 + .../src/main/java/com/core/data/CoreNode.java | 65 ++ .../main/java/com/core/data/CoreService.java | 23 + .../main/java/com/core/data/EventType.java | 45 + corefx/src/main/java/com/core/data/Hook.java | 13 + .../src/main/java/com/core/data/NodeType.java | 83 ++ .../src/main/java/com/core/data/Position.java | 12 + .../main/java/com/core/data/SessionState.java | 35 + .../java/com/core/graph/CoreAddresses.java | 41 + .../graph/CoreAnnotatingGraphMousePlugin.java | 57 ++ .../graph/CoreEditingModalGraphMouse.java | 17 + .../com/core/graph/CoreObservableGraph.java | 33 + .../core/graph/CorePopupGraphMousePlugin.java | 118 +++ .../java/com/core/graph/NetworkGraph.java | 334 +++++++ .../com/core/graph/UndirectedSimpleGraph.java | 24 + .../main/java/com/core/rest/ConfigGroup.java | 12 + .../main/java/com/core/rest/ConfigOption.java | 15 + .../src/main/java/com/core/rest/CoreApi.java | 177 ++++ .../java/com/core/rest/CreatedSession.java | 10 + .../main/java/com/core/rest/GetConfig.java | 11 + .../java/com/core/rest/GetEmaneModels.java | 11 + .../src/main/java/com/core/rest/GetHooks.java | 12 + .../main/java/com/core/rest/GetServices.java | 13 + .../main/java/com/core/rest/GetSession.java | 17 + .../main/java/com/core/rest/GetSessions.java | 13 + .../java/com/core/rest/GetSessionsData.java | 12 + .../main/java/com/core/rest/ServiceFile.java | 13 + .../main/java/com/core/rest/SetConfig.java | 15 + .../java/com/core/rest/SetEmaneConfig.java | 12 + .../com/core/rest/SetEmaneModelConfig.java | 13 + .../main/java/com/core/rest/WlanConfig.java | 12 + .../java/com/core/ui/AnnotationToolbar.java | 107 +++ .../main/java/com/core/ui/ConfigDialog.java | 91 ++ .../src/main/java/com/core/ui/ConfigItem.java | 88 ++ .../java/com/core/ui/CoreFoenixDialog.java | 58 ++ .../main/java/com/core/ui/GraphToolbar.java | 299 ++++++ .../src/main/java/com/core/ui/HookDialog.java | 87 ++ .../main/java/com/core/ui/HooksDialog.java | 123 +++ .../main/java/com/core/ui/LinkDetails.java | 113 +++ .../main/java/com/core/ui/NodeDetails.java | 124 +++ .../java/com/core/ui/NodeEmaneDialog.java | 95 ++ .../java/com/core/ui/NodeServicesDialog.java | 128 +++ .../main/java/com/core/ui/NodeWlanDialog.java | 75 ++ .../main/java/com/core/ui/ServiceDialog.java | 139 +++ .../main/java/com/core/ui/ServiceItem.java | 25 + .../main/java/com/core/ui/SessionsDialog.java | 80 ++ .../com/core/ui/SessionsFoenixDialog.java | 76 ++ .../main/java/com/core/ui/StageDialog.java | 128 +++ corefx/src/main/java/com/core/ui/Toast.java | 44 + .../main/java/com/core/utils/IconUtils.java | 40 + .../main/java/com/core/utils/JsonUtils.java | 49 + .../main/java/com/core/utils/WebUtils.java | 134 +++ .../com/core/websocket/CoreWebSocket.java | 55 ++ corefx/src/main/resources/config.properties | 1 + corefx/src/main/resources/core-icon.png | Bin 0 -> 2931 bytes corefx/src/main/resources/css/main.css | 114 +++ .../resources/fxml/annotation_toolbar.fxml | 26 + .../main/resources/fxml/config_dialog.fxml | 22 + .../main/resources/fxml/graph_toolbar.fxml | 19 + .../src/main/resources/fxml/hook_dialog.fxml | 35 + .../src/main/resources/fxml/hooks_dialog.fxml | 28 + .../src/main/resources/fxml/link_details.fxml | 34 + corefx/src/main/resources/fxml/main.fxml | 49 + .../src/main/resources/fxml/node_details.fxml | 33 + .../resources/fxml/node_emane_dialog.fxml | 28 + .../resources/fxml/node_services_dialog.fxml | 38 + .../main/resources/fxml/service_dialog.fxml | 68 ++ .../main/resources/fxml/sessions_dialog.fxml | 20 + .../src/main/resources/fxml/wlan_dialog.fxml | 37 + corefx/src/main/resources/icons/emane-100.png | Bin 0 -> 1624 bytes corefx/src/main/resources/icons/host-100.png | Bin 0 -> 942 bytes corefx/src/main/resources/icons/hub-100.png | Bin 0 -> 2386 bytes .../main/resources/icons/icomoon_material.svg | 855 ++++++++++++++++++ corefx/src/main/resources/icons/pc-100.png | Bin 0 -> 741 bytes .../src/main/resources/icons/router-100.png | Bin 0 -> 2103 bytes .../src/main/resources/icons/switch-100.png | Bin 0 -> 1460 bytes corefx/src/main/resources/icons/wlan-100.png | Bin 0 -> 1509 bytes corefx/src/main/resources/log4j2.xml | 13 + 87 files changed, 5638 insertions(+) create mode 100644 corefx/pom.xml create mode 100644 corefx/src/main/java/com/core/Controller.java create mode 100644 corefx/src/main/java/com/core/CoreClient.java create mode 100644 corefx/src/main/java/com/core/Main.java create mode 100644 corefx/src/main/java/com/core/data/ConfigDataType.java create mode 100644 corefx/src/main/java/com/core/data/CoreEvent.java create mode 100644 corefx/src/main/java/com/core/data/CoreInterface.java create mode 100644 corefx/src/main/java/com/core/data/CoreLink.java create mode 100644 corefx/src/main/java/com/core/data/CoreLinkOptions.java create mode 100644 corefx/src/main/java/com/core/data/CoreNode.java create mode 100644 corefx/src/main/java/com/core/data/CoreService.java create mode 100644 corefx/src/main/java/com/core/data/EventType.java create mode 100644 corefx/src/main/java/com/core/data/Hook.java create mode 100644 corefx/src/main/java/com/core/data/NodeType.java create mode 100644 corefx/src/main/java/com/core/data/Position.java create mode 100644 corefx/src/main/java/com/core/data/SessionState.java create mode 100644 corefx/src/main/java/com/core/graph/CoreAddresses.java create mode 100644 corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java create mode 100644 corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java create mode 100644 corefx/src/main/java/com/core/graph/CoreObservableGraph.java create mode 100644 corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java create mode 100644 corefx/src/main/java/com/core/graph/NetworkGraph.java create mode 100644 corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java create mode 100644 corefx/src/main/java/com/core/rest/ConfigGroup.java create mode 100644 corefx/src/main/java/com/core/rest/ConfigOption.java create mode 100644 corefx/src/main/java/com/core/rest/CoreApi.java create mode 100644 corefx/src/main/java/com/core/rest/CreatedSession.java create mode 100644 corefx/src/main/java/com/core/rest/GetConfig.java create mode 100644 corefx/src/main/java/com/core/rest/GetEmaneModels.java create mode 100644 corefx/src/main/java/com/core/rest/GetHooks.java create mode 100644 corefx/src/main/java/com/core/rest/GetServices.java create mode 100644 corefx/src/main/java/com/core/rest/GetSession.java create mode 100644 corefx/src/main/java/com/core/rest/GetSessions.java create mode 100644 corefx/src/main/java/com/core/rest/GetSessionsData.java create mode 100644 corefx/src/main/java/com/core/rest/ServiceFile.java create mode 100644 corefx/src/main/java/com/core/rest/SetConfig.java create mode 100644 corefx/src/main/java/com/core/rest/SetEmaneConfig.java create mode 100644 corefx/src/main/java/com/core/rest/SetEmaneModelConfig.java create mode 100644 corefx/src/main/java/com/core/rest/WlanConfig.java create mode 100644 corefx/src/main/java/com/core/ui/AnnotationToolbar.java create mode 100644 corefx/src/main/java/com/core/ui/ConfigDialog.java create mode 100644 corefx/src/main/java/com/core/ui/ConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/CoreFoenixDialog.java create mode 100644 corefx/src/main/java/com/core/ui/GraphToolbar.java create mode 100644 corefx/src/main/java/com/core/ui/HookDialog.java create mode 100644 corefx/src/main/java/com/core/ui/HooksDialog.java create mode 100644 corefx/src/main/java/com/core/ui/LinkDetails.java create mode 100644 corefx/src/main/java/com/core/ui/NodeDetails.java create mode 100644 corefx/src/main/java/com/core/ui/NodeEmaneDialog.java create mode 100644 corefx/src/main/java/com/core/ui/NodeServicesDialog.java create mode 100644 corefx/src/main/java/com/core/ui/NodeWlanDialog.java create mode 100644 corefx/src/main/java/com/core/ui/ServiceDialog.java create mode 100644 corefx/src/main/java/com/core/ui/ServiceItem.java create mode 100644 corefx/src/main/java/com/core/ui/SessionsDialog.java create mode 100644 corefx/src/main/java/com/core/ui/SessionsFoenixDialog.java create mode 100644 corefx/src/main/java/com/core/ui/StageDialog.java create mode 100644 corefx/src/main/java/com/core/ui/Toast.java create mode 100644 corefx/src/main/java/com/core/utils/IconUtils.java create mode 100644 corefx/src/main/java/com/core/utils/JsonUtils.java create mode 100644 corefx/src/main/java/com/core/utils/WebUtils.java create mode 100644 corefx/src/main/java/com/core/websocket/CoreWebSocket.java create mode 100644 corefx/src/main/resources/config.properties create mode 100644 corefx/src/main/resources/core-icon.png create mode 100644 corefx/src/main/resources/css/main.css create mode 100644 corefx/src/main/resources/fxml/annotation_toolbar.fxml create mode 100644 corefx/src/main/resources/fxml/config_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/graph_toolbar.fxml create mode 100644 corefx/src/main/resources/fxml/hook_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/hooks_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/link_details.fxml create mode 100644 corefx/src/main/resources/fxml/main.fxml create mode 100644 corefx/src/main/resources/fxml/node_details.fxml create mode 100644 corefx/src/main/resources/fxml/node_emane_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/node_services_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/service_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/sessions_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/wlan_dialog.fxml create mode 100644 corefx/src/main/resources/icons/emane-100.png create mode 100644 corefx/src/main/resources/icons/host-100.png create mode 100644 corefx/src/main/resources/icons/hub-100.png create mode 100644 corefx/src/main/resources/icons/icomoon_material.svg create mode 100644 corefx/src/main/resources/icons/pc-100.png create mode 100644 corefx/src/main/resources/icons/router-100.png create mode 100644 corefx/src/main/resources/icons/switch-100.png create mode 100644 corefx/src/main/resources/icons/wlan-100.png create mode 100644 corefx/src/main/resources/log4j2.xml diff --git a/.gitignore b/.gitignore index 9fb9d6ae..199757e3 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ coverage.xml # ignore swap files *.swp + +# java files +target diff --git a/corefx/pom.xml b/corefx/pom.xml new file mode 100644 index 00000000..4b6653a1 --- /dev/null +++ b/corefx/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.core + corefx + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + 2.1.1 + 2.9.6 + + + + + net.sf.jung + jung-api + ${jung.version} + + + net.sf.jung + jung-graph-impl + ${jung.version} + + + net.sf.jung + jung-algorithms + ${jung.version} + + + net.sf.jung + jung-io + ${jung.version} + + + net.sf.jung + jung-visualization + ${jung.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.squareup.okhttp3 + okhttp + 3.11.0 + + + org.apache.logging.log4j + log4j-api + 2.9.0 + + + org.apache.logging.log4j + log4j-core + 2.9.0 + + + io.socket + socket.io-client + 0.8.3 + + + org.projectlombok + lombok + 1.18.0 + provided + + + commons-net + commons-net + 3.6 + + + com.jfoenix + jfoenix + 8.0.7 + + + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + com.core.Main + + + + + \ No newline at end of file diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java new file mode 100644 index 00000000..2e114fbc --- /dev/null +++ b/corefx/src/main/java/com/core/Controller.java @@ -0,0 +1,250 @@ +package com.core; + +import com.core.data.CoreLink; +import com.core.data.CoreNode; +import com.core.graph.NetworkGraph; +import com.core.rest.ConfigOption; +import com.core.rest.CoreApi; +import com.core.rest.GetConfig; +import com.core.rest.SetConfig; +import com.core.ui.*; +import com.core.websocket.CoreWebSocket; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.embed.swing.SwingNode; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.ItemEvent; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Data +public class Controller implements Initializable { + private static final Logger logger = LogManager.getLogger(); + + @FXML + private StackPane stackPane; + + @FXML + private BorderPane borderPane; + + @FXML + private SwingNode swingNode; + + private Application application; + private Stage window; + + // core client utilities + private CoreWebSocket coreWebSocket; + private CoreApi coreApi; + private CoreClient coreClient; + + // ui elements + private NetworkGraph networkGraph = new NetworkGraph(this); + private AnnotationToolbar annotationToolbar = new AnnotationToolbar(networkGraph); + private NodeDetails nodeDetails = new NodeDetails(); + private LinkDetails linkDetails = new LinkDetails(networkGraph); + 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); + + public Controller() { + // load configuration + Properties properties = getConfiguration(); + String coreUrl = properties.getProperty("core-rest"); + logger.info("core rest: {}", coreUrl); + + // start web socket thread + try { + coreWebSocket = new CoreWebSocket(this, coreUrl); + coreWebSocket.start(); + } catch (URISyntaxException ex) { + logger.error("error starting web socket", ex); + } + + coreApi = new CoreApi(coreUrl); + coreClient = new CoreClient(this); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + try { + coreClient.initialJoin(); + } catch (IOException ex) { + logger.error("failure during initial join", ex); + Toast.error(String.format("Initial join failure: %s", ex.getMessage())); + } + }); + } + + public 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); + } + + @FXML + private void onHelpMenuWebsite(ActionEvent event) { + application.getHostServices().showDocument("https://github.com/coreemu/core"); + } + + @FXML + private void onHelpMenuDocumentation(ActionEvent event) { + application.getHostServices().showDocument("http://coreemu.github.io/core/"); + } + + @FXML + private void onHelpMenuMailingList(ActionEvent event) { + application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users"); + } + + @FXML + private void onOpenXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Session"); + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + File file = fileChooser.showOpenDialog(window); + if (file != null) { + logger.info("opening session xml: {}", file.getPath()); + try { + coreClient.openSession(file); + } catch (IOException ex) { + logger.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(System.getProperty("user.home"))); + 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) { + logger.error("error saving session xml", ex); + } + } + } + + @FXML + private void onJoinSessionMenu(ActionEvent event) { + logger.info("join sessions menu clicked!"); + try { + sessionsDialog.showDialog(); + } catch (IOException ex) { + logger.error("error getting session dialog", ex); + } + } + + @FXML + private void onSessionHooksMenu(ActionEvent event) { + hooksDialog.showDialog(); + } + + @FXML + private void onSessionOptionsMenu(ActionEvent event) { + try { + GetConfig config = coreClient.getSessionConfig(); + configDialog.showDialog("Session Options", config, () -> { + List options = configDialog.getOptions(); + SetConfig setConfig = new SetConfig(options); + try { + boolean result = coreClient.setSessionConfig(setConfig); + 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"); + } + } + + private Properties getConfiguration() { + try { + Properties properties = new Properties(); + properties.load(getClass().getResourceAsStream("/config.properties")); + return properties; + } catch (IOException ex) { + logger.error("error reading config file"); + throw new RuntimeException("configuration file did not exist"); + } + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + logger.info("controller initialize"); + swingNode.setContent(networkGraph.getGraphViewer()); + + // set graph toolbar + borderPane.setLeft(graphToolbar); + + // setup snackbar + Toast.setSnackbarRoot(stackPane); + + // 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)); + } + }); + } +} diff --git a/corefx/src/main/java/com/core/CoreClient.java b/corefx/src/main/java/com/core/CoreClient.java new file mode 100644 index 00000000..b19f91f8 --- /dev/null +++ b/corefx/src/main/java/com/core/CoreClient.java @@ -0,0 +1,223 @@ +package com.core; + +import com.core.data.*; +import com.core.graph.NetworkGraph; +import com.core.rest.*; +import com.core.ui.Toast; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Data +public class CoreClient { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private final NetworkGraph networkGraph; + private final CoreApi coreApi; + private Integer sessionId; + private SessionState sessionState; + private GetServices services; + private List emaneModels = new ArrayList<>(); + + public CoreClient(Controller controller) { + this.controller = controller; + this.networkGraph = controller.getNetworkGraph(); + this.coreApi = controller.getCoreApi(); + } + + public void joinSession(Integer joinId, boolean notification) throws IOException { + networkGraph.reset(); + GetSession session = coreApi.getSession(joinId); + sessionId = joinId; + sessionState = SessionState.get(session.getState()); + + logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session); + for (CoreNode node : session.getNodes()) { + if (node.getModel() == null) { + continue; + } + + NodeType nodeType = NodeType.getNodeType(node.getNodeTypeKey()); + node.setIcon(nodeType.getIcon()); + networkGraph.addNode(node); + } + + for (CoreLink link : session.getLinks()) { + networkGraph.addLink(link); + } + + networkGraph.getGraphViewer().repaint(); + + if (notification) { + Toast.info(String.format("Joined Session %s", sessionId.toString())); + } + + updateController(); + } + + public void createSession() throws IOException { + CreatedSession session = coreApi.createSession(); + logger.info("created session: {}", session); + sessionId = session.getId(); + sessionState = SessionState.get(session.getState()); + Toast.info(String.format("Created Session %s", sessionId.toString())); + joinSession(sessionId, false); + } + + public void initialJoin() throws IOException { + services = coreApi.getServices(); + controller.getNodeServicesDialog().setServices(services); + + logger.info("core services: {}", services); + + logger.info("initial core session join"); + GetSessions response = coreApi.getSessions(); + logger.info("existing sessions: {}", response); + if (response.getSessions().isEmpty()) { + logger.info("creating initial session"); + createSession(); + updateController(); + } else { + GetSessionsData getSessionsData = response.getSessions().get(0); + Integer joinId = getSessionsData.getId(); + joinSession(joinId, true); + } + + // set emane models + emaneModels = coreApi.getEmaneModels(sessionId).getModels(); + controller.getNodeEmaneDialog().setModels(emaneModels); + } + + public boolean start() throws IOException { + networkGraph.updatePositions(); + + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : controller.getHooksDialog().getHooks()) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : networkGraph.getGraph().getVertices()) { + if (!coreApi.createNode(sessionId, node)) { + return false; + } + } + + for (CoreLink link : networkGraph.getGraph().getEdges()) { + if (!coreApi.createLink(sessionId, link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + public boolean setState(SessionState state) throws IOException { + boolean result = coreApi.setSessionState(sessionId, state); + if (result) { + sessionState = state; + } + return result; + } + + public CoreService getService(CoreNode node, String serviceName) throws IOException { + return coreApi.getService(sessionId, node, serviceName); + } + + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + return coreApi.setService(sessionId, node, serviceName, service); + } + + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + return coreApi.getServiceFile(sessionId, node, serviceName, fileName); + } + + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + return coreApi.setServiceFile(sessionId, node, serviceName, serviceFile); + } + + public GetConfig getEmaneModelConfig(CoreNode node, String model) throws IOException { + return coreApi.getEmaneModelConfig(sessionId, node, model); + } + + public GetConfig getEmaneConfig(CoreNode node) throws IOException { + return coreApi.getEmaneConfig(sessionId, node); + } + + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + return coreApi.setEmaneConfig(sessionId, node, options); + } + + public boolean setEmaneModelConfig(CoreNode node, String model, List options) throws IOException { + return coreApi.setEmaneModelConfig(sessionId, node, model, options); + } + + private void updateController() { + controller.getGraphToolbar().setRunButton(isRunning()); + controller.getHooksDialog().updateHooks(); + } + + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + public void saveSession(File file) throws IOException { + coreApi.saveSession(sessionId, file); + } + + public void openSession(File file) throws IOException { + CreatedSession createdSession = coreApi.openSession(file); + joinSession(createdSession.getId(), true); + } + + public GetConfig getSessionConfig() throws IOException { + return coreApi.getSessionConfig(sessionId); + } + + public boolean setSessionConfig(SetConfig config) throws IOException { + return coreApi.setSessionConfig(sessionId, config); + } + + public boolean createNode(CoreNode node) throws IOException { + return coreApi.createNode(sessionId, node); + } + + public boolean deleteNode(CoreNode node) throws IOException { + return coreApi.deleteNode(sessionId, node); + } + + public boolean createHook(Hook hook) throws IOException { + return coreApi.createHook(sessionId, hook); + } + + public GetHooks getHooks() throws IOException { + return coreApi.getHooks(sessionId); + } + + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + return coreApi.getWlanConfig(sessionId, node); + } + + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + return coreApi.setWlanConfig(sessionId, node, config); + } + + public String getTerminalCommand(CoreNode node) throws IOException { + return coreApi.getTerminalCommand(sessionId, node); + } +} diff --git a/corefx/src/main/java/com/core/Main.java b/corefx/src/main/java/com/core/Main.java new file mode 100644 index 00000000..4a999804 --- /dev/null +++ b/corefx/src/main/java/com/core/Main.java @@ -0,0 +1,66 @@ +package com.core; + +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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class Main extends Application { + private static final Logger logger = LogManager.getLogger(); + + @Override + public void start(Stage window) throws Exception { + // load svg icons + SVGGlyphLoader.loadGlyphsFont(getClass().getResourceAsStream("/icons/icomoon_material.svg"), + "icomoon.svg"); + logger.info("icons: {}", SVGGlyphLoader.getAllGlyphsIDs()); + + // 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); + + // configure window + window.setOnCloseRequest(event -> { + logger.info("exiting gui"); + Platform.exit(); + System.exit(0); + }); + window.setOnShown(windowEvent -> logger.info("stage show event")); + window.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/corefx/src/main/java/com/core/data/ConfigDataType.java b/corefx/src/main/java/com/core/data/ConfigDataType.java new file mode 100644 index 00000000..3dd44864 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigDataType.java @@ -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 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); + } +} diff --git a/corefx/src/main/java/com/core/data/CoreEvent.java b/corefx/src/main/java/com/core/data/CoreEvent.java new file mode 100644 index 00000000..9ac8ed03 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreEvent.java @@ -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); + } +} diff --git a/corefx/src/main/java/com/core/data/CoreInterface.java b/corefx/src/main/java/com/core/data/CoreInterface.java new file mode 100644 index 00000000..f159f10e --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreInterface.java @@ -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; +} diff --git a/corefx/src/main/java/com/core/data/CoreLink.java b/corefx/src/main/java/com/core/data/CoreLink.java new file mode 100644 index 00000000..82c901c6 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLink.java @@ -0,0 +1,41 @@ +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; + private Float weight = 1.0f; + + @JsonIgnore + private boolean loaded = true; + + 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; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreLinkOptions.java b/corefx/src/main/java/com/core/data/CoreLinkOptions.java new file mode 100644 index 00000000..6556d3c0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLinkOptions.java @@ -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; +} diff --git a/corefx/src/main/java/com/core/data/CoreNode.java b/corefx/src/main/java/com/core/data/CoreNode.java new file mode 100644 index 00000000..353cb22a --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreNode.java @@ -0,0 +1,65 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; + +@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 services = new HashSet<>(); + private String emane; + private String url; + @JsonIgnore + private String icon; + @JsonIgnore + private Map interfaces = new HashMap<>(); + @JsonIgnore + private boolean loaded = true; + + public CoreNode(Integer id) { + this.id = id; + this.name = String.format("Node%s", this.id); + this.loaded = false; + } + + public CoreInterface getInterface(Integer id) { + return interfaces.get(id); + } + + @JsonIgnore + public String getNodeTypeKey() { + if (model == null) { + return type.toString(); + } else { + return String.format("%s-%s", type, model); + } + } + + public void addInterface(CoreInterface coreInterface) { + logger.info("adding interface node({}) {}", id, coreInterface); + interfaces.put(coreInterface.getId(), coreInterface); + } + + @JsonIgnore + public int getNextInterfaceId() { + if (interfaces.isEmpty()) { + return 0; + } else { + return Collections.max(interfaces.keySet()) + 1; + } + } +} diff --git a/corefx/src/main/java/com/core/data/CoreService.java b/corefx/src/main/java/com/core/data/CoreService.java new file mode 100644 index 00000000..ed9dd230 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreService.java @@ -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 executables = new ArrayList<>(); + private List dependencies = new ArrayList<>(); + private List dirs = new ArrayList<>(); + private List configs = new ArrayList<>(); + private List startup = new ArrayList<>(); + private List validate = new ArrayList<>(); + @JsonProperty("validation_mode") + private String validationMode; + @JsonProperty("validation_timer") + private String validationTimer; + private List shutdown = new ArrayList<>(); + private String meta; +} diff --git a/corefx/src/main/java/com/core/data/EventType.java b/corefx/src/main/java/com/core/data/EventType.java new file mode 100644 index 00000000..487de4c1 --- /dev/null +++ b/corefx/src/main/java/com/core/data/EventType.java @@ -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 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); + } +} diff --git a/corefx/src/main/java/com/core/data/Hook.java b/corefx/src/main/java/com/core/data/Hook.java new file mode 100644 index 00000000..35b1a934 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Hook.java @@ -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; +} diff --git a/corefx/src/main/java/com/core/data/NodeType.java b/corefx/src/main/java/com/core/data/NodeType.java new file mode 100644 index 00000000..165f5741 --- /dev/null +++ b/corefx/src/main/java/com/core/data/NodeType.java @@ -0,0 +1,83 @@ +package com.core.data; + +import lombok.Data; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Data +public class NodeType { + private static final Map LOOKUP = new HashMap<>(); + private static final Map DISPLAY_MAP = 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; + private final int value; + private final String display; + private final String model; + private final 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 { + addNodeType(new NodeType(DEFAULT, "host", "Host", "/icons/host-100.png")); + addNodeType(new NodeType(DEFAULT, "PC", "PC", "/icons/pc-100.png")); + addNodeType(new NodeType(DEFAULT, "mdr", "MDR", "/icons/router-100.png")); + addNodeType(new NodeType(SWITCH, "Switch", "/icons/switch-100.png")); + addNodeType(new NodeType(HUB, "Hub", "/icons/hub-100.png")); + addNodeType(new NodeType(WLAN, "WLAN", "/icons/wlan-100.png")); + addNodeType(new NodeType(EMANE, "EMANE", "/icons/emane-100.png")); + + DISPLAY_MAP.put(HUB, "Hub"); + DISPLAY_MAP.put(SWITCH, "Switch"); + DISPLAY_MAP.put(WLAN, "WLAN"); + DISPLAY_MAP.put(EMANE, "EMANE"); + } + + public NodeType(int value, String display, String icon) { + this(value, null, display, icon); + } + + + public NodeType(int value, String model, String display, String icon) { + this.value = value; + this.model = model; + this.display = display; + this.icon = icon; + } + + public String getKey() { + if (model == null) { + return Integer.toString(value); + } else { + return String.format("%s-%s", value, model); + } + } + + public static NodeType getNodeType(String key) { + return LOOKUP.get(key); + } + + public static String getDisplay(Integer value) { + return DISPLAY_MAP.get(value); + } + + public static void addNodeType(NodeType nodeType) { + LOOKUP.put(nodeType.getKey(), nodeType); + } + + public static Collection getNodeTypes() { + return LOOKUP.values(); + } +} diff --git a/corefx/src/main/java/com/core/data/Position.java b/corefx/src/main/java/com/core/data/Position.java new file mode 100644 index 00000000..c5bfa728 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Position.java @@ -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; +} diff --git a/corefx/src/main/java/com/core/data/SessionState.java b/corefx/src/main/java/com/core/data/SessionState.java new file mode 100644 index 00000000..71bca0e5 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionState.java @@ -0,0 +1,35 @@ +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); + + private static final Map 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); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java new file mode 100644 index 00000000..5db724b6 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -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 nodeOneInterfaces, Collection 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 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); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java new file mode 100644 index 00000000..6cbd1bf8 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java @@ -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 extends AnnotatingGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private JFrame frame = new JFrame(); + + public CoreAnnotatingGraphMousePlugin(Controller controller, RenderContext renderContext) { + super(renderContext); + this.controller = controller; + frame.setVisible(false); + frame.setAlwaysOnTop(true); + } + + @Override + public void mouseReleased(MouseEvent e) { + VisualizationViewer 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 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 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(); + } + + +} diff --git a/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java new file mode 100644 index 00000000..9d80183e --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java @@ -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 extends EditingModalGraphMouse { + public CoreEditingModalGraphMouse(Controller controller, NetworkGraph networkGraph, + RenderContext rc, Supplier vertexFactory, Supplier edgeFactory) { + super(rc, vertexFactory, edgeFactory); + remove(annotatingPlugin); + remove(popupEditingPlugin); + annotatingPlugin = new CoreAnnotatingGraphMousePlugin<>(controller, rc); + popupEditingPlugin = new CorePopupGraphMousePlugin<>(controller, networkGraph, vertexFactory, edgeFactory); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreObservableGraph.java b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java new file mode 100644 index 00000000..3b086346 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java @@ -0,0 +1,33 @@ +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 extends ObservableGraph { + private static final Logger logger = LogManager.getLogger(); + + public CoreObservableGraph(Graph graph) { + super(graph); + } + + @Override + public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { + logger.info("graph adding edge: {} - {}", v1, v2); + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2, edgeType); + } + + @Override + public boolean addEdge(E e, V v1, V v2) { + logger.info("graph adding edge: {} - {}", v1, v2); + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2); + } +} diff --git a/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java new file mode 100644 index 00000000..028c1417 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java @@ -0,0 +1,118 @@ +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.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; + +public class CorePopupGraphMousePlugin extends EditingPopupGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private final NetworkGraph networkGraph; + private final Layout graphLayout; + private final GraphElementAccessor pickSupport; + + public CorePopupGraphMousePlugin(Controller controller, NetworkGraph networkGraph, + Supplier vertexFactory, Supplier 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()); + + ContextMenu contextMenu = new ContextMenu(); + + // edge picked + if (node != null) { + List menuItems = handleNodeContext(node); + contextMenu.getItems().addAll(menuItems); + } else if (link != null) { + List menuItems = handleLinkContext(link); + contextMenu.getItems().addAll(menuItems); + } + + if (!contextMenu.getItems().isEmpty()) { + logger.info("showing context menu"); + Platform.runLater(() -> contextMenu.show(controller.getWindow(), + e.getXOnScreen(), e.getYOnScreen())); + } + } + + private MenuItem createMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + return menuItem; + } + + private List handleNodeContext(final CoreNode node) { + boolean isRunning = controller.getCoreClient().isRunning(); + + List menuItems = new ArrayList<>(); + + switch (node.getType()) { + case NodeType.DEFAULT: + menuItems.add(createMenuItem("Services", + event -> controller.getNodeServicesDialog().showDialog(node))); + break; + case NodeType.WLAN: + menuItems.add(createMenuItem("WLAN Settings", + event -> controller.getNodeWlanDialog().showDialog(node))); + menuItems.add(createMenuItem("Link MDRs", + event -> networkGraph.linkMdrs(node))); + break; + case NodeType.EMANE: + menuItems.add(createMenuItem("EMANE Settings", + event -> controller.getNodeEmaneDialog().showDialog(node))); + menuItems.add(createMenuItem("Link MDRs", + event -> networkGraph.linkMdrs(node))); + break; + default: + break; + } + + if (!isRunning) { + menuItems.add(createMenuItem("Delete Node", + event -> networkGraph.removeNode(node))); + } + + return menuItems; + } + + private List handleLinkContext(final CoreLink link) { + boolean isRunning = controller.getCoreClient().isRunning(); + + List menuItems = new ArrayList<>(); + + if (!isRunning) { + menuItems.add(createMenuItem("Delete Link", + event -> networkGraph.removeLink(link))); + } + + return menuItems; + } +} diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java new file mode 100644 index 00000000..78a8baa1 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -0,0 +1,334 @@ +package com.core.graph; + +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.ui.Toast; +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.renderers.Renderer; +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.awt.geom.Rectangle2D; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + + +@Data +public class NetworkGraph { + private static final Logger logger = LogManager.getLogger(); + private Controller controller; + private ObservableGraph graph; + private StaticLayout graphLayout; + private VisualizationViewer graphViewer; + private EditingModalGraphMouse graphMouse; + private AnnotationControls annotationControls; + + private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24"); + private CoreAddresses coreAddresses = new CoreAddresses("10.0"); + private NodeType nodeType = NodeType.getNodeType("0-host"); + private Map nodeMap = new ConcurrentHashMap<>(); + private int vertexId = 1; + private int linkId = 1; + private Supplier vertexFactory = () -> new CoreNode(vertexId++); + private Supplier linkFactory = () -> new CoreLink(linkId++); + private Set nodeTypes = new HashSet<>(); + private CorePopupGraphMousePlugin customPopupPlugin; + private CoreAnnotatingGraphMousePlugin customAnnotatingPlugin; + + 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); + + // establish render properties for vertexes/edges + RenderContext renderContext = graphViewer.getRenderContext(); + renderContext.setVertexLabelTransformer(CoreNode::getName); + renderContext.setVertexShapeTransformer(node -> { + double offset = -(IconUtils.ICON_SIZE / 2); + return new Ellipse2D.Double(offset, offset, IconUtils.ICON_SIZE, IconUtils.ICON_SIZE); + }); + renderContext.setEdgeStrokeTransformer(edge -> new BasicStroke(1)); + renderContext.setEdgeShapeTransformer(edge -> new Rectangle2D.Float(0, 0, 1, 10)); + renderContext.setEdgeFillPaintTransformer(edge -> Color.BLACK); + + graphViewer.setVertexToolTipTransformer(renderContext.getVertexLabelTransformer()); + graphMouse = new CoreEditingModalGraphMouse<>(controller, this, renderContext, + vertexFactory, linkFactory); + graphViewer.setGraphMouse(graphMouse); + + // icons + renderContext.setVertexIconTransformer(vertex -> IconUtils.getIcon(vertex.getIcon())); + + // mouse events + graphViewer.addGraphMouseListener(new GraphMouseListener() { + @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()) { + try { + String terminalCommand = controller.getCoreClient().getTerminalCommand(node); + terminalCommand = String.format("gnome-terminal -x %s", 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"); + } + } catch (IOException ex) { + logger.error("error launching terminal", ex); + Toast.error("Node terminal failed to start"); + } + } + } + + @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)) { + logger.debug("moved node({}): {}", node.getName(), mouseEvent.getPoint()); + node.getPosition().setX(mouseEvent.getPoint().getX()); + node.getPosition().setY(mouseEvent.getPoint().getY()); + } + } + }); + } + + 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 graphEventListener = graphEvent -> { + logger.info("graph event: {}", graphEvent.getType()); + switch (graphEvent.getType()) { + case EDGE_ADDED: + handleEdgeAdded((GraphEvent.Edge) graphEvent); + break; + case EDGE_REMOVED: + handleEdgeRemoved((GraphEvent.Edge) graphEvent); + break; + case VERTEX_ADDED: + handleVertexAdded((GraphEvent.Vertex) graphEvent); + break; + case VERTEX_REMOVED: + handleVertexRemoved((GraphEvent.Vertex) graphEvent); + break; + } + }; + + private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + if (!link.isLoaded()) { + Pair endpoints = graph.getEndpoints(link); + + CoreNode nodeOne = endpoints.getFirst(); + CoreNode nodeTwo = endpoints.getSecond(); + + // create interfaces for nodes + int sub = coreAddresses.getSubnet( + nodeOne.getInterfaces().values(), + nodeTwo.getInterfaces().values() + ); + + link.setNodeOne(nodeOne.getId()); + int interfaceOneId = nodeOne.getNextInterfaceId(); + if (isNode(nodeOne)) { + CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nodeTwo.getNextInterfaceId(); + CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); + link.setInterfaceTwo(interfaceTwo); + } + + logger.info("adding user created edge: {}", link); + } + } + + private boolean isNode(CoreNode node) { + return node.getType() == NodeType.DEFAULT; + } + + private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) { + String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId()); + CoreInterface coreInterface = new CoreInterface(); + coreInterface.setId(interfaceId); + coreInterface.setName(String.format("eth%s", interfaceId)); + coreInterface.setIp4(nodeOneIp4); + coreInterface.setIp4Mask(CoreAddresses.IP4_MASK); + node.addInterface(coreInterface); + return coreInterface; + } + + private void handleEdgeRemoved(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + logger.info("removed edge: {}", link); + } + + private void handleVertexAdded(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + if (!node.isLoaded()) { + node.setType(nodeType.getValue()); + node.setIcon(nodeType.getIcon()); + node.setModel(nodeType.getModel()); + logger.info("adding user created node: {}", node); + nodeMap.put(node.getId(), node); + } + } + + private void handleVertexRemoved(GraphEvent.Vertex 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 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(); + } + + public void addLink(CoreLink link) { + link.setId(linkId++); + + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreInterface interfaceOne = link.getInterfaceOne(); + if (interfaceOne != null) { + nodeOne.addInterface(interfaceOne); + } + + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + CoreInterface interfaceTwo = link.getInterfaceTwo(); + if (interfaceTwo != null) { + nodeTwo.addInterface(interfaceTwo); + } + + graph.addEdge(link, nodeOne, nodeTwo); + } + + 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 links = graph.findEdgeSet(node, currentNode); + if (links.isEmpty()) { + CoreLink link = linkFactory.get(); + graph.addEdge(link, currentNode, node); + graphViewer.repaint(); + } + } + } + + private void linkNodes(CoreNode nodeOne, CoreNode nodeTwo, CoreLink link) { + // create interfaces for nodes + int sub = coreAddresses.getSubnet( + nodeOne.getInterfaces().values(), + nodeTwo.getInterfaces().values() + ); + + link.setNodeOne(nodeOne.getId()); + int interfaceOneId = nodeOne.getNextInterfaceId(); + if (isNode(nodeOne)) { + CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nodeTwo.getNextInterfaceId(); + CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); + link.setInterfaceTwo(interfaceTwo); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java new file mode 100644 index 00000000..355b44a0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java @@ -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 extends UndirectedSparseGraph { + @Override + public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { + Pair 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); + } + } +} diff --git a/corefx/src/main/java/com/core/rest/ConfigGroup.java b/corefx/src/main/java/com/core/rest/ConfigGroup.java new file mode 100644 index 00000000..f5696c35 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/ConfigGroup.java @@ -0,0 +1,12 @@ +package com.core.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigGroup { + private String name; + private List options = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/ConfigOption.java b/corefx/src/main/java/com/core/rest/ConfigOption.java new file mode 100644 index 00000000..b2df5095 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/ConfigOption.java @@ -0,0 +1,15 @@ +package com.core.rest; + +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 select = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/CoreApi.java b/corefx/src/main/java/com/core/rest/CoreApi.java new file mode 100644 index 00000000..88a81fb4 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/CoreApi.java @@ -0,0 +1,177 @@ +package com.core.rest; + +import com.core.data.*; +import com.core.utils.JsonUtils; +import com.core.utils.WebUtils; +import lombok.AllArgsConstructor; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AllArgsConstructor +public class CoreApi { + private final String baseUrl; + + private String getUrl(String path) { + return String.format("%s/%s", baseUrl, path); + } + + public GetSessions getSessions() throws IOException { + String url = getUrl("sessions"); + return WebUtils.getJson(url, GetSessions.class); + } + + public GetSession getSession(Integer session) throws IOException { + String path = String.format("sessions/%s", session); + String url = getUrl(path); + return WebUtils.getJson(url, GetSession.class); + } + + public CreatedSession createSession() throws IOException { + String url = getUrl("sessions"); + return WebUtils.post(url, CreatedSession.class); + } + + public void saveSession(Integer id, File file) throws IOException { + String path = String.format("sessions/%s/xml", id); + String url = getUrl(path); + WebUtils.getFile(url, file); + } + + public CreatedSession openSession(File file) throws IOException { + String url = getUrl("sessions/xml"); + return WebUtils.putFile(url, file, CreatedSession.class); + } + + public GetConfig getSessionConfig(Integer session) throws IOException { + String url = getUrl(String.format("sessions/%s/options", session)); + return WebUtils.getJson(url, GetConfig.class); + } + + public boolean setSessionConfig(Integer session, SetConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/options", session)); + return WebUtils.putJson(url, JsonUtils.toString(config)); + } + + public boolean setSessionState(Integer session, SessionState state) throws IOException { + String url = getUrl(String.format("sessions/%s/state", session)); + Map data = new HashMap<>(); + data.put("state", state.getValue()); + return WebUtils.putJson(url, JsonUtils.toString(data)); + } + + public boolean createNode(Integer session, CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes", session)); + String data = JsonUtils.toString(node); + return WebUtils.postJson(url, data); + } + + public boolean deleteNode(Integer session, CoreNode node) throws IOException { + String url = getUrl(String.format("/sessions/%s/nodes/%s", session, node.getId())); + return WebUtils.delete(url); + } + + public boolean createLink(Integer session, CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", session)); + String data = JsonUtils.toString(link); + return WebUtils.postJson(url, data); + } + + public GetServices getServices() throws IOException { + String url = getUrl("services"); + return WebUtils.getJson(url, GetServices.class); + } + + public CoreService getService(Integer session, CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", session, node.getId(), serviceName)); + return WebUtils.getJson(url, CoreService.class); + } + + public String getServiceFile(Integer session, CoreNode node, String serviceName, String fileName) + throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", session, node.getId(), serviceName)); + Map args = new HashMap<>(); + args.put("file", fileName); + return WebUtils.getJson(url, String.class, args); + } + + public boolean setService(Integer session, CoreNode node, String serviceName, CoreService service) + throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", session, node.getId(), serviceName)); + return WebUtils.putJson(url, JsonUtils.toString(service)); + } + + public boolean setServiceFile(Integer session, CoreNode node, String service, ServiceFile serviceFile) + throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", session, node.getId(), service)); + return WebUtils.putJson(url, JsonUtils.toString(serviceFile)); + } + + public GetEmaneModels getEmaneModels(Integer session) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/models", session)); + return WebUtils.getJson(url, GetEmaneModels.class); + } + + public GetConfig getEmaneModelConfig(Integer session, CoreNode node, String model) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", session)); + Map args = new HashMap<>(); + args.put("node", node.getId().toString()); + args.put("name", model); + return WebUtils.getJson(url, GetConfig.class, args); + } + + public GetConfig getEmaneConfig(Integer session, CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", session)); + Map args = new HashMap<>(); + args.put("node", node.getId().toString()); + return WebUtils.getJson(url, GetConfig.class, args); + } + + public boolean setEmaneConfig(Integer session, CoreNode node, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", session)); + SetEmaneConfig setEmaneConfig = new SetEmaneConfig(); + setEmaneConfig.setNode(node.getId()); + setEmaneConfig.setValues(options); + return WebUtils.putJson(url, JsonUtils.toString(setEmaneConfig)); + } + + public boolean setEmaneModelConfig(Integer session, CoreNode node, String model, List options) + throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", session)); + SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig(); + setEmaneModelConfig.setNode(node.getId()); + setEmaneModelConfig.setName(model); + setEmaneModelConfig.setValues(options); + return WebUtils.putJson(url, JsonUtils.toString(setEmaneModelConfig)); + } + + public boolean createHook(Integer session, Hook hook) throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", session)); + String data = JsonUtils.toString(hook); + return WebUtils.postJson(url, data); + } + + public GetHooks getHooks(Integer session) throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", session)); + return WebUtils.getJson(url, GetHooks.class); + } + + public WlanConfig getWlanConfig(Integer session, CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", session, node.getId())); + return WebUtils.getJson(url, WlanConfig.class); + } + + public boolean setWlanConfig(Integer session, CoreNode node, WlanConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", session, node.getId())); + String jsonData = JsonUtils.toString(config); + return WebUtils.putJson(url, jsonData); + } + + public String getTerminalCommand(Integer session, CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", session, node.getId())); + return WebUtils.getJson(url, String.class); + } +} diff --git a/corefx/src/main/java/com/core/rest/CreatedSession.java b/corefx/src/main/java/com/core/rest/CreatedSession.java new file mode 100644 index 00000000..f9959385 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/CreatedSession.java @@ -0,0 +1,10 @@ +package com.core.rest; + +import lombok.Data; + +@Data +public class CreatedSession { + private Integer id; + private Integer state; + private String url; +} diff --git a/corefx/src/main/java/com/core/rest/GetConfig.java b/corefx/src/main/java/com/core/rest/GetConfig.java new file mode 100644 index 00000000..6176c204 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetConfig.java @@ -0,0 +1,11 @@ +package com.core.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetConfig { + private List groups = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/GetEmaneModels.java b/corefx/src/main/java/com/core/rest/GetEmaneModels.java new file mode 100644 index 00000000..713cf1e1 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetEmaneModels.java @@ -0,0 +1,11 @@ +package com.core.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetEmaneModels { + private List models = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/GetHooks.java b/corefx/src/main/java/com/core/rest/GetHooks.java new file mode 100644 index 00000000..831b1320 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetHooks.java @@ -0,0 +1,12 @@ +package com.core.rest; + +import com.core.data.Hook; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetHooks { + private List hooks = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/GetServices.java b/corefx/src/main/java/com/core/rest/GetServices.java new file mode 100644 index 00000000..8c5d19ab --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetServices.java @@ -0,0 +1,13 @@ +package com.core.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +public class GetServices { + private Map> groups; +} diff --git a/corefx/src/main/java/com/core/rest/GetSession.java b/corefx/src/main/java/com/core/rest/GetSession.java new file mode 100644 index 00000000..b824d09a --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetSession.java @@ -0,0 +1,17 @@ +package com.core.rest; + +import com.core.data.CoreNode; +import com.core.data.CoreLink; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class GetSession { + private Integer state; + private List nodes = new ArrayList<>(); + private List links = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/GetSessions.java b/corefx/src/main/java/com/core/rest/GetSessions.java new file mode 100644 index 00000000..e0d82d73 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetSessions.java @@ -0,0 +1,13 @@ +package com.core.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class GetSessions { + private List sessions = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/GetSessionsData.java b/corefx/src/main/java/com/core/rest/GetSessionsData.java new file mode 100644 index 00000000..27b52f25 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/GetSessionsData.java @@ -0,0 +1,12 @@ +package com.core.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class GetSessionsData { + private Integer id; + private Integer state; + private Integer nodes; +} diff --git a/corefx/src/main/java/com/core/rest/ServiceFile.java b/corefx/src/main/java/com/core/rest/ServiceFile.java new file mode 100644 index 00000000..0fffb9bf --- /dev/null +++ b/corefx/src/main/java/com/core/rest/ServiceFile.java @@ -0,0 +1,13 @@ +package com.core.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceFile { + private String name; + private String data; +} diff --git a/corefx/src/main/java/com/core/rest/SetConfig.java b/corefx/src/main/java/com/core/rest/SetConfig.java new file mode 100644 index 00000000..acaec7d4 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/SetConfig.java @@ -0,0 +1,15 @@ +package com.core.rest; + +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 values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/SetEmaneConfig.java b/corefx/src/main/java/com/core/rest/SetEmaneConfig.java new file mode 100644 index 00000000..fd3d5988 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/SetEmaneConfig.java @@ -0,0 +1,12 @@ +package com.core.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneConfig { + private Integer node; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/SetEmaneModelConfig.java b/corefx/src/main/java/com/core/rest/SetEmaneModelConfig.java new file mode 100644 index 00000000..6c562c64 --- /dev/null +++ b/corefx/src/main/java/com/core/rest/SetEmaneModelConfig.java @@ -0,0 +1,13 @@ +package com.core.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneModelConfig { + private Integer node; + private String name; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/rest/WlanConfig.java b/corefx/src/main/java/com/core/rest/WlanConfig.java new file mode 100644 index 00000000..f5eb349d --- /dev/null +++ b/corefx/src/main/java/com/core/rest/WlanConfig.java @@ -0,0 +1,12 @@ +package com.core.rest; + +import lombok.Data; + +@Data +public class WlanConfig { + private String range; + private String bandwidth; + private String jitter; + private String delay; + private String error; +} diff --git a/corefx/src/main/java/com/core/ui/AnnotationToolbar.java b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java new file mode 100644 index 00000000..0e083951 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java @@ -0,0 +1,107 @@ +package com.core.ui; + +import com.core.graph.NetworkGraph; +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.fxml.FXML; +import javafx.fxml.FXMLLoader; +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; +import java.io.IOException; + +public class AnnotationToolbar extends GridPane { + private static final Logger logger = LogManager.getLogger(); + private NetworkGraph graph; + + @FXML + private JFXComboBox shapeCombo; + + @FXML + private JFXColorPicker colorPicker; + + @FXML + private JFXComboBox layerCombo; + + @FXML + private JFXToggleButton fillToggle; + + public AnnotationToolbar(NetworkGraph graph) { + this.graph = graph; + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/annotation_toolbar.fxml")); + loader.setRoot(this); + loader.setController(this); + + try { + loader.load(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + setup(); + } + + public void setup() { + // setup annotation shape combo + shapeCombo.getItems().addAll("Rectangle", "RoundRectangle", "Ellipse"); + shapeCombo.getSelectionModel().select("Rectangle"); + shapeCombo.setOnAction(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 "RoundRectangle": + shape = new RoundRectangle2D.Double(0, 0, 0, 0, 50.0, 50.0); + break; + case "Ellipse": + shape = new Ellipse2D.Double(); + break; + } + graph.getGraphMouse().getAnnotatingPlugin().setRectangularShape(shape); + }); + + // setup annotation layer combo + layerCombo.getItems().addAll("Lower", "Upper"); + layerCombo.getSelectionModel().select("Lower"); + layerCombo.setOnAction(event -> { + String selected = layerCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation layer selected: {}", selected); + Annotation.Layer layer; + if ("Lower".equals(selected)) { + layer = Annotation.Layer.LOWER; + } else { + layer = Annotation.Layer.UPPER; + } + graph.getGraphMouse().getAnnotatingPlugin().setLayer(layer); + }); + + // setup annotation color picker + colorPicker.setOnAction(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); + }); + + // setup annotation toggle fill + fillToggle.setOnAction(event -> { + boolean selected = fillToggle.isSelected(); + graph.getGraphMouse().getAnnotatingPlugin().setFill(selected); + }); + } +} diff --git a/corefx/src/main/java/com/core/ui/ConfigDialog.java b/corefx/src/main/java/com/core/ui/ConfigDialog.java new file mode 100644 index 00000000..cfda1600 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/ConfigDialog.java @@ -0,0 +1,91 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.rest.ConfigGroup; +import com.core.rest.ConfigOption; +import com.core.rest.GetConfig; +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.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +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 CoreNode coreNode; + private List configItems = new ArrayList<>(); + private JFXButton saveButton; + + @FXML + private JFXTabPane tabPane; + + @FXML + private HBox buttonBar; + + public ConfigDialog(Controller controller) { + super(controller, "/fxml/config_dialog.fxml"); + saveButton = createButton("Save"); + addCancelButton(); + } + + public List getOptions() { + return configItems.stream().map(ConfigItem::getOption).collect(Collectors.toList()); + } + + public void showDialog(String title, GetConfig getConfig, Runnable runnable) { + setTitle(title); + + configItems.clear(); + tabPane.getTabs().clear(); + for (ConfigGroup group : getConfig.getGroups()) { + 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; + logger.info("tabs: {}", tabPane.getTabs()); + tabPane.getTabs().add(tab); + + for (ConfigOption option : group.getOptions()) { + ConfigItem configItem = new ConfigItem(option); + gridPane.addRow(index, configItem.getLabel(), configItem.getNode()); + configItems.add(configItem); + index += 1; + } + + JFXScrollPane.smoothScrolling(scrollPane); + } + + saveButton.setOnAction(event -> { + runnable.run(); + close(); + }); + + show(); + } +} diff --git a/corefx/src/main/java/com/core/ui/ConfigItem.java b/corefx/src/main/java/com/core/ui/ConfigItem.java new file mode 100644 index 00000000..35264cf0 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/ConfigItem.java @@ -0,0 +1,88 @@ +package com.core.ui; + +import com.core.data.ConfigDataType; +import com.core.rest.ConfigOption; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.JFXToggleButton; +import javafx.scene.Node; +import javafx.scene.control.Label; +import lombok.Data; + +@Data +public class ConfigItem { + private ConfigOption option; + private Label label; + private Node node; + + public ConfigItem(ConfigOption option) { + this.option = option; + label = new Label(option.getLabel()); + createNode(); + } + + private void createNode() { + ConfigDataType dataType = ConfigDataType.get(option.getType()); + switch (dataType) { + case BOOL: + node = booleanConfig(); + break; + default: + if (!option.getSelect().isEmpty()) { + node = optionsConfig(); + } else { + node = defaultConfigItem(); + } + break; + } + } + + public ConfigOption getOption() { + String value; + ConfigDataType dataType = ConfigDataType.get(option.getType()); + switch (dataType) { + case BOOL: + JFXToggleButton button = (JFXToggleButton) node; + if (button.isSelected()) { + value = "1"; + } else { + value = "0"; + } + break; + default: + if (!option.getSelect().isEmpty()) { + JFXComboBox comboBox = (JFXComboBox) node; + value = comboBox.getSelectionModel().getSelectedItem(); + } else { + JFXTextField textField = (JFXTextField) node; + value = textField.getText(); + } + break; + } + option.setValue(value); + return option; + } + + private JFXTextField defaultConfigItem() { + JFXTextField textField = new JFXTextField(option.getValue()); + textField.setMaxWidth(Double.MAX_VALUE); + return textField; + } + + private JFXToggleButton booleanConfig() { + JFXToggleButton button = new JFXToggleButton(); + button.setMaxWidth(Double.MAX_VALUE); + if ("1".equals(option.getValue())) { + button.setSelected(true); + } + return button; + } + + private JFXComboBox optionsConfig() { + JFXComboBox comboBox = new JFXComboBox<>(); + comboBox.setMaxWidth(Double.MAX_VALUE); + comboBox.getItems().addAll(option.getSelect()); + comboBox.getSelectionModel().select(option.getValue()); + return comboBox; + } +} diff --git a/corefx/src/main/java/com/core/ui/CoreFoenixDialog.java b/corefx/src/main/java/com/core/ui/CoreFoenixDialog.java new file mode 100644 index 00000000..e53fd1b2 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/CoreFoenixDialog.java @@ -0,0 +1,58 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.CoreClient; +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 CoreClient getCoreClient() { + return controller.getCoreClient(); + } + + public JFXButton createButton(String text) { + JFXButton button = new JFXButton(text); + button.getStyleClass().add("core-button"); + return button; + } +} diff --git a/corefx/src/main/java/com/core/ui/GraphToolbar.java b/corefx/src/main/java/com/core/ui/GraphToolbar.java new file mode 100644 index 00000000..26d820aa --- /dev/null +++ b/corefx/src/main/java/com/core/ui/GraphToolbar.java @@ -0,0 +1,299 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.data.NodeType; +import com.core.data.SessionState; +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.fxml.FXMLLoader; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +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; + + @FXML + private JFXButton runButton; + + @FXML + private JFXButton pickingButton; + + @FXML + private JFXButton editingButton; + + @FXML + private JFXButton drawingButton; + + @FXML + private ComboBox graphModeCombo; + + @FXML + private JFXButton nodesButton; + + @FXML + private JFXButton devicesButton; + + private SVGGlyph startIcon; + private SVGGlyph stopIcon; + private JFXListView