updates to support orchestrating new container nodes with corefx gui

This commit is contained in:
Blake J. Harnden 2019-07-11 13:08:03 -07:00
parent 11473a7138
commit e87cdc929c
14 changed files with 170 additions and 141 deletions

View file

@ -140,36 +140,15 @@ public class Controller implements Initializable {
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);
Session session = coreClient.joinSession(sessionId);
// 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);
}
@ -448,6 +427,7 @@ public class Controller implements Initializable {
connectToCore(address, port);
logger.info("controller initialize");
coreClient.initialize(this);
swingNode.setContent(networkGraph.getGraphViewer());
// update graph preferences

View file

@ -23,16 +23,14 @@ public interface ICoreClient {
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 joinSession(Integer sessionId) throws IOException;
Session getSession(Integer sessionId) throws IOException;
boolean start(Collection<CoreNode> nodes, Collection<CoreLink> links, List<Hook> hooks) throws IOException;
@ -117,5 +115,5 @@ public interface ICoreClient {
boolean setLocationConfig(LocationConfig config) throws IOException;
void setupEventHandlers(Controller controller) throws IOException;
void initialize(Controller controller);
}

View file

@ -33,6 +33,7 @@ public class CoreGrpcClient implements ICoreClient {
private final ExecutorService executorService = Executors.newFixedThreadPool(6);
private boolean handlingEvents = false;
private boolean handlingThroughputs = false;
private Controller controller;
private CoreProto.Node nodeToProto(CoreNode node) {
CoreProto.Position position = CoreProto.Position.newBuilder()
@ -58,6 +59,9 @@ public class CoreGrpcClient implements ICoreClient {
if (node.getIcon() != null) {
builder.setIcon(node.getIcon());
}
if (node.getImage() != null) {
builder.setImage(node.getImage());
}
return builder.build();
}
@ -157,14 +161,26 @@ public class CoreGrpcClient implements ICoreClient {
private CoreNode protoToNode(CoreProto.Node protoNode) {
CoreNode node = new CoreNode(protoNode.getId());
node.setType(protoNode.getTypeValue());
node.setName(protoNode.getName());
node.setEmane(protoNode.getEmane());
node.setIcon(protoNode.getIcon());
node.setModel(protoNode.getModel());
if (!protoNode.getEmane().isEmpty()) {
node.setEmane(protoNode.getEmane());
}
if (!protoNode.getImage().isEmpty()) {
node.setImage(protoNode.getImage());
}
node.setServices(new HashSet<>(protoNode.getServicesList()));
node.getPosition().setX((double) protoNode.getPosition().getX());
node.getPosition().setY((double) protoNode.getPosition().getY());
node.setType(protoNode.getTypeValue());
NodeType nodeType = NodeType.find(node.getType(), node.getModel());
if (nodeType == null) {
logger.error("failed to find node type({}) model({}): {}",
node.getType(), node.getModel(), node.getName());
}
node.setNodeType(nodeType);
node.setLoaded(true);
return node;
}
@ -172,7 +188,9 @@ public class CoreGrpcClient implements ICoreClient {
CoreInterface coreInterface = new CoreInterface();
coreInterface.setId(protoInterface.getId());
coreInterface.setName(protoInterface.getName());
coreInterface.setMac(protoInterface.getMac());
if (!protoInterface.getMac().isEmpty()) {
coreInterface.setMac(protoInterface.getMac());
}
String ip4String = String.format("%s/%s", protoInterface.getIp4(), protoInterface.getIp4Mask());
IPAddress ip4 = new IPAddressString(ip4String).getAddress();
coreInterface.setIp4(ip4);
@ -184,6 +202,7 @@ public class CoreGrpcClient implements ICoreClient {
private CoreLink protoToLink(CoreProto.Link linkProto) {
CoreLink link = new CoreLink();
link.setType(linkProto.getTypeValue());
link.setNodeOne(linkProto.getNodeOneId());
link.setNodeTwo(linkProto.getNodeTwoId());
CoreInterface interfaceOne = protoToInterface(linkProto.getInterfaceOne());
@ -211,6 +230,11 @@ public class CoreGrpcClient implements ICoreClient {
return link;
}
@Override
public void initialize(Controller controller) {
this.controller = controller;
}
@Override
public void setConnection(String address, int port) {
this.address = address;
@ -283,16 +307,6 @@ public class CoreGrpcClient implements ICoreClient {
return true;
}
@Override
public void updateSession(Integer sessionId) {
this.sessionId = sessionId;
}
@Override
public void updateState(SessionState state) {
sessionState = state;
}
@Override
public SessionOverview createSession() throws IOException {
CoreProto.CreateSessionRequest request = CoreProto.CreateSessionRequest.newBuilder().build();
@ -345,6 +359,7 @@ public class CoreGrpcClient implements ICoreClient {
try {
CoreProto.GetSessionResponse response = blockingStub.getSession(request);
Session session = new Session();
session.setId(sessionId);
for (CoreProto.Node protoNode : response.getSession().getNodesList()) {
if (CoreProto.NodeType.Enum.PEER_TO_PEER == protoNode.getType()) {
continue;
@ -355,19 +370,43 @@ public class CoreGrpcClient implements ICoreClient {
session.getNodes().add(node);
}
for (CoreProto.Link linkProto : response.getSession().getLinksList()) {
logger.info("adding link: {} - {}", linkProto.getNodeOneId(), linkProto.getNodeTwoId());
logger.info("adding link: {}", linkProto);
CoreLink link = protoToLink(linkProto);
session.getLinks().add(link);
}
session.setState(response.getSession().getStateValue());
SessionState state = SessionState.get(response.getSession().getStateValue());
session.setState(state);
return session;
} catch (StatusRuntimeException ex) {
throw new IOException(ex);
}
}
@Override
public Session joinSession(Integer sessionId) throws IOException {
// stop handling previous session events if currently running
if (isRunning()) {
handlingEvents = false;
}
// join desired session
Session session = getSession(sessionId);
this.sessionId = session.getId();
sessionState = session.getState();
logger.info("joining session({}) state({})", this.sessionId, sessionState);
// setup event handlers if joined session is running
if (isRunning()) {
setupEventHandlers();
}
return session;
}
@Override
public boolean start(Collection<CoreNode> nodes, Collection<CoreLink> links, List<Hook> hooks) throws IOException {
setupEventHandlers();
boolean result = setState(SessionState.DEFINITION);
if (!result) {
return false;
@ -806,6 +845,8 @@ public class CoreGrpcClient implements ICoreClient {
@Override
public boolean deleteNode(CoreNode node) throws IOException {
CoreProto.DeleteNodeRequest request = CoreProto.DeleteNodeRequest.newBuilder()
.setSessionId(sessionId)
.setNodeId(node.getId())
.build();
try {
CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request);
@ -1132,8 +1173,7 @@ public class CoreGrpcClient implements ICoreClient {
}
}
@Override
public void setupEventHandlers(Controller controller) throws IOException {
private void setupEventHandlers() throws IOException {
logger.info("setting up event handlers");
handlingEvents = true;
try {
@ -1151,22 +1191,22 @@ public class CoreGrpcClient implements ICoreClient {
logger.info("handling event: {}", event);
switch (event.getEventTypeCase()) {
case SESSION_EVENT:
handleSessionEvents(controller, event.getSessionEvent());
handleSessionEvents(event.getSessionEvent());
break;
case NODE_EVENT:
handleNodeEvents(controller, event.getNodeEvent());
handleNodeEvents(event.getNodeEvent());
break;
case LINK_EVENT:
handleLinkEvents(controller, event.getLinkEvent());
handleLinkEvents(event.getLinkEvent());
break;
case CONFIG_EVENT:
handleConfigEvents(controller, event.getConfigEvent());
handleConfigEvents(event.getConfigEvent());
break;
case EXCEPTION_EVENT:
handleExceptionEvents(controller, event.getExceptionEvent());
handleExceptionEvents(event.getExceptionEvent());
break;
case FILE_EVENT:
handleFileEvents(controller, event.getFileEvent());
handleFileEvents(event.getFileEvent());
break;
default:
logger.error("unknown event type: {}", event.getEventTypeCase());
@ -1185,7 +1225,7 @@ public class CoreGrpcClient implements ICoreClient {
}
}
private void handleSessionEvents(Controller controller, CoreProto.SessionEvent event) {
private void handleSessionEvents(CoreProto.SessionEvent event) {
logger.info("session event: {}", event);
SessionState state = SessionState.get(event.getEvent());
if (state == null) {
@ -1196,7 +1236,7 @@ public class CoreGrpcClient implements ICoreClient {
// session state event
if (state.getValue() <= 6) {
logger.info("event updating session state: {}", state);
updateState(state);
sessionState = state;
// mobility script event
} else if (state.getValue() <= 9) {
Integer nodeId = event.getNodeId();
@ -1211,21 +1251,21 @@ public class CoreGrpcClient implements ICoreClient {
}
}
private void handleNodeEvents(Controller controller, CoreProto.NodeEvent event) {
private void handleNodeEvents(CoreProto.NodeEvent event) {
logger.info("node event: {}", event);
CoreNode node = protoToNode(event.getNode());
controller.getNetworkGraph().setNodeLocation(node);
}
private void handleExceptionEvents(Controller controller, CoreProto.ExceptionEvent event) {
private void handleExceptionEvents(CoreProto.ExceptionEvent event) {
logger.info("exception event: {}", event);
}
private void handleConfigEvents(Controller controller, CoreProto.ConfigEvent event) {
private void handleConfigEvents(CoreProto.ConfigEvent event) {
logger.info("config event: {}", event);
}
private void handleLinkEvents(Controller controller, CoreProto.LinkEvent event) {
private void handleLinkEvents(CoreProto.LinkEvent event) {
logger.info("link event: {}", event);
CoreLink link = protoToLink(event.getLink());
MessageFlags flag = MessageFlags.get(event.getMessageTypeValue());
@ -1239,7 +1279,7 @@ public class CoreGrpcClient implements ICoreClient {
controller.getNetworkGraph().getGraphViewer().repaint();
}
private void handleFileEvents(Controller controller, CoreProto.FileEvent event) {
private void handleFileEvents(CoreProto.FileEvent event) {
logger.info("file event: {}", event);
}
}

View file

@ -15,7 +15,7 @@ public class CoreLink {
private double throughput;
private boolean visible = true;
private Integer messageType;
private Integer type = 1;
private Integer type = LinkTypes.WIRED.getValue();
private Integer nodeOne;
private Integer nodeTwo;
private CoreInterface interfaceOne;

View file

@ -28,6 +28,7 @@ public class CoreNode {
private String url;
private NodeType nodeType;
private String icon;
private String image;
private boolean loaded = true;
private LayeredIcon graphIcon;
private RadioIcon radioIcon = new RadioIcon();

View file

@ -1,5 +1,7 @@
package com.core.data;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.logging.log4j.LogManager;
@ -19,6 +21,8 @@ public class NodeType {
public static final int HUB = 5;
public static final int WLAN = 6;
public static final int EMANE = 10;
public static final int DOCKER = 15;
public static final int LXC = 16;
@EqualsAndHashCode.Include
private final int id;
private final int value;
@ -27,21 +31,13 @@ public class NodeType {
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"));
add(new NodeType(NodeType.DOCKER, null, "DockerNode", "/icons/dockernode-100.png"));
add(new NodeType(NodeType.LXC, null, "LxcNode", "/icons/lxcnode-100.png"));
}
@ -53,6 +49,19 @@ public class NodeType {
this.icon = icon;
}
public Label createLabel(int size) {
ImageView labelIcon = new ImageView(icon);
labelIcon.setFitWidth(size);
labelIcon.setFitHeight(size);
Label label = new Label(display, labelIcon);
label.setUserData(id);
return label;
}
public static boolean isDefault(NodeType nodeType) {
return nodeType.value == DEFAULT || nodeType.value == DOCKER || nodeType.value == LXC;
}
public static void add(NodeType nodeType) {
ID_LOOKUP.put(nodeType.getId(), nodeType);
}
@ -74,7 +83,7 @@ public class NodeType {
.filter(nodeType -> {
boolean sameType = nodeType.getValue() == type;
boolean sameModel = true;
if (!model.isEmpty()) {
if (model != null && !model.isEmpty()) {
sameModel = model.equals(nodeType.getModel());
}
return sameType && sameModel;

View file

@ -9,7 +9,8 @@ import java.util.List;
@Data
@NoArgsConstructor
public class Session {
private Integer state;
private Integer id;
private SessionState state;
private List<CoreNode> nodes = new ArrayList<>();
private List<CoreLink> links = new ArrayList<>();
}

View file

@ -60,6 +60,8 @@ public class CorePopupGraphMousePlugin<V, E> extends EditingPopupGraphMousePlugi
ContextMenu contextMenu = new ContextMenu();
switch (node.getType()) {
case NodeType.DEFAULT:
case NodeType.DOCKER:
case NodeType.LXC:
contextMenu = new NodeContextMenu(controller, node);
break;
case NodeType.WLAN:

View file

@ -298,8 +298,8 @@ public class NetworkGraph {
Pair<CoreNode> endpoints = graph.getEndpoints(link);
CoreNode nodeOne = endpoints.getFirst();
CoreNode nodeTwo = endpoints.getSecond();
boolean nodeOneIsDefault = isNode(nodeOne);
boolean nodeTwoIsDefault = isNode(nodeTwo);
boolean nodeOneIsDefault = NodeType.isDefault(nodeOne.getNodeType());
boolean nodeTwoIsDefault = NodeType.isDefault(nodeTwo.getNodeType());
// check what we are linking together
IPAddress subnet = null;
@ -360,7 +360,7 @@ public class NetworkGraph {
currentInterface = link.getInterfaceTwo();
}
if (isNode(currentNode)) {
if (NodeType.isDefault(currentNode.getNodeType())) {
interfaces.add(currentInterface);
} else {
Set<CoreInterface> nextInterfaces = getNetworkInterfaces(currentNode, visited);
@ -407,10 +407,6 @@ public class NetworkGraph {
}
}
private boolean isNode(CoreNode node) {
return node.getType() == NodeType.DEFAULT;
}
private CoreInterface createInterface(CoreNode node, int interfaceId, IPAddress subnet) {
CoreInterface coreInterface = new CoreInterface();
coreInterface.setId(interfaceId);
@ -429,8 +425,8 @@ public class NetworkGraph {
CoreInterface interfaceOne = link.getInterfaceOne();
CoreNode nodeTwo = getVertex(link.getNodeTwo());
CoreInterface interfaceTwo = link.getInterfaceTwo();
boolean nodeOneIsDefault = isNode(nodeOne);
boolean nodeTwoIsDefault = isNode(nodeTwo);
boolean nodeOneIsDefault = NodeType.isDefault(nodeOne.getNodeType());
boolean nodeTwoIsDefault = NodeType.isDefault(nodeTwo.getNodeType());
// check what we are unlinking
Set<CoreInterface> interfaces;
@ -445,7 +441,7 @@ public class NetworkGraph {
logger.info("unlinking node one from network reuse subnet: {}", subnet);
}
} else if (nodeTwoIsDefault) {
interfaces = getNetworkInterfaces(nodeOne, new HashSet<>());
interfaces = getNetworkInterfaces(nodeOne, new HashSet<>());
if (interfaces.isEmpty()) {
subnet = interfaceTwo.getIp4().toPrefixBlock();
logger.info("unlinking node two from network reuse subnet: {}", subnet);
@ -461,16 +457,20 @@ public class NetworkGraph {
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);
if (node.isLoaded()) {
return;
}
node.setNodeType(nodeType);
if (node.getType() == NodeType.EMANE) {
String emaneModel = controller.getNodeEmaneDialog().getModels().get(0);
node.setEmane(emaneModel);
} else if (node.getType() == NodeType.DOCKER || node.getType() == NodeType.LXC) {
node.setImage("ubuntu");
}
logger.info("adding user created node: {}", node);
nodeMap.put(node.getId(), node);
}
private void handleVertexRemoved(GraphEvent.Vertex<CoreNode, CoreLink> vertexEvent) {
@ -506,7 +506,7 @@ public class NetworkGraph {
try {
controller.getCoreClient().deleteNode(node);
} catch (IOException ex) {
logger.error("error deleting node");
logger.error("error deleting node", ex);
Toast.error(String.format("Error deleting node: %s", node.getName()));
}
graphViewer.getPickedVertexState().pick(node, false);

View file

@ -40,6 +40,7 @@ public class GraphToolbar extends VBox {
private SVGGlyph stopIcon;
private JFXListView<Label> nodesList = new JFXListView<>();
private JFXListView<Label> devicesList = new JFXListView<>();
private JFXListView<Label> containersList = new JFXListView<>();
private JFXButton selectedEditButton;
private NodeType selectedNodeType;
private boolean isEditing = false;
@ -49,6 +50,7 @@ public class GraphToolbar extends VBox {
@FXML private ComboBox<String> graphModeCombo;
@FXML private JFXButton nodesButton;
@FXML private JFXButton devicesButton;
@FXML private JFXButton containersButton;
public GraphToolbar(Controller controller) {
this.controller = controller;
@ -63,6 +65,7 @@ public class GraphToolbar extends VBox {
setupDrawingButton();
setupNodesButton();
setupDevicesButton();
setupContainersButton();
// initial state
setSelected(true, pickingButton);
@ -113,28 +116,20 @@ public class GraphToolbar extends VBox {
public void setupNodeTypes() {
// clear existing configuration
labelMap.clear();
for (Label label : nodesList.getItems()) {
int id = (int) label.getUserData();
labelMap.remove(id);
}
nodesList.getItems().clear();
devicesList.getItems().clear();
// add current default nodes
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);
if (NodeType.DEFAULT == nodeType.getValue()) {
addNodeType(nodeType.getValue(), nodeType.getModel(), nodesList);
}
}
Comparator<Label> comparator = Comparator.comparing(Label::getText);
nodesList.getItems().sort(comparator);
devicesList.getItems().sort(comparator);
// initial node
nodesList.getSelectionModel().selectFirst();
@ -143,9 +138,6 @@ public class GraphToolbar extends VBox {
selectedEditButton = nodesButton;
controller.getNetworkGraph().setNodeType(selectedNodeType);
updateButtonValues(nodesButton, selectedNodeLabel);
// initial device
updateButtonValues(devicesButton, devicesList.getItems().get(0));
}
private void updateButtonValues(JFXButton button, Label label) {
@ -167,49 +159,53 @@ public class GraphToolbar extends VBox {
}
private void setupNodesButton() {
nodesButton.setTooltip(new Tooltip("Network Nodes (host, pc, etc)"));
nodesList.setOnMouseClicked(event -> {
Label selectedLabel = nodesList.getSelectionModel().getSelectedItem();
setupButtonSelection("CORE Nodes", nodesButton, nodesList);
}
private void setupDevicesButton() {
addNodeType(NodeType.HUB, "hub", devicesList);
addNodeType(NodeType.SWITCH, "lanswitch", devicesList);
addNodeType(NodeType.WLAN, "wlan", devicesList);
addNodeType(NodeType.EMANE, "wlan", devicesList);
devicesList.getSelectionModel().selectFirst();
updateButtonValues(devicesButton, devicesList.getSelectionModel().getSelectedItem());
setupButtonSelection("Network Nodes", devicesButton, devicesList);
}
private void setupContainersButton() {
addNodeType(NodeType.DOCKER, null, containersList);
addNodeType(NodeType.LXC, null, containersList);
containersList.getSelectionModel().selectFirst();
updateButtonValues(containersButton, containersList.getSelectionModel().getSelectedItem());
setupButtonSelection("Container Nodes", containersButton, containersList);
}
private void addNodeType(int type, String model, JFXListView<Label> list) {
NodeType nodeType = NodeType.find(type, model);
Label label = nodeType.createLabel(NODES_ICON_SIZE);
labelMap.put(nodeType.getId(), label);
list.getItems().add(label);
}
private void setupButtonSelection(String tooltipText, JFXButton button, JFXListView<Label> list) {
button.setTooltip(new Tooltip(tooltipText));
list.setOnMouseClicked(event -> {
Label selectedLabel = list.getSelectionModel().getSelectedItem();
if (selectedLabel == null) {
return;
}
updateButtonValues(nodesButton, selectedLabel);
updateButtonValues(button, selectedLabel);
selectedNodeType = NodeType.get((int) selectedLabel.getUserData());
logger.info("selected node type: {}", selectedNodeType);
setSelectedEditButton(nodesButton);
devicesList.getSelectionModel().clearSelection();
controller.getNetworkGraph().setNodeType(selectedNodeType);
setSelectedEditButton(button);
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));
JFXPopup popup = new JFXPopup(list);
button.setOnAction(event -> popup.show(button, JFXPopup.PopupVPosition.TOP,
JFXPopup.PopupHPosition.LEFT, button.getWidth(), 0));
}
@FXML

View file

@ -54,6 +54,7 @@ public class NodeDetails extends ScrollPane {
addRow("Type", node.getNodeType().getDisplay(), true);
}
addRow("EMANE", node.getEmane(), true);
addRow("Image", node.getImage(), true);
addRow("X", node.getPosition().getX().toString(), true);
addRow("Y", node.getPosition().getY().toString(), true);

View file

@ -10,6 +10,7 @@
<JFXButton fx:id="pickingButton" styleClass="toolbar-button" />
<JFXButton fx:id="nodesButton" styleClass="toolbar-button" text="Nodes" />
<JFXButton fx:id="devicesButton" styleClass="toolbar-button" text="Devices" />
<JFXButton fx:id="containersButton" styleClass="toolbar-button" text="Containers" />
<JFXButton fx:id="drawingButton" styleClass="toolbar-button" />
</children>
<padding>

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B