Simon Hunt
Committed by Gerrit Code Review

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

Change-Id: I11820a9ff8f446c0d10a0311cee5ce448c15f402
......@@ -17,29 +17,22 @@
package org.onosproject.ui.topo;
import com.google.common.base.MoreObjects;
/**
* Designates a descriptor for a button on the topology view panels.
* Designates the identity of a button on the topology view panels.
*/
public class ButtonDescriptor {
public class ButtonId {
private final String id;
private final String glyphId;
private final String tooltip;
/**
* Creates a button descriptor with the given identifier, glyph ID, and
* tooltip text. To reference a custom glyph defined in the overlay itself,
* prefix its ID with an asterisk, (e.g. {@code "*myGlyph"}). Alternatively,
* use one of the {@link TopoConstants.Glyphs predefined constant}.
* Creates a button ID with the given identifier.
*
* @param id identifier for the button
* @param glyphId identifier for the glyph
* @param tooltip tooltip text
*/
public ButtonDescriptor(String id, String glyphId, String tooltip) {
public ButtonId(String id) {
this.id = id;
this.glyphId = glyphId;
this.tooltip = tooltip;
}
/**
......@@ -51,22 +44,10 @@ public class ButtonDescriptor {
return id;
}
/**
* Returns the glyph identifier for this button.
*
* @return glyph identifier
*/
public String glyphId() {
return glyphId;
}
/**
* Returns the tooltip text for this button.
*
* @return tooltip text
*/
public String tooltip() {
return tooltip;
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("id", id()).toString();
}
@Override
......@@ -78,9 +59,8 @@ public class ButtonDescriptor {
return false;
}
ButtonDescriptor that = (ButtonDescriptor) o;
ButtonId that = (ButtonId) o;
return id.equals(that.id);
}
@Override
......
......@@ -35,7 +35,7 @@ public class PropertyPanel {
private String typeId;
private String id;
private List<Prop> properties = new ArrayList<>();
private List<ButtonDescriptor> buttons = new ArrayList<>();
private List<ButtonId> buttons = new ArrayList<>();
/**
* Constructs a property panel model with the given title and
......@@ -181,7 +181,7 @@ public class PropertyPanel {
* @return the button list
*/
// TODO: consider protecting this?
public List<ButtonDescriptor> buttons() {
public List<ButtonId> buttons() {
return buttons;
}
......@@ -243,7 +243,7 @@ public class PropertyPanel {
* @param button button descriptor
* @return self, for chaining
*/
public PropertyPanel addButton(ButtonDescriptor button) {
public PropertyPanel addButton(ButtonId button) {
buttons.add(button);
return this;
}
......@@ -254,10 +254,10 @@ public class PropertyPanel {
* @param descriptors descriptors to remove
* @return self, for chaining
*/
public PropertyPanel removeButtons(ButtonDescriptor... descriptors) {
Set<ButtonDescriptor> forRemoval = Sets.newHashSet(descriptors);
List<ButtonDescriptor> toKeep = new ArrayList<>();
for (ButtonDescriptor bd: buttons) {
public PropertyPanel removeButtons(ButtonId... descriptors) {
Set<ButtonId> forRemoval = Sets.newHashSet(descriptors);
List<ButtonId> toKeep = new ArrayList<>();
for (ButtonId bd: buttons) {
if (!forRemoval.contains(bd)) {
toKeep.add(bd);
}
......
......@@ -108,30 +108,22 @@ public final class TopoConstants {
public static final String VLAN = "VLAN";
}
private static final class CoreButton extends ButtonDescriptor {
private CoreButton(String tag, String glyphId, boolean extra) {
super("show" + tag + "View",
glyphId,
"Show " + tag + " View" + (extra ? " for this Device" : ""));
}
}
/**
* Defines constants for core buttons that appear on the topology
* Defines identities of core buttons that appear on the topology
* details panel.
*/
public static final class CoreButtons {
public static final ButtonDescriptor SHOW_DEVICE_VIEW =
new CoreButton("Device", Glyphs.SWITCH, false);
public static final ButtonId SHOW_DEVICE_VIEW =
new ButtonId("showDeviceView");
public static final ButtonDescriptor SHOW_FLOW_VIEW =
new CoreButton("Flow", Glyphs.FLOW_TABLE, true);
public static final ButtonId SHOW_FLOW_VIEW =
new ButtonId("showFlowView");
public static final ButtonDescriptor SHOW_PORT_VIEW =
new CoreButton("Port", Glyphs.PORT_TABLE, true);
public static final ButtonId SHOW_PORT_VIEW =
new ButtonId("showPortView");
public static final ButtonDescriptor SHOW_GROUP_VIEW =
new CoreButton("Group", Glyphs.GROUP_TABLE, true);
public static final ButtonId SHOW_GROUP_VIEW =
new ButtonId("showGroupView");
}
}
......
......@@ -19,27 +19,38 @@ package org.onosproject.ui.topo;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ButtonDescriptor}.
* Unit tests for {@link ButtonId}.
*/
public class ButtonDescriptorTest {
public class ButtonIdTest {
private static final String ID = "my-id";
private static final String GID = "my-glyphId";
private static final String TT = "my-tewltyp";
private static final String ID1 = "id-1";
private static final String ID2 = "id-2";
private ButtonDescriptor bd;
private ButtonId b1, b2;
@Test
public void basic() {
bd = new ButtonDescriptor(ID, GID, TT);
b1 = new ButtonId(ID1);
}
assertEquals("bad id", ID, bd.id());
assertEquals("bad gid", GID, bd.glyphId());
assertEquals("bad tt", TT, bd.tooltip());
@Test
public void same() {
b1 = new ButtonId(ID1);
b2 = new ButtonId(ID1);
assertFalse("same ref?", b1 == b2);
assertTrue("not equal?", b1.equals(b2));
}
@Test
public void notSame() {
b1 = new ButtonId(ID1);
b2 = new ButtonId(ID2);
assertFalse("same ref?", b1 == b2);
assertFalse("equal?", b1.equals(b2));
}
}
......
......@@ -47,14 +47,6 @@ public class PropertyPanelTest {
private static final String VALUE_B = "Bee";
private static final String VALUE_C = "Sea";
private static final String VALUE_Z = "Zed";
private static final String GID_A = "gid-A";
private static final String GID_B = "gid-B";
private static final String GID_C = "gid-C";
private static final String GID_Z = "gid-Z";
private static final String TT_A = "toolTip-A";
private static final String TT_B = "toolTip-B";
private static final String TT_C = "toolTip-C";
private static final String TT_Z = "toolTip-Z";
private static final Map<String, Prop> PROP_MAP = new HashMap<>();
......@@ -211,17 +203,13 @@ public class PropertyPanelTest {
validateProp(KEY_B, ">byyy<");
}
private static final ButtonDescriptor BD_A =
new ButtonDescriptor(KEY_A, GID_A, TT_A);
private static final ButtonDescriptor BD_B =
new ButtonDescriptor(KEY_B, GID_B, TT_B);
private static final ButtonDescriptor BD_C =
new ButtonDescriptor(KEY_C, GID_C, TT_C);
private static final ButtonDescriptor BD_Z =
new ButtonDescriptor(KEY_Z, GID_Z, TT_Z);
private static final ButtonId BD_A = new ButtonId(KEY_A);
private static final ButtonId BD_B = new ButtonId(KEY_B);
private static final ButtonId BD_C = new ButtonId(KEY_C);
private static final ButtonId BD_Z = new ButtonId(KEY_Z);
private void verifyButtons(String... keys) {
Iterator<ButtonDescriptor> iter = pp.buttons().iterator();
Iterator<ButtonId> iter = pp.buttons().iterator();
for (String k: keys) {
assertEquals("wrong button", k, iter.next().id());
}
......
......@@ -112,7 +112,10 @@ public abstract class AbstractAccumulator<T> implements Accumulator<T> {
if (isReady()) {
try {
maxTask = cancelIfActive(maxTask);
processItems(finalizeCurrentBatch());
List<T> items = finalizeCurrentBatch();
if (!items.isEmpty()) {
processItems(items);
}
} catch (Exception e) {
log.warn("Unable to process batch due to {}", e);
}
......
......@@ -214,6 +214,8 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
new UpdateMeta(),
new EqMasters(),
// TODO: implement "showHighlights" event (replaces "showTraffic")
// TODO: migrate traffic related to separate app
new AddHostIntent(),
new AddMultiSourceIntent(),
......@@ -965,6 +967,14 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
@Override
public void processItems(List<Event> items) {
// Start-of-Debugging
long now = System.currentTimeMillis();
String me = this.toString();
String miniMe = me.replaceAll("^.*@", "me@");
log.debug("Time: {}; this: {}, processing items ({} events)",
now, miniMe, items.size());
// End-of-Debugging
try {
if (summaryRunning) {
msgSender.execute(() -> requestSummary(0));
......
......@@ -72,7 +72,7 @@ import org.onosproject.net.topology.TopologyService;
import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.topo.ButtonDescriptor;
import org.onosproject.ui.topo.ButtonId;
import org.onosproject.ui.topo.PropertyPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -123,6 +123,8 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
new ProviderId("core", "org.onosproject.core", true);
private static final String COMPACT = "%s/%s-%s/%s";
private static final String SHOW_HIGHLIGHTS = "showHighlights";
private static final double KILO = 1024;
private static final double MEGA = 1024 * KILO;
private static final double GIGA = 1024 * MEGA;
......@@ -642,7 +644,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
labelsN.add("");
}
}
return JsonUtils.envelope("showTraffic", 0, payload);
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
}
private Load getLinkLoad(Link link) {
......@@ -679,7 +681,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
addLinkFlows(link, paths, counts.get(link));
}
}
return JsonUtils.envelope("showTraffic", 0, payload);
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
}
private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
......@@ -723,7 +725,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
}
return JsonUtils.envelope("showTraffic", 0, payload);
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
}
// Classifies the link traffic according to the specified classes.
......@@ -870,21 +872,13 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
result.set("props", pnode);
ArrayNode buttons = arrayNode();
for (ButtonDescriptor b : pp.buttons()) {
buttons.add(json(b));
for (ButtonId b : pp.buttons()) {
buttons.add(b.id());
}
result.set("buttons", buttons);
return result;
}
// translates the button descriptor into JSON
private ObjectNode json(ButtonDescriptor bdesc) {
return objectNode()
.put("id", bdesc.id())
.put("gid", bdesc.glyphId())
.put("tt", bdesc.tooltip());
}
// Produces canonical link key, i.e. one that will match link and its inverse.
static LinkKey canonicalLinkKey(Link link) {
......
......@@ -18,6 +18,8 @@
package org.onosproject.ui.impl;
import org.onosproject.ui.UiTopoOverlay;
import org.onosproject.ui.topo.ButtonId;
import org.onosproject.ui.topo.PropertyPanel;
/**
* Topology Overlay for network traffic.
......@@ -25,12 +27,21 @@ import org.onosproject.ui.UiTopoOverlay;
public class TrafficOverlay extends UiTopoOverlay {
private static final String TRAFFIC_ID = "traffic";
/**
* Constructs the traffic overlay.
*/
private static final String SDF_ID = "showDeviceFlows";
private static final String SRT_ID = "showRelatedTraffic";
private static final ButtonId SHOW_DEVICE_FLOWS = new ButtonId(SDF_ID);
private static final ButtonId SHOW_RELATED_TRAFFIC = new ButtonId(SRT_ID);
public TrafficOverlay() {
super(TRAFFIC_ID);
}
// TODO : override init(), activate(), deactivate(), destroy()
@Override
public void modifyDeviceDetails(PropertyPanel pp) {
pp.addButton(SHOW_DEVICE_FLOWS)
.addButton(SHOW_RELATED_TRAFFIC);
}
}
......
......@@ -18,7 +18,7 @@
package org.meowster.over;
import org.onosproject.ui.UiTopoOverlay;
import org.onosproject.ui.topo.ButtonDescriptor;
import org.onosproject.ui.topo.ButtonId;
import org.onosproject.ui.topo.PropertyPanel;
import org.onosproject.ui.topo.TopoConstants.CoreButtons;
import org.onosproject.ui.topo.TopoConstants.Glyphs;
......@@ -36,12 +36,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay {
private static final String MY_TITLE = "I changed the title";
private static final String MY_VERSION = "Beta-1.0.0042";
private static final ButtonDescriptor FOO_DESCRIPTOR =
new ButtonDescriptor("foo", "chain", "A FOO action");
private static final ButtonDescriptor BAR_DESCRIPTOR =
new ButtonDescriptor("bar", "*banner", "A BAR action");
private static final ButtonId FOO_BUTTON = new ButtonId("foo");
private static final ButtonId BAR_BUTTON = new ButtonId("bar");
public AppUiTopoOverlay() {
super(OVERLAY_ID);
......@@ -68,8 +64,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay {
pp.title(MY_TITLE);
pp.removeProps(LATITUDE, LONGITUDE);
pp.addButton(FOO_DESCRIPTOR)
.addButton(BAR_DESCRIPTOR);
pp.addButton(FOO_BUTTON)
.addButton(BAR_BUTTON);
pp.removeButtons(CoreButtons.SHOW_PORT_VIEW)
.removeButtons(CoreButtons.SHOW_GROUP_VIEW);
......
......@@ -3,7 +3,10 @@
'use strict';
// injected refs
var $log;
var $log, tov;
// internal state
var someStateValue = true;
// our overlay definition
var overlay = {
......@@ -27,40 +30,127 @@
}
},
activate: activateOverlay,
deactivate: deactivateOverlay,
activate: function () {
$log.debug("sample topology overlay ACTIVATED");
},
deactivate: function () {
$log.debug("sample topology overlay DEACTIVATED");
},
// button callbacks matching button identifiers
buttonActions: {
foo: fooCb,
bar: barCb
}
};
function fooCb(data) {
$log.debug('FOO callback with data:', data);
// detail panel button definitions
buttons: {
foo: {
gid: 'chain',
tt: 'A FOO action',
cb: function (data) {
$log.debug('FOO action invoked with data:', data);
}
},
bar: {
gid: '*banner',
tt: 'A BAR action',
cb: function (data) {
$log.debug('BAR action invoked with data:', data);
}
}
},
function barCb(data) {
$log.debug('BAR callback with data:', data);
// Key bindings for traffic overlay buttons
// NOTE: fully qual. button ID is derived from overlay-id and key-name
keyBindings: {
V: {
cb: buttonCallback,
tt: 'Uses the V key',
gid: '*banner'
},
F: {
cb: buttonCallback,
tt: 'Uses the F key',
gid: 'chain'
},
G: {
cb: buttonCallback,
tt: 'Uses the G key',
gid: 'crown'
},
T: {
cb: buttonCallback,
tt: 'Uses the T key',
gid: 'switch'
},
R: {
cb: buttonCallback,
tt: 'Uses the R key',
gid: 'endstation'
},
0: {
cb: buttonCallback,
tt: 'Uses the ZERO key',
gid: 'xMark'
},
_keyOrder: [
'0', 'V', 'F', 'G', 'T', 'R'
]
// NOTE: T and R should be rejected (not installed)
// T is reserved for 'toggle Theme'
// R is reserved for 'Reset pan and zoom'
},
hooks: {
// hook for handling escape key
// Must return true to consume ESC, false otherwise.
escape: cancelState,
// hooks for when the selection changes...
empty: function () {
selectionCallback('empty');
},
single: function (data) {
selectionCallback('single', data);
},
multi: function (selectOrder) {
selectionCallback('multi', selectOrder);
tov.addDetailButton('foo');
tov.addDetailButton('bar');
}
}
// === implementation of overlay API (essentially callbacks)
function activateOverlay() {
$log.debug("sample topology overlay ACTIVATED");
};
// invoked when the escape key is pressed
function cancelState() {
if (someStateValue) {
someStateValue = false;
// we consumed the ESC event
return true;
}
return false;
}
function deactivateOverlay() {
$log.debug("sample topology overlay DEACTIVATED");
function buttonCallback(x) {
$log.debug('Toolbar-button callback', x);
}
function selectionCallback(x, d) {
$log.debug('Selection callback', x, d);
}
// invoke code to register with the overlay service
angular.module('ovSample')
.run(['$log', 'TopoOverlayService',
function (_$log_, tov) {
function (_$log_, _tov_) {
$log = _$log_;
tov = _tov_;
tov.register(overlay);
}]);
......
......@@ -45,6 +45,10 @@
"C429.9,285.5,426.7,293.2,427.7,300.4z"
},
// TODO: ONOS-2566 glyphs for device types:
// otn, roadm_otn, firewall, balancer, ips, ids,
// controller, virtual, fiber_switch, other
glyphDataSet = {
_viewbox: "0 0 110 110",
......
......@@ -18,6 +18,7 @@
ONOS GUI -- Widget -- Toolbar Service
*/
// TODO: Augment service to allow toolbars to exist on right edge of screen
// TODO: also - make toolbar more object aware (rows etc.)
(function () {
......@@ -80,6 +81,7 @@
panel = ps.createPanel(tbid, settings),
arrowDiv = createArrow(panel),
currentRow = panel.append('div').classed('tbar-row', true),
rowButtonIds = [], // for removable buttons
tbWidth = arrowSize + 2, // empty toolbar width
maxWidth = panel.width();
......@@ -162,7 +164,41 @@
} else {
panel.append('br');
currentRow = panel.append('div').classed('tbar-row', true);
// return API to allow caller more access to the row
return {
clear: rowClear,
setText: rowSetText,
addButton: rowAddButton,
classed: rowClassed
};
}
}
function rowClear() {
currentRow.selectAll('*').remove();
rowButtonIds.forEach(function (bid) {
delete items[bid];
});
rowButtonIds = [];
}
// installs a div with text into the button row
function rowSetText(text) {
rowClear();
currentRow.append('div').classed('tbar-row-text', true)
.html(text);
}
function rowAddButton(id, gid, cb, tooltip) {
var b = addButton(id, gid, cb, tooltip);
if (b) {
rowButtonIds.push(id);
}
}
function rowClassed(classes, bool) {
currentRow.classed(classes, bool);
}
function show(cb) {
......
......@@ -305,6 +305,21 @@ html[data-platform='iPad'] #topo-p-detail {
filter: url("data:image/svg+xml;utf8, <svg xmlns = \'http://www.w3.org/2000/svg\'><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" id=\"yellow-glow\"><feColorMatrix type=\"matrix\" values=\"0 0 0 0 1.0 0 0 0 0 1.0 0 0 0 0 0.3 0 0 0 1 0 \"></feColorMatrix><feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"></feGaussianBlur><feMerge><feMergeNode in=\"coloredBlur\"></feMergeNode><feMergeNode in=\"SourceGraphic\"></feMergeNode></feMerge></filter></svg>#yellow-glow");
}
/* --- Toolbar --- */
#toolbar-topo-tbar .tbar-row.right {
width: 100%;
}
#toolbar-topo-tbar .tbar-row-text {
height: 21px;
text-align: right;
padding: 8px 60px 0 0;
font-style: italic;
}
/* --- Topo Nodes --- */
#ov-topo svg .suppressed {
......
......@@ -40,7 +40,7 @@
// --- Short Cut Keys ------------------------------------------------
function setUpKeys() {
function setUpKeys(overlayKeys) {
// key bindings need to be made after the services have been injected
// thus, deferred to here...
actionMap = {
......@@ -63,14 +63,6 @@
R: [resetZoom, 'Reset pan / zoom'],
dot: [ttbs.toggleToolbar, 'Toggle Toolbar'],
V: [tts.showRelatedIntentsAction, 'Show all related intents'],
rightArrow: [tts.showNextIntentAction, 'Show next related intent'],
leftArrow: [tts.showPrevIntentAction, 'Show previous related intent'],
W: [tts.showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
A: [tts.showAllFlowTrafficAction, 'Monitor all traffic using flow stats'],
Q: [tts.showAllPortTrafficAction, 'Monitor all traffic using port stats'],
F: [tts.showDeviceLinkFlowsAction, 'Show device link flows'],
E: [equalizeMasters, 'Equalize mastership roles'],
esc: handleEscape,
......@@ -78,12 +70,16 @@
_keyListener: ttbs.keyListener,
_helpFormat: [
['I', 'O', 'D', '-', 'H', 'M', 'P', 'dash', 'B' ],
['X', 'Z', 'N', 'L', 'U', 'R', '-', 'dot'],
['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B', 'S' ],
['X', 'Z', 'N', 'L', 'U', 'R', '-', 'E', '-', 'dot'],
[] // this column reserved for overlay actions
]
};
if (fs.isO(overlayKeys)) {
mergeKeys(overlayKeys);
}
ks.keyBindings(actionMap);
ks.gestureNotes([
......@@ -95,6 +91,22 @@
]);
}
// when a topology overlay is activated, we need to bind their keystrokes
// and include them in the quick-help panel
function mergeKeys(extra) {
var _hf = actionMap._helpFormat[2];
extra._keyOrder.forEach(function (k) {
var d = extra[k],
cb = d && d.cb,
tt = d && d.tt;
// NOTE: ignore keys that are already defined
if (d && !actionMap[k]) {
actionMap[k] = [cb, tt];
_hf.push(k);
}
});
}
// --- Keystroke functions -------------------------------------------
function toggleInstances(x) {
......@@ -153,6 +165,10 @@
// if an instance is selected, cancel the affinity mapping
tis.cancelAffinity()
} else if (tov.hooks.escape()) {
// else if the overlay consumed the ESC event...
// (work already done)
} else if (tss.deselectAll()) {
// else if we have node selections, deselect them all
// (work already done)
......@@ -169,19 +185,15 @@
} else if (tps.summaryVisible()) {
// else if the Summary Panel is visible, hide it
tps.hideSummaryPanel();
} else {
// TODO: set hover mode to hoverModeNone
// talk to Thomas about this: shouldn't it be done
// when we deselect the node (if tss.haveDetails()...)
}
}
// --- Toolbar Functions ---------------------------------------------
function notValid(what) {
$log.warn('Topo.js getActionEntry(): Not a valid ' + what);
$log.warn('topo.js getActionEntry(): Not a valid ' + what);
}
function getActionEntry(key) {
var entry;
......@@ -201,7 +213,8 @@
function setUpToolbar() {
ttbs.init({
getActionEntry: getActionEntry
getActionEntry: getActionEntry,
setUpKeys: setUpKeys
});
ttbs.createToolbar();
}
......@@ -503,7 +516,6 @@
restoreConfigFromPrefs();
$log.debug('registered overlays...', tov.list());
$log.log('OvTopoCtrl has been created');
}]);
}());
......
......@@ -27,14 +27,14 @@
'use strict';
// injected refs
var $log, $interval, wss, tps, tis, tfs, tss, tts, tspr;
var $log, $interval, wss, tps, tis, tfs, tss, tov, tspr;
// internal state
var handlerMap,
openListener,
heartbeatTimer;
var heartbeatPeriod = 5000; // 5 seconds
var heartbeatPeriod = 9000; // 9 seconds
// ==========================
......@@ -44,7 +44,7 @@
showDetails: tss,
showTraffic: tts,
showHighlights: tov,
addInstance: tis,
updateInstance: tis,
......@@ -90,10 +90,10 @@
.factory('TopoEventService',
['$log', '$interval', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
'TopoSelectService', 'TopoTrafficService', 'TopoSpriteService',
'TopoSelectService', 'TopoOverlayService', 'TopoSpriteService',
function (_$log_, _$interval_, _wss_,
_tps_, _tis_, _tfs_, _tss_, _tts_, _tspr_) {
_tps_, _tis_, _tfs_, _tss_, _tov_, _tspr_) {
$log = _$log_;
$interval = _$interval_;
wss = _wss_;
......@@ -101,7 +101,7 @@
tis = _tis_;
tfs = _tfs_;
tss = _tss_;
tts = _tts_;
tov = _tov_;
tspr = _tspr_;
createHandlerMap();
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, $timeout, fs, sus, is, ts, flash, wss,
var $log, $timeout, fs, sus, ts, flash, wss, tov,
tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg;
// configuration
......@@ -797,9 +797,10 @@
return true;
}
// ==========================
// function entry points for traffic module
// =============================================
// function entry points for overlay module
// TODO: find an automatic way of tracking via the "showHighlights" events
var allTrafficClasses = 'primary secondary optical animated ' +
'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' +
'port-traffic-Gbps-choked';
......@@ -845,7 +846,7 @@
};
}
function mkD3Api(uplink) {
function mkD3Api() {
return {
node: function () { return node; },
link: function () { return link; },
......@@ -859,7 +860,7 @@
};
}
function mkSelectApi(uplink) {
function mkSelectApi() {
return {
node: function () { return node; },
zoomingOrPanning: zoomingOrPanning,
......@@ -868,15 +869,20 @@
};
}
function mkTrafficApi(uplink) {
function mkTrafficApi() {
return {
hovered: tss.hovered,
somethingSelected: tss.somethingSelected,
selectOrder: tss.selectOrder
};
}
function mkOverlayApi() {
return {
clearLinkTrafficStyle: clearLinkTrafficStyle,
removeLinkLabels: removeLinkLabels,
updateLinks: updateLinks,
findLinkById: tms.findLinkById,
hovered: tss.hovered,
validateSelectionContext: tss.validateSelectionContext,
selectOrder: tss.selectOrder
findLinkById: tms.findLinkById
};
}
......@@ -904,7 +910,7 @@
};
}
function mkFilterApi(uplink) {
function mkFilterApi() {
return {
node: function () { return node; },
link: function () { return link; }
......@@ -925,11 +931,11 @@
.factory('TopoForceService',
['$log', '$timeout', 'FnService', 'SvgUtilService',
'ThemeService', 'FlashService', 'WebSocketService',
'TopoInstService', 'TopoModelService',
'TopoOverlayService', 'TopoInstService', 'TopoModelService',
'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_,
function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_,
_tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
$log = _$log_;
$timeout = _$timeout_;
......@@ -938,6 +944,7 @@
ts = _ts_;
flash = _flash_;
wss = _wss_;
tov = _tov_;
tis = _tis_;
tms = _tms_;
td3 = _td3_;
......@@ -966,12 +973,13 @@
$log.debug('initForce().. dim = ' + dim);
tov.setApi(mkOverlayApi(), tss);
tms.initModel(mkModelApi(uplink), dim);
td3.initD3(mkD3Api(uplink));
tss.initSelect(mkSelectApi(uplink));
tts.initTraffic(mkTrafficApi(uplink));
td3.initD3(mkD3Api());
tss.initSelect(mkSelectApi());
tts.initTraffic(mkTrafficApi());
tos.initOblique(mkObliqueApi(uplink, fltr));
fltr.initFilter(mkFilterApi(uplink));
fltr.initFilter(mkFilterApi());
tls.initLink(mkLinkApi(svg, uplink), td3);
settings = angular.extend({}, defaultSettings, opts);
......@@ -1016,6 +1024,7 @@
tss.destroySelect();
td3.destroyD3();
tms.destroyModel();
// note: no need to destroy overlay service
ts.removeListener(themeListener);
themeListener = null;
......
......@@ -30,7 +30,7 @@
var tos = 'TopoOverlayService: ';
// injected refs
var $log, fs, gs, wss, ns;
var $log, fs, gs, wss, ns, tss, tps, api;
// internal state
var overlays = {},
......@@ -80,6 +80,7 @@
function register(overlay) {
var r = 'register',
over = fs.isO(overlay),
kb = over ? fs.isO(overlay.keyBindings) : null,
id = over ? over.overlayId : '';
if (!id) {
......@@ -90,11 +91,26 @@
}
overlays[id] = overlay;
handleGlyphs(overlay);
if (kb) {
if (!fs.isA(kb._keyOrder)) {
warn(r, 'no _keyOrder array defined on keyBindings');
} else {
kb._keyOrder.forEach(function (k) {
if (k !== '-' && !kb[k]) {
warn(r, 'no "' + k + '" property defined on keyBindings');
}
});
}
}
$log.debug(tos + 'registered overlay: ' + id, overlay);
}
// TODO: remove this redundant code.......
// NOTE: unregister needs to be called if an app is ever
// deactivated/uninstalled via the applications view
/*
function unregister(overlay) {
var u = 'unregister',
over = fs.isO(overlay),
......@@ -108,21 +124,33 @@
}
delete overlays[id];
$log.debug(tos + 'unregistered overlay: ' + id);
// TODO: rebuild the toolbar overlay radio button set
}
*/
// returns the list of overlay identifiers
function list() {
return d3.map(overlays).keys();
}
function overlay(id) {
return overlays[id];
// add a radio button for each registered overlay
function augmentRbset(rset, switchFn) {
angular.forEach(overlays, function (ov) {
rset.push({
gid: ov._glyphId,
tooltip: (ov.tooltip || '(no tooltip)'),
cb: function () {
tbSelection(ov.overlayId, switchFn);
}
});
});
}
// an overlay was selected via toolbar radio button press from user
function tbSelection(id) {
function tbSelection(id, switchFn) {
var same = current && current.overlayId === id,
payload = {};
payload = {},
actions;
function doop(op) {
var oid = current.overlayId;
......@@ -133,70 +161,211 @@
if (!same) {
current && doop('deactivate');
current = overlay(id);
current = overlays[id];
current && doop('activate');
actions = current && fs.isO(current.keyBindings);
switchFn(id, actions);
wss.sendEvent('topoSelectOverlay', payload);
// TODO: refactor to emit "flush on overlay change" messages
// Ensure summary and details panels are updated immediately..
wss.sendEvent('requestSummary');
tss.updateDetail();
}
}
var coreButtonPath = {
showDeviceView: 'device',
showFlowView: 'flow',
showPortView: 'port',
showGroupView: 'group'
var coreButtons = {
showDeviceView: {
gid: 'switch',
tt: 'Show Device View',
path: 'device'
},
showFlowView: {
gid: 'flowTable',
tt: 'Show Flow View for this Device',
path: 'flow'
},
showPortView: {
gid: 'portTable',
tt: 'Show Port View for this Device',
path: 'port'
},
showGroupView: {
gid: 'groupTable',
tt: 'Show Group View for this Device',
path: 'group'
}
};
// retrieves a button definition from the current overlay and generates
// a button descriptor to be added to the panel, with the data baked in
function _getButtonDef(id, data) {
var btns = current && current.buttons,
b = btns && btns[id],
cb = fs.isF(b.cb),
f = cb ? function () { cb(data); } : function () {};
return b ? {
id: current.mkId(id),
gid: current.mkGid(b.gid),
tt: b.tt,
cb: f
} : null;
}
// install core buttons, and include any additional from the current overlay
function installButtons(buttons, addFn, data, devId) {
angular.forEach(buttons, function (btn) {
var path = coreButtonPath[btn.id],
_id,
_gid,
_cb,
action;
if (path) {
// core callback function
_id = btn.id;
_gid = btn.gid;
action = function () {
ns.navTo(path, { devId: devId });
};
} else if (current) {
_id = current.mkId(btn.id);
_gid = current.mkGid(btn.gid);
action = current.buttonActions[btn.id] || function () {};
function installButtons(buttons, data, devId) {
buttons.forEach(function (id) {
var btn = coreButtons[id],
gid = btn && btn.gid,
tt = btn && btn.tt,
path = btn && btn.path;
if (btn) {
tps.addAction({
id: 'core-' + id,
gid: gid,
tt: tt,
cb: function () { ns.navTo(path, {devId: devId }); }
});
} else if (btn = _getButtonDef(id, data)) {
tps.addAction(btn);
}
});
}
_cb = function () { action(data); };
function addDetailButton(id) {
var b = _getButtonDef(id);
if (b) {
tps.addAction({
id: current.mkId(id),
gid: current.mkGid(b.gid),
cb: b.cb,
tt: b.tt
});
}
}
// === -----------------------------------------------------
// Hooks for overlays
function _hook(x) {
var h = current && current.hooks;
return h && fs.isF(h[x]);
}
addFn({ id: _id, gid: _gid, cb: _cb, tt: btn.tt});
function escapeHook() {
var eh = _hook('escape');
return eh ? eh() : false;
}
function emptySelectHook() {
var cb = _hook('empty');
cb && cb();
}
function singleSelectHook(data) {
var cb = _hook('single');
cb && cb(data);
}
function multiSelectHook(selectOrder) {
var cb = _hook('multi');
cb && cb(selectOrder);
}
// === -----------------------------------------------------
// Event (from server) Handlers
function setApi(_api_, _tss_) {
api = _api_;
tss = _tss_;
}
// TODO: refactor this (currently using showTraffic data structure)
function showHighlights(data) {
/*
API to topoForce
clearLinkTrafficStyle()
removeLinkLabels()
updateLinks()
findLinkById( id )
*/
var paths = data.paths;
api.clearLinkTrafficStyle();
api.removeLinkLabels();
// Now highlight all links in the paths payload, and attach
// labels to them, if they are defined.
paths.forEach(function (p) {
var n = p.links.length,
i, ldata, lab, units, magnitude, portcls;
for (i=0; i<n; i++) {
ldata = api.findLinkById(p.links[i]);
lab = p.labels[i];
if (ldata && !ldata.el.empty()) {
ldata.el.classed(p.class, true);
ldata.label = lab;
if (fs.endsWith(lab, 'bps')) {
// inject additional styling for port-based traffic
units = lab.substring(lab.length-4);
portcls = 'port-traffic-' + units;
// for GBps
if (units.substring(0,1) === 'G') {
magnitude = fs.parseBitRate(lab);
if (magnitude >= 9) {
portcls += '-choked'
}
}
ldata.el.classed(portcls, true);
}
}
}
});
api.updateLinks();
}
// ========================================================================
angular.module('ovTopo')
.factory('TopoOverlayService',
['$log', 'FnService', 'GlyphService', 'WebSocketService', 'NavService',
'TopoPanelService',
function (_$log_, _fs_, _gs_, _wss_, _ns_) {
function (_$log_, _fs_, _gs_, _wss_, _ns_, _tps_) {
$log = _$log_;
fs = _fs_;
gs = _gs_;
wss = _wss_;
ns = _ns_;
tps = _tps_;
return {
register: register,
unregister: unregister,
//unregister: unregister,
setApi: setApi,
list: list,
overlay: overlay,
augmentRbset: augmentRbset,
mkGlyphId: mkGlyphId,
tbSelection: tbSelection,
installButtons: installButtons
installButtons: installButtons,
addDetailButton: addDetailButton,
hooks: {
escape: escapeHook,
emptySelect: emptySelectHook,
singleSelect: singleSelectHook,
multiSelect: multiSelectHook
},
showHighlights: showHighlights
}
}]);
......
......@@ -40,12 +40,6 @@
selectOrder = [], // the order in which we made selections
consumeClick = false; // used to coordinate with SVG click handler
// constants
var devPath = 'device',
flowPath = 'flow',
portPath ='port',
groupPath = 'group';
// ==========================
function nSel() {
......@@ -157,8 +151,7 @@
// === -----------------------------------------------------
function requestDetails() {
var data = getSel(0).obj;
function requestDetails(data) {
wss.sendEvent('requestDetails', {
id: data.id,
class: data.class
......@@ -179,91 +172,62 @@
}
function emptySelect() {
tts.cancelTraffic();
tov.hooks.emptySelect();
tps.displayNothing();
}
function singleSelect() {
// NOTE: detail is shown from 'showDetails' event callback
requestDetails();
tts.cancelTraffic();
tts.requestTrafficForMode();
var data = getSel(0).obj;
requestDetails(data);
// NOTE: detail panel is shown as a response to receiving
// a 'showDetails' event from the server. See 'showDetails'
// callback function below...
}
function multiSelect() {
// display the selected nodes in the detail panel
tps.displayMulti(selectOrder);
addHostSelectionActions();
tov.hooks.multiSelect(selectOrder);
tps.displaySomething();
}
// always add the 'show traffic' action
tps.addAction({
id: '-mult-rel-traf-btn',
gid: 'allTraffic',
cb: tts.showRelatedIntentsAction,
tt: 'Show Related Traffic'
});
// add other actions, based on what is selected...
if (nSel() === 2 && allSelectionsClass('host')) {
function addHostSelectionActions() {
if (allSelectionsClass('host')) {
if (nSel() === 2) {
tps.addAction({
id: 'host-flow-btn',
gid: 'endstation',
cb: tts.addHostIntentAction,
cb: tts.addHostIntent,
tt: 'Create Host-to-Host Flow'
});
} else if (nSel() >= 2 && allSelectionsClass('host')) {
} else if (nSel() >= 2) {
tps.addAction({
id: 'mult-src-flow-btn',
gid: 'flows',
cb: tts.addMultiSourceIntentAction,
cb: tts.addMultiSourceIntent,
tt: 'Create Multi-Source Flow'
});
}
tts.cancelTraffic();
tts.requestTrafficForMode();
tps.displaySomething();
}
}
// === -----------------------------------------------------
// Event Handlers
// display the data for the single selected node
function showDetails(data) {
var buttons = fs.isA(data.buttons) || [];
// display the data for the single selected node
tps.displaySingle(data);
tov.installButtons(buttons, tps.addAction, data, data.props['URI']);
// TODO: MOVE traffic buttons to the traffic overlay
// always add the 'show traffic' action
tps.addAction({
id: '-sin-rel-traf-btn',
gid: 'intentTraffic',
cb: tts.showRelatedIntentsAction,
tt: 'Show Related Traffic'
});
// add other actions, based on what is selected...
if (data.type === 'switch') {
tps.addAction({
id: 'sin-dev-flows-btn',
gid: 'flows',
cb: tts.showDeviceLinkFlowsAction,
tt: 'Show Device Flows'
});
}
tov.installButtons(buttons, data, data.props['URI']);
tov.hooks.singleSelect(data);
tps.displaySomething();
}
function validateSelectionContext() {
if (!hovered && !nSel()) {
tts.cancelTraffic();
return false;
}
return true;
// returns true if we are hovering over a node, or any nodes are selected
function somethingSelected() {
return hovered || nSel();
}
function clickConsumed(x) {
......@@ -306,10 +270,11 @@
selectObject: selectObject,
deselectObject: deselectObject,
deselectAll: deselectAll,
updateDetail: updateDetail,
hovered: function () { return hovered; },
selectOrder: function () { return selectOrder; },
validateSelectionContext: validateSelectionContext,
somethingSelected: somethingSelected,
clickConsumed: clickConsumed
};
......
......@@ -25,19 +25,25 @@
// injected references
var $log, fs, tbs, ps, tov, api;
// API:
// getActionEntry
// setUpKeys
// internal state
var toolbar, keyData, cachedState;
var toolbar, keyData, cachedState, thirdRow;
// constants
var name = 'topo-tbar',
cooktag = 'topo_prefs';
cooktag = 'topo_prefs',
soa = 'switchOverlayActions: ',
selOver = 'Select overlay here &#x21e7;';
// key to button mapping data
var k2b = {
O: { id: 'summary-tog', gid: 'summary', isel: true},
I: { id: 'instance-tog', gid: 'uiAttached', isel: true },
D: { id: 'details-tog', gid: 'details', isel: true },
H: { id: 'hosts-tog', gid: 'endstation', isel: false },
M: { id: 'offline-tog', gid: 'switch', isel: true },
P: { id: 'ports-tog', gid: 'ports', isel: true },
......@@ -50,16 +56,16 @@
L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
R: { id: 'resetZoom-btn', gid: 'resetZoom' },
E: { id: 'eqMaster-btn', gid: 'eqMaster' },
V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
A: { id: 'allTraffic-btn', gid: 'allTraffic' },
F: { id: 'flows-btn', gid: 'flows' }
E: { id: 'eqMaster-btn', gid: 'eqMaster' }
};
var prohibited = [
'T', 'backSlash', 'slash',
'X' // needed until we re-instate X above.
];
prohibited = prohibited.concat(d3.map(k2b).keys());
// initial toggle state: default settings and tag to key mapping
var defaultPrefsState = {
summary: 1,
......@@ -112,6 +118,7 @@
}
function initKeyData() {
// TODO: use angular forEach instead of d3.map
keyData = d3.map(k2b);
keyData.forEach(function(key, value) {
var data = api.getActionEntry(key);
......@@ -124,6 +131,7 @@
var v = keyData.get(key);
v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt);
}
function addToggle(key, suppressIfMobile) {
var v = keyData.get(key);
if (suppressIfMobile && fs.isMobile()) { return; }
......@@ -158,36 +166,60 @@
// generate radio button set for overlays; start with 'none'
var rset = [{
gid: 'unknown',
gid: 'topo',
tooltip: 'No Overlay',
cb: function () {
tov.tbSelection(null);
tov.tbSelection(null, switchOverlayActions);
}
}];
tov.list().forEach(function (key) {
var ov = tov.overlay(key);
rset.push({
gid: ov._glyphId,
tooltip: (ov.tooltip || '(no tooltip)'),
cb: function () {
tov.tbSelection(ov.overlayId);
tov.augmentRbset(rset, switchOverlayActions);
toolbar.addRadioSet('topo-overlays', rset);
}
// invoked by overlay service to switch out old buttons and switch in new
function switchOverlayActions(oid, keyBindings) {
var prohibits = [],
kb = fs.isO(keyBindings) || {},
order = fs.isA(kb._keyOrder) || [];
if (keyBindings && !keyBindings._keyOrder) {
$log.warn(soa + 'no _keyOrder property defined');
} else {
// sanity removal of reserved property names
['esc', '_keyListener', '_helpFormat'].forEach(function (k) {
fs.removeFromArray(k, order);
});
});
}
toolbar.addRadioSet('topo-overlays', rset);
thirdRow.clear();
if (!order.length) {
thirdRow.setText(selOver);
thirdRow.classed('right', true);
api.setUpKeys(); // clear previous overlay key bindings
} else {
thirdRow.classed('right', false);
angular.forEach(order, function (key) {
var value, bid, gid, tt;
if (prohibited.indexOf(key) > -1) {
prohibits.push(key);
} else {
value = keyBindings[key];
bid = oid + '-' + key;
gid = tov.mkGlyphId(oid, value.gid);
tt = value.tt + ' (' + key + ')';
thirdRow.addButton(bid, gid, value.cb, tt);
}
});
api.setUpKeys(keyBindings); // add overlay key bindings
}
// TODO: 3rd row needs to be swapped in/out based on selected overlay
// NOTE: This particular row of buttons is for the traffic overlay
function addThirdRow() {
addButton('V');
addButton('leftArrow');
addButton('rightArrow');
addButton('W');
addButton('A');
addButton('F');
if (prohibits.length) {
$log.warn(soa + 'Prohibited key bindings ignored:', prohibits);
}
}
function createToolbar() {
......@@ -197,8 +229,9 @@
toolbar.addRow();
addSecondRow();
addOverlays();
toolbar.addRow();
addThirdRow();
thirdRow = toolbar.addRow();
thirdRow.setText(selOver);
thirdRow.classed('right', true);
if (cachedState.toolbar) {
toolbar.show();
......
......@@ -23,85 +23,44 @@
'use strict';
// injected refs
var $log, fs, flash, wss;
var $log, fs, flash, wss, api;
// api to topoForce
var api;
/*
clearLinkTrafficStyle()
removeLinkLabels()
updateLinks()
findLinkById( id )
API to topoForce
hovered()
validateSelectionContext()
somethingSelected()
selectOrder()
*/
// constants
var hoverModeNone = 0,
hoverModeAll = 1,
hoverModeFlows = 2,
hoverModeIntents = 3;
// internal state
var hoverMode = hoverModeNone;
var trafficMode = null,
hoverMode = null;
// === -----------------------------------------------------
// Event Handlers
function showTraffic(data) {
var paths = data.paths;
api.clearLinkTrafficStyle();
api.removeLinkLabels();
// Now highlight all links in the paths payload, and attach
// labels to them, if they are defined.
paths.forEach(function (p) {
var n = p.links.length,
i, ldata, lab, units, magnitude, portcls;
for (i=0; i<n; i++) {
ldata = api.findLinkById(p.links[i]);
lab = p.labels[i];
if (ldata && !ldata.el.empty()) {
ldata.el.classed(p.class, true);
ldata.label = lab;
if (fs.endsWith(lab, 'bps')) {
// inject additional styling for port-based traffic
units = lab.substring(lab.length-4);
portcls = 'port-traffic-' + units;
// for GBps
if (units.substring(0,1) === 'G') {
magnitude = fs.parseBitRate(lab);
if (magnitude >= 9) {
portcls += '-choked'
}
}
ldata.el.classed(portcls, true);
}
}
}
});
// Helper functions
api.updateLinks();
// invoked in response to change in selection and/or mouseover/out:
function requestTrafficForMode() {
if (hoverMode === 'flows') {
requestDeviceLinkFlows();
} else if (hoverMode === 'intents') {
requestRelatedIntents();
} else {
cancelTraffic();
}
}
// === -----------------------------------------------------
// Helper functions
function requestDeviceLinkFlows() {
// generates payload based on current hover-state
var hov = api.hovered();
function hoverValid() {
return hoverMode === hoverModeFlows &&
return hoverMode === 'flows' &&
hov && (hov.class === 'device');
}
if (api.validateSelectionContext()) {
if (api.somethingSelected()) {
wss.sendEvent('requestDeviceLinkFlows', {
ids: api.selectOrder(),
hover: hoverValid() ? hov.id : ''
......@@ -110,14 +69,15 @@
}
function requestRelatedIntents() {
// generates payload based on current hover-state
var hov = api.hovered();
function hoverValid() {
return hoverMode === hoverModeIntents &&
return hoverMode === 'intents' &&
hov && (hov.class === 'host' || hov.class === 'device');
}
if (api.validateSelectionContext()) {
if (api.somethingSelected()) {
wss.sendEvent('requestRelatedIntents', {
ids: api.selectOrder(),
hover: hoverValid() ? hov.id : ''
......@@ -126,71 +86,75 @@
}
// === -----------------------------------------------------
// Traffic requests
// === -------------------------------------------------------------
// Traffic requests invoked from keystrokes or toolbar buttons...
function cancelTraffic() {
if (!trafficMode) {
return false;
}
trafficMode = hoverMode = null;
wss.sendEvent('cancelTraffic');
flash.flash('Traffic monitoring canceled');
return true;
}
// invoked in response to change in selection and/or mouseover/out:
function requestTrafficForMode() {
if (hoverMode === hoverModeFlows) {
requestDeviceLinkFlows();
} else if (hoverMode === hoverModeIntents) {
requestRelatedIntents();
function showAllFlowTraffic() {
trafficMode = 'allFlow';
hoverMode = 'all';
wss.sendEvent('requestAllFlowTraffic');
flash.flash('All Flow Traffic');
}
function showAllPortTraffic() {
trafficMode = 'allPort';
hoverMode = 'all';
wss.sendEvent('requestAllPortTraffic');
flash.flash('All Port Traffic');
}
// === -----------------------------
// keystroke commands
function showDeviceLinkFlows () {
trafficMode = hoverMode = 'flows';
requestDeviceLinkFlows();
flash.flash('Device Flows');
}
// keystroke-right-arrow (see topo.js)
function showNextIntentAction() {
hoverMode = hoverModeNone;
wss.sendEvent('requestNextRelatedIntent');
flash.flash('Next related intent');
function showRelatedIntents () {
trafficMode = hoverMode = 'intents';
requestRelatedIntents();
flash.flash('Related Paths');
}
// keystroke-left-arrow (see topo.js)
function showPrevIntentAction() {
hoverMode = hoverModeNone;
function showPrevIntent() {
if (trafficMode === 'intents') {
hoverMode = null;
wss.sendEvent('requestPrevRelatedIntent');
flash.flash('Previous related intent');
}
// keystroke-W (see topo.js)
function showSelectedIntentTrafficAction() {
hoverMode = hoverModeNone;
wss.sendEvent('requestSelectedIntentTraffic');
flash.flash('Traffic on Selected Path');
}
// keystroke-A (see topo.js)
function showAllFlowTrafficAction() {
hoverMode = hoverModeAll;
wss.sendEvent('requestAllFlowTraffic');
flash.flash('All Flow Traffic');
function showNextIntent() {
if (trafficMode === 'intents') {
hoverMode = null;
wss.sendEvent('requestNextRelatedIntent');
flash.flash('Next related intent');
}
}
// keystroke-A (see topo.js)
function showAllPortTrafficAction() {
hoverMode = hoverModeAll;
wss.sendEvent('requestAllPortTraffic');
flash.flash('All Port Traffic');
function showSelectedIntentTraffic() {
if (trafficMode === 'intents') {
hoverMode = null;
wss.sendEvent('requestSelectedIntentTraffic');
flash.flash('Traffic on Selected Path');
}
}
// === -----------------------------
// action buttons on detail panel
// also, keystroke-V (see topo.js)
function showRelatedIntentsAction () {
hoverMode = hoverModeIntents;
requestRelatedIntents();
flash.flash('Related Paths');
}
// === ------------------------------------------------------
// action buttons on detail panel (multiple selection)
function addHostIntentAction () {
function addHostIntent () {
var so = api.selectOrder();
wss.sendEvent('addHostIntent', {
one: so[0],
......@@ -200,7 +164,7 @@
flash.flash('Host-to-Host flow added');
}
function addMultiSourceIntentAction () {
function addMultiSourceIntent () {
var so = api.selectOrder();
wss.sendEvent('addMultiSourceIntent', {
src: so.slice(0, so.length - 1),
......@@ -210,12 +174,6 @@
flash.flash('Multi-Source flow added');
}
// also, keystroke-F (see topo.js)
function showDeviceLinkFlowsAction () {
hoverMode = hoverModeFlows;
requestDeviceLinkFlows();
flash.flash('Device Flows');
}
// === -----------------------------------------------------
......@@ -231,29 +189,26 @@
flash = _flash_;
wss = _wss_;
function initTraffic(_api_) {
api = _api_;
}
function destroyTraffic() { }
return {
initTraffic: initTraffic,
destroyTraffic: destroyTraffic,
showTraffic: showTraffic,
initTraffic: function (_api_) { api = _api_; },
destroyTraffic: function () { },
// invoked from toolbar overlay buttons or keystrokes
cancelTraffic: cancelTraffic,
showAllFlowTraffic: showAllFlowTraffic,
showAllPortTraffic: showAllPortTraffic,
showDeviceLinkFlows: showDeviceLinkFlows,
showRelatedIntents: showRelatedIntents,
showPrevIntent: showPrevIntent,
showNextIntent: showNextIntent,
showSelectedIntentTraffic: showSelectedIntentTraffic,
// invoked from mouseover/mouseout and selection change
requestTrafficForMode: requestTrafficForMode,
showRelatedIntentsAction: showRelatedIntentsAction,
addHostIntentAction: addHostIntentAction,
addMultiSourceIntentAction: addMultiSourceIntentAction,
showDeviceLinkFlowsAction: showDeviceLinkFlowsAction,
showNextIntentAction: showNextIntentAction,
showPrevIntentAction: showPrevIntentAction,
showSelectedIntentTrafficAction: showSelectedIntentTrafficAction,
showAllFlowTrafficAction: showAllFlowTrafficAction,
showAllPortTrafficAction: showAllPortTrafficAction
// invoked from buttons on detail (multi-select) panel
addHostIntent: addHostIntent,
addMultiSourceIntent: addMultiSourceIntent
};
}]);
}());
......
......@@ -16,7 +16,7 @@
*/
/*
ONOS GUI -- Topology Traffic Module.
ONOS GUI -- Topology Traffic Overlay Module.
Defines behavior for viewing different traffic modes.
Installed as a Topology Overlay.
*/
......@@ -24,7 +24,13 @@
'use strict';
// injected refs
var $log;
var $log, tov, tts;
// NOTE: no internal state here -- see TopoTrafficService for that
// NOTE: providing button disabling requires too big a refactoring of
// the button factory etc. Will have to be done another time.
// traffic overlay definition
var overlay = {
......@@ -32,26 +38,112 @@
glyphId: 'allTraffic',
tooltip: 'Traffic Overlay',
activate: activateTraffic,
deactivate: deactivateTraffic
};
// NOTE: Traffic glyphs already installed as part of the base ONOS set.
// === implementation of overlay API (essentially callbacks)
function activateTraffic() {
$log.debug("Topology traffic overlay ACTIVATED");
}
activate: function () {
$log.debug("Traffic overlay ACTIVATED");
},
deactivate: function () {
tts.cancelTraffic();
$log.debug("Traffic overlay DEACTIVATED");
},
// detail panel button definitions
// (keys match button identifiers, also defined in TrafficOverlay.java)
buttons: {
showDeviceFlows: {
gid: 'flows',
tt: 'Show Device Flows',
cb: function (data) { tts.showDeviceLinkFlows(); }
},
function deactivateTraffic() {
$log.debug("Topology traffic overlay DEACTIVATED");
showRelatedTraffic: {
gid: 'relatedIntents',
tt: 'Show Related Traffic',
cb: function (data) { tts.showRelatedIntents(); }
}
},
// key bindings for traffic overlay toolbar buttons
// NOTE: fully qual. button ID is derived from overlay-id and key-name
keyBindings: {
0: {
cb: function () { tts.cancelTraffic(); },
tt: 'Cancel traffic monitoring',
gid: 'xMark'
},
A: {
cb: function () { tts.showAllFlowTraffic(); },
tt: 'Monitor all traffic using flow stats',
gid: 'allTraffic'
},
Q: {
cb: function () { tts.showAllPortTraffic(); },
tt: 'Monitor all traffic using port stats',
gid: 'allTraffic'
},
F: {
cb: function () { tts.showDeviceLinkFlows(); },
tt: 'Show device link flows',
gid: 'flows'
},
V: {
cb: function () { tts.showRelatedIntents(); },
tt: 'Show all related intents',
gid: 'relatedIntents'
},
leftArrow: {
cb: function () { tts.showPrevIntent(); },
tt: 'Show previous related intent',
gid: 'prevIntent'
},
rightArrow: {
cb: function () { tts.showNextIntent(); },
tt: 'Show next related intent',
gid: 'nextIntent'
},
W: {
cb: function () { tts.showSelectedIntentTraffic(); },
tt: 'Monitor traffic of selected intent',
gid: 'intentTraffic'
},
_keyOrder: [
'0', 'A', 'Q', 'F', 'V', 'leftArrow', 'rightArrow', 'W'
]
},
hooks: {
// hook for handling escape key
escape: function () {
// Must return true to consume ESC, false otherwise.
return tts.cancelTraffic();
},
// hooks for when the selection changes...
empty: function () {
tts.cancelTraffic();
},
single: function (data) {
tts.requestTrafficForMode();
},
multi: function (selectOrder) {
tts.requestTrafficForMode();
tov.addDetailButton('showRelatedTraffic');
}
}
};
// invoke code to register with the overlay service
angular.module('ovTopo')
.run(['$log', 'TopoOverlayService',
.run(['$log', 'TopoOverlayService', 'TopoTrafficService',
function (_$log_, tov) {
function (_$log_, _tov_, _tts_) {
$log = _$log_;
tov = _tov_;
tts = _tts_;
tov.register(overlay);
}]);
......