mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-23 05:11:04 +02:00
Adding multi-selection to the GUI and sketching out GUI/Server interactions.
Added persistent meta-data; including node coordinates. Added ability to request path and return one. Change-Id: I3edbdf44bbb8d8133a5e5a1fd0660a3fa5a2d6a1
This commit is contained in:
parent
50128c084f
commit
d1be50de92
@ -23,7 +23,9 @@ import org.eclipse.jetty.websocket.WebSocket;
|
|||||||
import org.onlab.onos.event.Event;
|
import org.onlab.onos.event.Event;
|
||||||
import org.onlab.onos.net.Annotations;
|
import org.onlab.onos.net.Annotations;
|
||||||
import org.onlab.onos.net.Device;
|
import org.onlab.onos.net.Device;
|
||||||
|
import org.onlab.onos.net.DeviceId;
|
||||||
import org.onlab.onos.net.Link;
|
import org.onlab.onos.net.Link;
|
||||||
|
import org.onlab.onos.net.Path;
|
||||||
import org.onlab.onos.net.device.DeviceEvent;
|
import org.onlab.onos.net.device.DeviceEvent;
|
||||||
import org.onlab.onos.net.device.DeviceService;
|
import org.onlab.onos.net.device.DeviceService;
|
||||||
import org.onlab.onos.net.link.LinkEvent;
|
import org.onlab.onos.net.link.LinkEvent;
|
||||||
@ -37,7 +39,11 @@ import org.onlab.onos.net.topology.TopologyVertex;
|
|||||||
import org.onlab.osgi.ServiceDirectory;
|
import org.onlab.osgi.ServiceDirectory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.onlab.onos.net.DeviceId.deviceId;
|
||||||
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
|
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
|
||||||
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
|
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
|
||||||
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
|
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
|
||||||
@ -56,6 +62,12 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
|
|||||||
|
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
|
|
||||||
|
// TODO: extract into an external & durable state; good enough for now and demo
|
||||||
|
private static Map<String, ObjectNode> metaUi = new HashMap<>();
|
||||||
|
|
||||||
|
private static final String COMPACT = "%s/%s-%s/%s";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new web-socket for serving data to GUI topology view.
|
* Creates a new web-socket for serving data to GUI topology view.
|
||||||
*
|
*
|
||||||
@ -101,9 +113,56 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(String data) {
|
public void onMessage(String data) {
|
||||||
System.out.println("Received: " + data);
|
try {
|
||||||
|
ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
|
||||||
|
String type = event.path("event").asText("unknown");
|
||||||
|
ObjectNode payload = (ObjectNode) event.path("payload");
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "updateMeta":
|
||||||
|
metaUi.put(payload.path("id").asText(), payload);
|
||||||
|
break;
|
||||||
|
case "requestPath":
|
||||||
|
findPath(deviceId(payload.path("one").asText()),
|
||||||
|
deviceId(payload.path("two").asText()));
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("Received: " + data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void findPath(DeviceId one, DeviceId two) {
|
||||||
|
Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
|
||||||
|
one, two);
|
||||||
|
if (!paths.isEmpty()) {
|
||||||
|
ObjectNode payload = mapper.createObjectNode();
|
||||||
|
ArrayNode links = mapper.createArrayNode();
|
||||||
|
|
||||||
|
Path path = paths.iterator().next();
|
||||||
|
for (Link link : path.links()) {
|
||||||
|
links.add(compactLinkString(link));
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.set("links", links);
|
||||||
|
sendMessage(envelope("showPath", payload));
|
||||||
|
}
|
||||||
|
// TODO: when no path, send a message to the client
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a compact string representing the given link.
|
||||||
|
*
|
||||||
|
* @param link infrastructure link
|
||||||
|
* @return formatted link string
|
||||||
|
*/
|
||||||
|
public static String compactLinkString(Link link) {
|
||||||
|
return String.format(COMPACT, link.src().deviceId(), link.src().port(),
|
||||||
|
link.dst().deviceId(), link.dst().port());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void sendMessage(String data) {
|
private void sendMessage(String data) {
|
||||||
try {
|
try {
|
||||||
connection.sendMessage(data);
|
connection.sendMessage(data);
|
||||||
@ -130,7 +189,11 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe
|
|||||||
// Add labels, props and stuff the payload into envelope.
|
// Add labels, props and stuff the payload into envelope.
|
||||||
payload.set("labels", labels);
|
payload.set("labels", labels);
|
||||||
payload.set("props", props(device.annotations()));
|
payload.set("props", props(device.annotations()));
|
||||||
payload.set("metaUi", mapper.createObjectNode());
|
|
||||||
|
ObjectNode meta = metaUi.get(device.id().toString());
|
||||||
|
if (meta != null) {
|
||||||
|
payload.set("metaUi", meta);
|
||||||
|
}
|
||||||
|
|
||||||
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
|
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
|
||||||
((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
|
((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
|
||||||
|
8
web/gui/src/main/webapp/json/intent/ev_1_ui.json
Normal file
8
web/gui/src/main/webapp/json/intent/ev_1_ui.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"event": "addHostIntent",
|
||||||
|
"sid": 1,
|
||||||
|
"payload": {
|
||||||
|
"one": "hostOne",
|
||||||
|
"two": "hostTwo"
|
||||||
|
}
|
||||||
|
}
|
11
web/gui/src/main/webapp/json/intent/ev_2_onos.json
Normal file
11
web/gui/src/main/webapp/json/intent/ev_2_onos.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"event": "showPath",
|
||||||
|
"sid": 1,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234",
|
||||||
|
"path": {
|
||||||
|
"links": [ "1-2", "2-3" ],
|
||||||
|
"traffic": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
web/gui/src/main/webapp/json/intent/ev_3_ui.json
Normal file
7
web/gui/src/main/webapp/json/intent/ev_3_ui.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"event": "monitorIntent",
|
||||||
|
"sid": 2,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234"
|
||||||
|
}
|
||||||
|
}
|
13
web/gui/src/main/webapp/json/intent/ev_4_onos.json
Normal file
13
web/gui/src/main/webapp/json/intent/ev_4_onos.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"event": "showPath",
|
||||||
|
"sid": 2,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234",
|
||||||
|
"path": {
|
||||||
|
"links": [ "1-2", "2-3" ],
|
||||||
|
"traffic": true,
|
||||||
|
"srcLabel": "567 Mb",
|
||||||
|
"dstLabel": "6 Mb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
web/gui/src/main/webapp/json/intent/ev_5_onos.json
Normal file
13
web/gui/src/main/webapp/json/intent/ev_5_onos.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"event": "showPath",
|
||||||
|
"sid": 2,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234",
|
||||||
|
"path": {
|
||||||
|
"links": [ "1-2", "2-3" ],
|
||||||
|
"traffic": true,
|
||||||
|
"srcLabel": "967 Mb",
|
||||||
|
"dstLabel": "65 Mb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
web/gui/src/main/webapp/json/intent/ev_6_onos.json
Normal file
11
web/gui/src/main/webapp/json/intent/ev_6_onos.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"event": "showPath",
|
||||||
|
"sid": 2,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234",
|
||||||
|
"path": {
|
||||||
|
"links": [ "1-2", "2-3" ],
|
||||||
|
"traffic": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
web/gui/src/main/webapp/json/intent/ev_7_ui.json
Normal file
7
web/gui/src/main/webapp/json/intent/ev_7_ui.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"event": "cancelMonitorIntent",
|
||||||
|
"sid": 3,
|
||||||
|
"payload": {
|
||||||
|
"intentId": "0x1234"
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
// configuration data
|
// configuration data
|
||||||
var config = {
|
var config = {
|
||||||
useLiveData: false,
|
useLiveData: true,
|
||||||
debugOn: false,
|
debugOn: false,
|
||||||
debug: {
|
debug: {
|
||||||
showNodeXY: true,
|
showNodeXY: true,
|
||||||
@ -120,7 +120,9 @@
|
|||||||
B: toggleBg,
|
B: toggleBg,
|
||||||
L: cycleLabels,
|
L: cycleLabels,
|
||||||
P: togglePorts,
|
P: togglePorts,
|
||||||
U: unpin
|
U: unpin,
|
||||||
|
|
||||||
|
X: requestPath
|
||||||
};
|
};
|
||||||
|
|
||||||
// state variables
|
// state variables
|
||||||
@ -132,7 +134,11 @@
|
|||||||
},
|
},
|
||||||
webSock,
|
webSock,
|
||||||
labelIdx = 0,
|
labelIdx = 0,
|
||||||
selected = {},
|
|
||||||
|
//selected = {},
|
||||||
|
selectOrder = [],
|
||||||
|
selections = {},
|
||||||
|
|
||||||
highlighted = null,
|
highlighted = null,
|
||||||
hovered = null,
|
hovered = null,
|
||||||
viewMode = 'showAll',
|
viewMode = 'showAll',
|
||||||
@ -239,6 +245,14 @@
|
|||||||
view.alert('unpin() callback')
|
view.alert('unpin() callback')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestPath(view) {
|
||||||
|
var payload = {
|
||||||
|
one: selections[selectOrder[0]].obj.id,
|
||||||
|
two: selections[selectOrder[1]].obj.id
|
||||||
|
}
|
||||||
|
sendMessage('requestPath', payload);
|
||||||
|
}
|
||||||
|
|
||||||
// ==============================
|
// ==============================
|
||||||
// Radio Button Callbacks
|
// Radio Button Callbacks
|
||||||
|
|
||||||
@ -287,7 +301,8 @@
|
|||||||
addDevice: addDevice,
|
addDevice: addDevice,
|
||||||
updateDevice: updateDevice,
|
updateDevice: updateDevice,
|
||||||
removeDevice: removeDevice,
|
removeDevice: removeDevice,
|
||||||
addLink: addLink
|
addLink: addLink,
|
||||||
|
showPath: showPath
|
||||||
};
|
};
|
||||||
|
|
||||||
function addDevice(data) {
|
function addDevice(data) {
|
||||||
@ -326,6 +341,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showPath(data) {
|
||||||
|
network.view.alert(data.event + "\n" + data.payload.links.length);
|
||||||
|
}
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
|
|
||||||
function unknownEvent(data) {
|
function unknownEvent(data) {
|
||||||
@ -611,7 +630,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
send : function(text) {
|
send : function(text) {
|
||||||
if (text != null && text.length > 0) {
|
if (text != null) {
|
||||||
webSock._send(text);
|
webSock._send(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -619,11 +638,93 @@
|
|||||||
_send : function(message) {
|
_send : function(message) {
|
||||||
if (webSock.ws) {
|
if (webSock.ws) {
|
||||||
webSock.ws.send(message);
|
webSock.ws.send(message);
|
||||||
|
} else {
|
||||||
|
network.view.alert('no web socket open');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var sid = 0;
|
||||||
|
|
||||||
|
function sendMessage(evType, payload) {
|
||||||
|
var toSend = {
|
||||||
|
event: evType,
|
||||||
|
sid: ++sid,
|
||||||
|
payload: payload
|
||||||
|
};
|
||||||
|
webSock.send(JSON.stringify(toSend));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// Selection stuff
|
||||||
|
|
||||||
|
function selectObject(obj, el) {
|
||||||
|
var n,
|
||||||
|
meta = d3.event.sourceEvent.metaKey;
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
n = d3.select(el);
|
||||||
|
} else {
|
||||||
|
node.each(function(d) {
|
||||||
|
if (d == obj) {
|
||||||
|
n = d3.select(el = this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!n) return;
|
||||||
|
|
||||||
|
if (meta && n.classed('selected')) {
|
||||||
|
deselectObject(obj.id);
|
||||||
|
//flyinPane(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta) {
|
||||||
|
deselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allow for mutli selections
|
||||||
|
var selected = {
|
||||||
|
obj : obj,
|
||||||
|
el : el
|
||||||
|
};
|
||||||
|
|
||||||
|
selections[obj.id] = selected;
|
||||||
|
selectOrder.push(obj.id);
|
||||||
|
|
||||||
|
n.classed('selected', true);
|
||||||
|
//flyinPane(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectObject(id) {
|
||||||
|
var obj = selections[id];
|
||||||
|
if (obj) {
|
||||||
|
d3.select(obj.el).classed('selected', false);
|
||||||
|
selections[id] = null;
|
||||||
|
// TODO: use splice to remove element
|
||||||
|
}
|
||||||
|
//flyinPane(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAll() {
|
||||||
|
// deselect all nodes in the network...
|
||||||
|
node.classed('selected', false);
|
||||||
|
selections = {};
|
||||||
|
selectOrder = [];
|
||||||
|
//flyinPane(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#view').on('click', function(e) {
|
||||||
|
if (!$(e.target).closest('.node').length) {
|
||||||
|
if (!e.metaKey) {
|
||||||
|
deselectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ==============================
|
// ==============================
|
||||||
// View life-cycle callbacks
|
// View life-cycle callbacks
|
||||||
|
|
||||||
@ -678,7 +779,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectCb(d, self) {
|
function selectCb(d, self) {
|
||||||
// TODO: selectObject(d, self);
|
selectObject(d, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
function atDragEnd(d, self) {
|
function atDragEnd(d, self) {
|
||||||
@ -686,11 +787,21 @@
|
|||||||
// if it is a device (not a host)
|
// if it is a device (not a host)
|
||||||
if (d.class === 'device') {
|
if (d.class === 'device') {
|
||||||
d.fixed = true;
|
d.fixed = true;
|
||||||
d3.select(self).classed('fixed', true)
|
d3.select(self).classed('fixed', true);
|
||||||
|
tellServerCoords(d);
|
||||||
// TODO: send new [x,y] back to server, via websocket.
|
// TODO: send new [x,y] back to server, via websocket.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tellServerCoords(d) {
|
||||||
|
sendMessage('updateMeta', {
|
||||||
|
id: d.id,
|
||||||
|
'class': d.class,
|
||||||
|
x: d.x,
|
||||||
|
y: d.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// set up the force layout
|
// set up the force layout
|
||||||
network.force = d3.layout.force()
|
network.force = d3.layout.force()
|
||||||
.size(forceDim)
|
.size(forceDim)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user