Simon Hunt
Committed by Gerrit Code Review

ONOS-1479 -- GUI - augmenting topology view for extensibility:

- Preliminary work in implementing installation of custom buttons to details panel for selected device.

Change-Id: Id26ac301f72b4521d2a388d34ee0a287f400c68c
......@@ -35,6 +35,7 @@ public class PropertyPanel {
private String typeId;
private String id;
private List<Prop> properties = new ArrayList<>();
private List<Button> buttons = new ArrayList<>();
/**
* Constructs a property panel model with the given title and
......@@ -174,6 +175,16 @@ public class PropertyPanel {
return properties;
}
/**
* Returns the list of button descriptors.
*
* @return the button list
*/
// TODO: consider protecting this?
public List<Button> buttons() {
return buttons;
}
// == MUTATORS
/**
......@@ -226,6 +237,17 @@ public class PropertyPanel {
return this;
}
/**
* Adds a button descriptor with the given identifier, to the panel data.
*
* @param id button identifier
* @return self, for chaining
*/
public PropertyPanel addButton(String id) {
buttons.add(new Button(id));
return this;
}
// ====================
......@@ -300,4 +322,29 @@ public class PropertyPanel {
}
}
/**
* Button descriptor. Note that these work in conjunction with
* "buttons" defined in the JavaScript code for the overlay.
*/
public static class Button {
private final String id;
/**
* Constructs a button descriptor with the given identifier.
*
* @param id button identifier
*/
public Button(String id) {
this.id = id;
}
/**
* Returns the identifier for this button.
*
* @return button identifier
*/
public String id() {
return id;
}
}
}
......
......@@ -73,4 +73,37 @@ public final class TopoConstants {
public static final String STOP = "stop";
public static final String CLOUD = "cloud";
}
/**
* Defines constants for property names on the default summary and
* details panels.
*/
public static final class Properties {
// summary panel
public static final String DEVICES = "Devices";
public static final String LINKS = "Links";
public static final String HOSTS = "Hosts";
public static final String TOPOLOGY_SSCS = "Topology SCCs";
public static final String INTENTS = "Intents";
public static final String TUNNELS = "Tunnels";
public static final String FLOWS = "Flows";
public static final String VERSION = "Version";
// device details
public static final String URI = "URI";
public static final String VENDOR = "Vendor";
public static final String HW_VERSION = "H/W Version";
public static final String SW_VERSION = "S/W Version";
public static final String SERIAL_NUMBER = "Serial Number";
public static final String PROTOCOL = "Protocol";
public static final String LATITUDE = "Latitude";
public static final String LONGITUDE = "Longitude";
public static final String PORTS = "Ports";
// host details
public static final String MAC = "MAC";
public static final String IP = "IP";
public static final String VLAN = "VLAN";
}
}
......
......@@ -107,6 +107,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
import static org.onosproject.ui.topo.TopoConstants.*;
/**
* Facility for creating messages bound for the topology viewer.
......@@ -448,15 +449,15 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Topology topology = topologyService.currentTopology();
return new PropertyPanel("ONOS Summary", "node")
.addProp("Devices", topology.deviceCount())
.addProp("Links", topology.linkCount())
.addProp("Hosts", hostService.getHostCount())
.addProp("Topology SCCs", topology.clusterCount())
.addProp(Properties.DEVICES, topology.deviceCount())
.addProp(Properties.LINKS, topology.linkCount())
.addProp(Properties.HOSTS, hostService.getHostCount())
.addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
.addSeparator()
.addProp("Intents", intentService.getIntentCount())
.addProp("Tunnels", tunnelService.tunnelCount())
.addProp("Flows", flowService.getFlowRuleCount())
.addProp("Version", version);
.addProp(Properties.INTENTS, intentService.getIntentCount())
.addProp(Properties.TUNNELS, tunnelService.tunnelCount())
.addProp(Properties.FLOWS, flowService.getFlowRuleCount())
.addProp(Properties.VERSION, version);
}
// Returns property panel model for device details response.
......@@ -473,19 +474,19 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
PropertyPanel pp = new PropertyPanel(title, typeId)
.id(deviceId.toString())
.addProp("URI", deviceId.toString())
.addProp("Vendor", device.manufacturer())
.addProp("H/W Version", device.hwVersion())
.addProp("S/W Version", device.swVersion())
.addProp("Serial Number", device.serialNumber())
.addProp("Protocol", annot.value(AnnotationKeys.PROTOCOL))
.addProp(Properties.URI, deviceId.toString())
.addProp(Properties.VENDOR, device.manufacturer())
.addProp(Properties.HW_VERSION, device.hwVersion())
.addProp(Properties.SW_VERSION, device.swVersion())
.addProp(Properties.SERIAL_NUMBER, device.serialNumber())
.addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
.addSeparator()
.addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
.addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE))
.addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
.addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
.addSeparator()
.addProp("Ports", portCount)
.addProp("Flows", flowCount)
.addProp("Tunnels", tunnelCount);
.addProp(Properties.PORTS, portCount)
.addProp(Properties.FLOWS, flowCount)
.addProp(Properties.TUNNELS, tunnelCount);
// TODO: add button descriptors
......@@ -571,12 +572,12 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
PropertyPanel pp = new PropertyPanel(title, typeId)
.id(hostId.toString())
.addProp("MAC", host.mac())
.addProp("IP", host.ipAddresses(), "[\\[\\]]")
.addProp("VLAN", vlan.equals("-1") ? "none" : vlan)
.addProp(Properties.MAC, host.mac())
.addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
.addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
.addSeparator()
.addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
.addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE));
.addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
.addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
// TODO: add button descriptors
return pp;
......@@ -859,6 +860,12 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
}
result.set("propOrder", porder);
result.set("props", pnode);
ArrayNode buttons = arrayNode();
for (PropertyPanel.Button b : pp.buttons()) {
buttons.add(b.id());
}
result.set("buttons", buttons);
return result;
}
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.meowster.over;
import org.onosproject.ui.UiTopoOverlay;
import org.onosproject.ui.topo.PropertyPanel;
import org.onosproject.ui.topo.TopoConstants.Glyphs;
import static org.onosproject.ui.topo.TopoConstants.Properties.*;
/**
* Our topology overlay.
*/
public class AppUiTopoOverlay extends UiTopoOverlay {
// NOTE: this must match the ID defined in topov.js
private static final String OVERLAY_ID = "meowster-overlay";
private static final String MY_TITLE = "I changed the title";
private static final String MY_VERSION = "Beta-1.0.0042";
private static final String FOO = "foo";
private static final String BAR = "bar";
public AppUiTopoOverlay() {
super(OVERLAY_ID);
}
@Override
public void modifySummary(PropertyPanel pp) {
pp.title("My App Rocks!")
.typeId(Glyphs.CROWN)
.removeProps(
TOPOLOGY_SSCS,
INTENTS,
TUNNELS,
FLOWS,
VERSION
)
.addProp(VERSION, MY_VERSION);
}
@Override
public void modifyDeviceDetails(PropertyPanel pp) {
pp.title(MY_TITLE);
pp.removeProps(LATITUDE, LONGITUDE);
pp.addButton(FOO).addButton(BAR);
}
// TODO: override more methods, as required...
}
NOTE:
This directory is putting under revision control some key files from
an example stand-alone app, as I develop the topology overlay functionality.
......@@ -7,21 +7,52 @@
// our overlay definition
var overlay = {
overlayId: 'sampleTopoOver',
// NOTE: this must match the ID defined in AppUiTopoOverlay
overlayId: 'meowster-overlay',
glyphId: '*star4',
tooltip: 'Sample Meowster Topo Overlay',
// NOTE: for the glyph, could alternately use id: <existingGlyphId>
// instead of defining viewbox and data (vb, d)
// glyph: { id: 'crown' }
glyph: {
// These glyphs get installed using the overlayId as a prefix.
// e.g. 'star4' is installed as 'meowster-overlay-star4'
// They can be referenced (from this overlay) as '*star4'
// That is, the '*' prefix stands in for 'meowster-overlay-'
glyphs: {
star4: {
vb: '0 0 8 8',
d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z'
},
tooltip: 'Sample Topology Overlay',
banner: {
vb: '0 0 6 6',
d: 'M1,1v4l2,-2l2,2v-4z'
}
},
activate: activateOverlay,
deactivate: deactivateOverlay
deactivate: deactivateOverlay,
// button descriptors - these can be added to overview or detail panels
buttons: {
foo: {
gid: 'chain',
tt: 'a FOO action',
cb: fooCb
},
bar: {
gid: '*banner',
tt: 'a BAR action',
cb: barCb
}
}
};
function fooCb(data) {
$log.debug('FOO callback with data:', data);
}
function barCb(data) {
$log.debug('BAR callback with data:', data);
}
// === implementation of overlay API (essentially callbacks)
function activateOverlay() {
$log.debug("sample topology overlay ACTIVATED");
......
......@@ -44,26 +44,36 @@
$log.warn(tos + fn + '(): ' + msg);
}
function handleGlyph(o) {
var gdata = fs.isO(o.glyph),
oid,
data = {};
if (!gdata) {
o._glyphId = 'unknown';
} else {
if (gdata.id) {
o._glyphId = gdata.id;
} else if (gdata.vb && gdata.d) {
oid = o.overlayId;
data['_' + oid] = gdata.vb;
data[oid] = gdata.d;
function mkGlyphId(oid, gid) {
return (gid[0] === '*') ? oid + '-' + gid.slice(1) : gid;
}
function handleGlyphs(o) {
var gdata = fs.isO(o.glyphs),
oid = o.overlayId,
gid = o.glyphId || 'unknown',
data = {},
note = [];
o._glyphId = mkGlyphId(oid, gid);
o.mkGid = function (g) {
return mkGlyphId(oid, g);
};
o.mkId = function (s) {
return oid + '-' + s;
};
// process glyphs if defined
if (gdata) {
angular.forEach(gdata, function (value, key) {
var fullkey = oid + '-' + key;
data['_' + fullkey] = value.vb;
data[fullkey] = value.d;
note.push('*' + key);
});
gs.registerGlyphs(data);
o._glyphId = oid;
$log.debug('registered overlay glyph:', oid);
} else {
warn('registerGlyph', 'problem with glyph data');
}
$log.debug('registered overlay glyphs:', oid, note);
}
}
......@@ -79,7 +89,7 @@
return warn(r, 'already registered: "' + id + '"');
}
overlays[id] = overlay;
handleGlyph(overlay);
handleGlyphs(overlay);
$log.debug(tos + 'registered overlay: ' + id, overlay);
}
......@@ -132,6 +142,28 @@
}
}
// install buttons from the current overlay
function installButtons(bids, addFn, data) {
if (current) {
bids.forEach(function (bid) {
var btn = current.buttons[bid],
funcWrap = function () {
btn.cb(data);
};
if (btn) {
addFn({
id: current.mkId(bid),
gid: current.mkGid(btn.gid),
cb: funcWrap,
tt: btn.tt
});
}
});
}
}
angular.module('ovTopo')
.factory('TopoOverlayService',
['$log', 'FnService', 'GlyphService', 'WebSocketService',
......@@ -147,7 +179,8 @@
unregister: unregister,
list: list,
overlay: overlay,
tbSelection: tbSelection
tbSelection: tbSelection,
installButtons: installButtons
}
}]);
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, fs, wss, tps, tts, ns;
var $log, fs, wss, tov, tps, tts, ns;
// api to topoForce
var api;
......@@ -229,9 +229,13 @@
// Event Handlers
function showDetails(data) {
var buttons = fs.isA(data.buttons);
// display the data for the single selected node
tps.displaySingle(data);
// TODO: use server-side-button-descriptors to add buttons
// always add the 'show traffic' action
tps.addAction({
id: '-sin-rel-traf-btn',
......@@ -249,6 +253,13 @@
tt: 'Show Device Flows'
});
}
// TODO: for now, install overlay buttons here
if (buttons) {
tov.installButtons(buttons, tps.addAction, data);
}
// TODO: have the server return explicit class and ID of each node
// for now, we assume the node is a device if it has a URI
if ((data.props).hasOwnProperty('URI')) {
......@@ -308,13 +319,14 @@
angular.module('ovTopo')
.factory('TopoSelectService',
['$log', 'FnService', 'WebSocketService',
['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
'TopoPanelService', 'TopoTrafficService', 'NavService',
function (_$log_, _fs_, _wss_, _tps_, _tts_, _ns_) {
function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _ns_) {
$log = _$log_;
fs = _fs_;
wss = _wss_;
tov = _tov_;
tps = _tps_;
tts = _tts_;
ns = _ns_;
......
......@@ -29,7 +29,7 @@
// traffic overlay definition
var overlay = {
overlayId: 'traffic',
glyph: { id: 'allTraffic' },
glyphId: 'allTraffic',
tooltip: 'Traffic Overlay',
activate: activateTraffic,
......