Simon Hunt
Committed by Gerrit Code Review

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

Change-Id: I11820a9ff8f446c0d10a0311cee5ce448c15f402
...@@ -17,29 +17,22 @@ ...@@ -17,29 +17,22 @@
17 17
18 package org.onosproject.ui.topo; 18 package org.onosproject.ui.topo;
19 19
20 +import com.google.common.base.MoreObjects;
21 +
20 /** 22 /**
21 - * Designates a descriptor for a button on the topology view panels. 23 + * Designates the identity of a button on the topology view panels.
22 */ 24 */
23 -public class ButtonDescriptor { 25 +public class ButtonId {
24 26
25 private final String id; 27 private final String id;
26 - private final String glyphId;
27 - private final String tooltip;
28 28
29 /** 29 /**
30 - * Creates a button descriptor with the given identifier, glyph ID, and 30 + * Creates a button ID with the given identifier.
31 - * tooltip text. To reference a custom glyph defined in the overlay itself,
32 - * prefix its ID with an asterisk, (e.g. {@code "*myGlyph"}). Alternatively,
33 - * use one of the {@link TopoConstants.Glyphs predefined constant}.
34 * 31 *
35 * @param id identifier for the button 32 * @param id identifier for the button
36 - * @param glyphId identifier for the glyph
37 - * @param tooltip tooltip text
38 */ 33 */
39 - public ButtonDescriptor(String id, String glyphId, String tooltip) { 34 + public ButtonId(String id) {
40 this.id = id; 35 this.id = id;
41 - this.glyphId = glyphId;
42 - this.tooltip = tooltip;
43 } 36 }
44 37
45 /** 38 /**
...@@ -51,22 +44,10 @@ public class ButtonDescriptor { ...@@ -51,22 +44,10 @@ public class ButtonDescriptor {
51 return id; 44 return id;
52 } 45 }
53 46
54 - /** 47 + @Override
55 - * Returns the glyph identifier for this button. 48 + public String toString() {
56 - * 49 + return MoreObjects.toStringHelper(getClass())
57 - * @return glyph identifier 50 + .add("id", id()).toString();
58 - */
59 - public String glyphId() {
60 - return glyphId;
61 - }
62 -
63 - /**
64 - * Returns the tooltip text for this button.
65 - *
66 - * @return tooltip text
67 - */
68 - public String tooltip() {
69 - return tooltip;
70 } 51 }
71 52
72 @Override 53 @Override
...@@ -78,9 +59,8 @@ public class ButtonDescriptor { ...@@ -78,9 +59,8 @@ public class ButtonDescriptor {
78 return false; 59 return false;
79 } 60 }
80 61
81 - ButtonDescriptor that = (ButtonDescriptor) o; 62 + ButtonId that = (ButtonId) o;
82 return id.equals(that.id); 63 return id.equals(that.id);
83 -
84 } 64 }
85 65
86 @Override 66 @Override
......
...@@ -35,7 +35,7 @@ public class PropertyPanel { ...@@ -35,7 +35,7 @@ public class PropertyPanel {
35 private String typeId; 35 private String typeId;
36 private String id; 36 private String id;
37 private List<Prop> properties = new ArrayList<>(); 37 private List<Prop> properties = new ArrayList<>();
38 - private List<ButtonDescriptor> buttons = new ArrayList<>(); 38 + private List<ButtonId> buttons = new ArrayList<>();
39 39
40 /** 40 /**
41 * Constructs a property panel model with the given title and 41 * Constructs a property panel model with the given title and
...@@ -181,7 +181,7 @@ public class PropertyPanel { ...@@ -181,7 +181,7 @@ public class PropertyPanel {
181 * @return the button list 181 * @return the button list
182 */ 182 */
183 // TODO: consider protecting this? 183 // TODO: consider protecting this?
184 - public List<ButtonDescriptor> buttons() { 184 + public List<ButtonId> buttons() {
185 return buttons; 185 return buttons;
186 } 186 }
187 187
...@@ -243,7 +243,7 @@ public class PropertyPanel { ...@@ -243,7 +243,7 @@ public class PropertyPanel {
243 * @param button button descriptor 243 * @param button button descriptor
244 * @return self, for chaining 244 * @return self, for chaining
245 */ 245 */
246 - public PropertyPanel addButton(ButtonDescriptor button) { 246 + public PropertyPanel addButton(ButtonId button) {
247 buttons.add(button); 247 buttons.add(button);
248 return this; 248 return this;
249 } 249 }
...@@ -254,10 +254,10 @@ public class PropertyPanel { ...@@ -254,10 +254,10 @@ public class PropertyPanel {
254 * @param descriptors descriptors to remove 254 * @param descriptors descriptors to remove
255 * @return self, for chaining 255 * @return self, for chaining
256 */ 256 */
257 - public PropertyPanel removeButtons(ButtonDescriptor... descriptors) { 257 + public PropertyPanel removeButtons(ButtonId... descriptors) {
258 - Set<ButtonDescriptor> forRemoval = Sets.newHashSet(descriptors); 258 + Set<ButtonId> forRemoval = Sets.newHashSet(descriptors);
259 - List<ButtonDescriptor> toKeep = new ArrayList<>(); 259 + List<ButtonId> toKeep = new ArrayList<>();
260 - for (ButtonDescriptor bd: buttons) { 260 + for (ButtonId bd: buttons) {
261 if (!forRemoval.contains(bd)) { 261 if (!forRemoval.contains(bd)) {
262 toKeep.add(bd); 262 toKeep.add(bd);
263 } 263 }
......
...@@ -108,30 +108,22 @@ public final class TopoConstants { ...@@ -108,30 +108,22 @@ public final class TopoConstants {
108 public static final String VLAN = "VLAN"; 108 public static final String VLAN = "VLAN";
109 } 109 }
110 110
111 - private static final class CoreButton extends ButtonDescriptor {
112 - private CoreButton(String tag, String glyphId, boolean extra) {
113 - super("show" + tag + "View",
114 - glyphId,
115 - "Show " + tag + " View" + (extra ? " for this Device" : ""));
116 - }
117 - }
118 -
119 /** 111 /**
120 - * Defines constants for core buttons that appear on the topology 112 + * Defines identities of core buttons that appear on the topology
121 * details panel. 113 * details panel.
122 */ 114 */
123 public static final class CoreButtons { 115 public static final class CoreButtons {
124 - public static final ButtonDescriptor SHOW_DEVICE_VIEW = 116 + public static final ButtonId SHOW_DEVICE_VIEW =
125 - new CoreButton("Device", Glyphs.SWITCH, false); 117 + new ButtonId("showDeviceView");
126 118
127 - public static final ButtonDescriptor SHOW_FLOW_VIEW = 119 + public static final ButtonId SHOW_FLOW_VIEW =
128 - new CoreButton("Flow", Glyphs.FLOW_TABLE, true); 120 + new ButtonId("showFlowView");
129 121
130 - public static final ButtonDescriptor SHOW_PORT_VIEW = 122 + public static final ButtonId SHOW_PORT_VIEW =
131 - new CoreButton("Port", Glyphs.PORT_TABLE, true); 123 + new ButtonId("showPortView");
132 124
133 - public static final ButtonDescriptor SHOW_GROUP_VIEW = 125 + public static final ButtonId SHOW_GROUP_VIEW =
134 - new CoreButton("Group", Glyphs.GROUP_TABLE, true); 126 + new ButtonId("showGroupView");
135 } 127 }
136 128
137 } 129 }
......
...@@ -19,27 +19,38 @@ package org.onosproject.ui.topo; ...@@ -19,27 +19,38 @@ package org.onosproject.ui.topo;
19 19
20 import org.junit.Test; 20 import org.junit.Test;
21 21
22 -import static org.junit.Assert.assertEquals; 22 +import static org.junit.Assert.assertFalse;
23 +import static org.junit.Assert.assertTrue;
23 24
24 /** 25 /**
25 - * Unit tests for {@link ButtonDescriptor}. 26 + * Unit tests for {@link ButtonId}.
26 */ 27 */
27 -public class ButtonDescriptorTest { 28 +public class ButtonIdTest {
28 29
29 - private static final String ID = "my-id"; 30 + private static final String ID1 = "id-1";
30 - private static final String GID = "my-glyphId"; 31 + private static final String ID2 = "id-2";
31 - private static final String TT = "my-tewltyp";
32 32
33 - private ButtonDescriptor bd; 33 + private ButtonId b1, b2;
34 34
35 35
36 @Test 36 @Test
37 public void basic() { 37 public void basic() {
38 - bd = new ButtonDescriptor(ID, GID, TT); 38 + b1 = new ButtonId(ID1);
39 + }
39 40
40 - assertEquals("bad id", ID, bd.id()); 41 + @Test
41 - assertEquals("bad gid", GID, bd.glyphId()); 42 + public void same() {
42 - assertEquals("bad tt", TT, bd.tooltip()); 43 + b1 = new ButtonId(ID1);
44 + b2 = new ButtonId(ID1);
45 + assertFalse("same ref?", b1 == b2);
46 + assertTrue("not equal?", b1.equals(b2));
43 } 47 }
44 48
49 + @Test
50 + public void notSame() {
51 + b1 = new ButtonId(ID1);
52 + b2 = new ButtonId(ID2);
53 + assertFalse("same ref?", b1 == b2);
54 + assertFalse("equal?", b1.equals(b2));
55 + }
45 } 56 }
......
...@@ -47,14 +47,6 @@ public class PropertyPanelTest { ...@@ -47,14 +47,6 @@ public class PropertyPanelTest {
47 private static final String VALUE_B = "Bee"; 47 private static final String VALUE_B = "Bee";
48 private static final String VALUE_C = "Sea"; 48 private static final String VALUE_C = "Sea";
49 private static final String VALUE_Z = "Zed"; 49 private static final String VALUE_Z = "Zed";
50 - private static final String GID_A = "gid-A";
51 - private static final String GID_B = "gid-B";
52 - private static final String GID_C = "gid-C";
53 - private static final String GID_Z = "gid-Z";
54 - private static final String TT_A = "toolTip-A";
55 - private static final String TT_B = "toolTip-B";
56 - private static final String TT_C = "toolTip-C";
57 - private static final String TT_Z = "toolTip-Z";
58 50
59 private static final Map<String, Prop> PROP_MAP = new HashMap<>(); 51 private static final Map<String, Prop> PROP_MAP = new HashMap<>();
60 52
...@@ -211,17 +203,13 @@ public class PropertyPanelTest { ...@@ -211,17 +203,13 @@ public class PropertyPanelTest {
211 validateProp(KEY_B, ">byyy<"); 203 validateProp(KEY_B, ">byyy<");
212 } 204 }
213 205
214 - private static final ButtonDescriptor BD_A = 206 + private static final ButtonId BD_A = new ButtonId(KEY_A);
215 - new ButtonDescriptor(KEY_A, GID_A, TT_A); 207 + private static final ButtonId BD_B = new ButtonId(KEY_B);
216 - private static final ButtonDescriptor BD_B = 208 + private static final ButtonId BD_C = new ButtonId(KEY_C);
217 - new ButtonDescriptor(KEY_B, GID_B, TT_B); 209 + private static final ButtonId BD_Z = new ButtonId(KEY_Z);
218 - private static final ButtonDescriptor BD_C =
219 - new ButtonDescriptor(KEY_C, GID_C, TT_C);
220 - private static final ButtonDescriptor BD_Z =
221 - new ButtonDescriptor(KEY_Z, GID_Z, TT_Z);
222 210
223 private void verifyButtons(String... keys) { 211 private void verifyButtons(String... keys) {
224 - Iterator<ButtonDescriptor> iter = pp.buttons().iterator(); 212 + Iterator<ButtonId> iter = pp.buttons().iterator();
225 for (String k: keys) { 213 for (String k: keys) {
226 assertEquals("wrong button", k, iter.next().id()); 214 assertEquals("wrong button", k, iter.next().id());
227 } 215 }
......
...@@ -112,7 +112,10 @@ public abstract class AbstractAccumulator<T> implements Accumulator<T> { ...@@ -112,7 +112,10 @@ public abstract class AbstractAccumulator<T> implements Accumulator<T> {
112 if (isReady()) { 112 if (isReady()) {
113 try { 113 try {
114 maxTask = cancelIfActive(maxTask); 114 maxTask = cancelIfActive(maxTask);
115 - processItems(finalizeCurrentBatch()); 115 + List<T> items = finalizeCurrentBatch();
116 + if (!items.isEmpty()) {
117 + processItems(items);
118 + }
116 } catch (Exception e) { 119 } catch (Exception e) {
117 log.warn("Unable to process batch due to {}", e); 120 log.warn("Unable to process batch due to {}", e);
118 } 121 }
......
...@@ -214,6 +214,8 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { ...@@ -214,6 +214,8 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
214 new UpdateMeta(), 214 new UpdateMeta(),
215 new EqMasters(), 215 new EqMasters(),
216 216
217 + // TODO: implement "showHighlights" event (replaces "showTraffic")
218 +
217 // TODO: migrate traffic related to separate app 219 // TODO: migrate traffic related to separate app
218 new AddHostIntent(), 220 new AddHostIntent(),
219 new AddMultiSourceIntent(), 221 new AddMultiSourceIntent(),
...@@ -965,6 +967,14 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { ...@@ -965,6 +967,14 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
965 967
966 @Override 968 @Override
967 public void processItems(List<Event> items) { 969 public void processItems(List<Event> items) {
970 + // Start-of-Debugging
971 + long now = System.currentTimeMillis();
972 + String me = this.toString();
973 + String miniMe = me.replaceAll("^.*@", "me@");
974 + log.debug("Time: {}; this: {}, processing items ({} events)",
975 + now, miniMe, items.size());
976 + // End-of-Debugging
977 +
968 try { 978 try {
969 if (summaryRunning) { 979 if (summaryRunning) {
970 msgSender.execute(() -> requestSummary(0)); 980 msgSender.execute(() -> requestSummary(0));
......
...@@ -72,7 +72,7 @@ import org.onosproject.net.topology.TopologyService; ...@@ -72,7 +72,7 @@ import org.onosproject.net.topology.TopologyService;
72 import org.onosproject.ui.JsonUtils; 72 import org.onosproject.ui.JsonUtils;
73 import org.onosproject.ui.UiConnection; 73 import org.onosproject.ui.UiConnection;
74 import org.onosproject.ui.UiMessageHandler; 74 import org.onosproject.ui.UiMessageHandler;
75 -import org.onosproject.ui.topo.ButtonDescriptor; 75 +import org.onosproject.ui.topo.ButtonId;
76 import org.onosproject.ui.topo.PropertyPanel; 76 import org.onosproject.ui.topo.PropertyPanel;
77 import org.slf4j.Logger; 77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory; 78 import org.slf4j.LoggerFactory;
...@@ -123,6 +123,8 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -123,6 +123,8 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
123 new ProviderId("core", "org.onosproject.core", true); 123 new ProviderId("core", "org.onosproject.core", true);
124 private static final String COMPACT = "%s/%s-%s/%s"; 124 private static final String COMPACT = "%s/%s-%s/%s";
125 125
126 + private static final String SHOW_HIGHLIGHTS = "showHighlights";
127 +
126 private static final double KILO = 1024; 128 private static final double KILO = 1024;
127 private static final double MEGA = 1024 * KILO; 129 private static final double MEGA = 1024 * KILO;
128 private static final double GIGA = 1024 * MEGA; 130 private static final double GIGA = 1024 * MEGA;
...@@ -642,7 +644,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -642,7 +644,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
642 labelsN.add(""); 644 labelsN.add("");
643 } 645 }
644 } 646 }
645 - return JsonUtils.envelope("showTraffic", 0, payload); 647 + return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
646 } 648 }
647 649
648 private Load getLinkLoad(Link link) { 650 private Load getLinkLoad(Link link) {
...@@ -679,7 +681,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -679,7 +681,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
679 addLinkFlows(link, paths, counts.get(link)); 681 addLinkFlows(link, paths, counts.get(link));
680 } 682 }
681 } 683 }
682 - return JsonUtils.envelope("showTraffic", 0, payload); 684 + return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
683 } 685 }
684 686
685 private void addLinkFlows(Link link, ArrayNode paths, Integer count) { 687 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
...@@ -723,7 +725,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -723,7 +725,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
723 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : ""); 725 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
724 } 726 }
725 727
726 - return JsonUtils.envelope("showTraffic", 0, payload); 728 + return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
727 } 729 }
728 730
729 // Classifies the link traffic according to the specified classes. 731 // Classifies the link traffic according to the specified classes.
...@@ -870,21 +872,13 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -870,21 +872,13 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
870 result.set("props", pnode); 872 result.set("props", pnode);
871 873
872 ArrayNode buttons = arrayNode(); 874 ArrayNode buttons = arrayNode();
873 - for (ButtonDescriptor b : pp.buttons()) { 875 + for (ButtonId b : pp.buttons()) {
874 - buttons.add(json(b)); 876 + buttons.add(b.id());
875 } 877 }
876 result.set("buttons", buttons); 878 result.set("buttons", buttons);
877 return result; 879 return result;
878 } 880 }
879 881
880 - // translates the button descriptor into JSON
881 - private ObjectNode json(ButtonDescriptor bdesc) {
882 - return objectNode()
883 - .put("id", bdesc.id())
884 - .put("gid", bdesc.glyphId())
885 - .put("tt", bdesc.tooltip());
886 - }
887 -
888 882
889 // Produces canonical link key, i.e. one that will match link and its inverse. 883 // Produces canonical link key, i.e. one that will match link and its inverse.
890 static LinkKey canonicalLinkKey(Link link) { 884 static LinkKey canonicalLinkKey(Link link) {
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
18 package org.onosproject.ui.impl; 18 package org.onosproject.ui.impl;
19 19
20 import org.onosproject.ui.UiTopoOverlay; 20 import org.onosproject.ui.UiTopoOverlay;
21 +import org.onosproject.ui.topo.ButtonId;
22 +import org.onosproject.ui.topo.PropertyPanel;
21 23
22 /** 24 /**
23 * Topology Overlay for network traffic. 25 * Topology Overlay for network traffic.
...@@ -25,12 +27,21 @@ import org.onosproject.ui.UiTopoOverlay; ...@@ -25,12 +27,21 @@ import org.onosproject.ui.UiTopoOverlay;
25 public class TrafficOverlay extends UiTopoOverlay { 27 public class TrafficOverlay extends UiTopoOverlay {
26 private static final String TRAFFIC_ID = "traffic"; 28 private static final String TRAFFIC_ID = "traffic";
27 29
28 - /** 30 + private static final String SDF_ID = "showDeviceFlows";
29 - * Constructs the traffic overlay. 31 + private static final String SRT_ID = "showRelatedTraffic";
30 - */ 32 +
33 + private static final ButtonId SHOW_DEVICE_FLOWS = new ButtonId(SDF_ID);
34 + private static final ButtonId SHOW_RELATED_TRAFFIC = new ButtonId(SRT_ID);
35 +
36 +
31 public TrafficOverlay() { 37 public TrafficOverlay() {
32 super(TRAFFIC_ID); 38 super(TRAFFIC_ID);
33 } 39 }
34 40
35 - // TODO : override init(), activate(), deactivate(), destroy() 41 + @Override
42 + public void modifyDeviceDetails(PropertyPanel pp) {
43 + pp.addButton(SHOW_DEVICE_FLOWS)
44 + .addButton(SHOW_RELATED_TRAFFIC);
45 + }
46 +
36 } 47 }
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
18 package org.meowster.over; 18 package org.meowster.over;
19 19
20 import org.onosproject.ui.UiTopoOverlay; 20 import org.onosproject.ui.UiTopoOverlay;
21 -import org.onosproject.ui.topo.ButtonDescriptor; 21 +import org.onosproject.ui.topo.ButtonId;
22 import org.onosproject.ui.topo.PropertyPanel; 22 import org.onosproject.ui.topo.PropertyPanel;
23 import org.onosproject.ui.topo.TopoConstants.CoreButtons; 23 import org.onosproject.ui.topo.TopoConstants.CoreButtons;
24 import org.onosproject.ui.topo.TopoConstants.Glyphs; 24 import org.onosproject.ui.topo.TopoConstants.Glyphs;
...@@ -36,12 +36,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay { ...@@ -36,12 +36,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay {
36 private static final String MY_TITLE = "I changed the title"; 36 private static final String MY_TITLE = "I changed the title";
37 private static final String MY_VERSION = "Beta-1.0.0042"; 37 private static final String MY_VERSION = "Beta-1.0.0042";
38 38
39 - private static final ButtonDescriptor FOO_DESCRIPTOR = 39 + private static final ButtonId FOO_BUTTON = new ButtonId("foo");
40 - new ButtonDescriptor("foo", "chain", "A FOO action"); 40 + private static final ButtonId BAR_BUTTON = new ButtonId("bar");
41 -
42 - private static final ButtonDescriptor BAR_DESCRIPTOR =
43 - new ButtonDescriptor("bar", "*banner", "A BAR action");
44 -
45 41
46 public AppUiTopoOverlay() { 42 public AppUiTopoOverlay() {
47 super(OVERLAY_ID); 43 super(OVERLAY_ID);
...@@ -68,8 +64,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay { ...@@ -68,8 +64,8 @@ public class AppUiTopoOverlay extends UiTopoOverlay {
68 pp.title(MY_TITLE); 64 pp.title(MY_TITLE);
69 pp.removeProps(LATITUDE, LONGITUDE); 65 pp.removeProps(LATITUDE, LONGITUDE);
70 66
71 - pp.addButton(FOO_DESCRIPTOR) 67 + pp.addButton(FOO_BUTTON)
72 - .addButton(BAR_DESCRIPTOR); 68 + .addButton(BAR_BUTTON);
73 69
74 pp.removeButtons(CoreButtons.SHOW_PORT_VIEW) 70 pp.removeButtons(CoreButtons.SHOW_PORT_VIEW)
75 .removeButtons(CoreButtons.SHOW_GROUP_VIEW); 71 .removeButtons(CoreButtons.SHOW_GROUP_VIEW);
......
...@@ -3,7 +3,10 @@ ...@@ -3,7 +3,10 @@
3 'use strict'; 3 'use strict';
4 4
5 // injected refs 5 // injected refs
6 - var $log; 6 + var $log, tov;
7 +
8 + // internal state
9 + var someStateValue = true;
7 10
8 // our overlay definition 11 // our overlay definition
9 var overlay = { 12 var overlay = {
...@@ -27,40 +30,127 @@ ...@@ -27,40 +30,127 @@
27 } 30 }
28 }, 31 },
29 32
30 - activate: activateOverlay, 33 + activate: function () {
31 - deactivate: deactivateOverlay, 34 + $log.debug("sample topology overlay ACTIVATED");
35 + },
36 + deactivate: function () {
37 + $log.debug("sample topology overlay DEACTIVATED");
38 + },
39 +
40 +
41 +
32 42
33 - // button callbacks matching button identifiers
34 - buttonActions: {
35 - foo: fooCb,
36 - bar: barCb
37 - }
38 - };
39 43
40 - function fooCb(data) { 44 + // detail panel button definitions
41 - $log.debug('FOO callback with data:', data); 45 + buttons: {
46 + foo: {
47 + gid: 'chain',
48 + tt: 'A FOO action',
49 + cb: function (data) {
50 + $log.debug('FOO action invoked with data:', data);
42 } 51 }
52 + },
53 + bar: {
54 + gid: '*banner',
55 + tt: 'A BAR action',
56 + cb: function (data) {
57 + $log.debug('BAR action invoked with data:', data);
58 + }
59 + }
60 + },
43 61
44 - function barCb(data) { 62 + // Key bindings for traffic overlay buttons
45 - $log.debug('BAR callback with data:', data); 63 + // NOTE: fully qual. button ID is derived from overlay-id and key-name
64 + keyBindings: {
65 + V: {
66 + cb: buttonCallback,
67 + tt: 'Uses the V key',
68 + gid: '*banner'
69 + },
70 + F: {
71 + cb: buttonCallback,
72 + tt: 'Uses the F key',
73 + gid: 'chain'
74 + },
75 + G: {
76 + cb: buttonCallback,
77 + tt: 'Uses the G key',
78 + gid: 'crown'
79 + },
80 +
81 + T: {
82 + cb: buttonCallback,
83 + tt: 'Uses the T key',
84 + gid: 'switch'
85 + },
86 +
87 + R: {
88 + cb: buttonCallback,
89 + tt: 'Uses the R key',
90 + gid: 'endstation'
91 + },
92 +
93 + 0: {
94 + cb: buttonCallback,
95 + tt: 'Uses the ZERO key',
96 + gid: 'xMark'
97 + },
98 +
99 + _keyOrder: [
100 + '0', 'V', 'F', 'G', 'T', 'R'
101 + ]
102 +
103 + // NOTE: T and R should be rejected (not installed)
104 + // T is reserved for 'toggle Theme'
105 + // R is reserved for 'Reset pan and zoom'
106 + },
107 +
108 + hooks: {
109 + // hook for handling escape key
110 + // Must return true to consume ESC, false otherwise.
111 + escape: cancelState,
112 +
113 + // hooks for when the selection changes...
114 + empty: function () {
115 + selectionCallback('empty');
116 + },
117 + single: function (data) {
118 + selectionCallback('single', data);
119 + },
120 + multi: function (selectOrder) {
121 + selectionCallback('multi', selectOrder);
122 + tov.addDetailButton('foo');
123 + tov.addDetailButton('bar');
124 + }
46 } 125 }
47 126
48 - // === implementation of overlay API (essentially callbacks) 127 + };
49 - function activateOverlay() { 128 +
50 - $log.debug("sample topology overlay ACTIVATED"); 129 + // invoked when the escape key is pressed
130 + function cancelState() {
131 + if (someStateValue) {
132 + someStateValue = false;
133 + // we consumed the ESC event
134 + return true;
135 + }
136 + return false;
51 } 137 }
52 138
53 - function deactivateOverlay() { 139 + function buttonCallback(x) {
54 - $log.debug("sample topology overlay DEACTIVATED"); 140 + $log.debug('Toolbar-button callback', x);
55 } 141 }
56 142
143 + function selectionCallback(x, d) {
144 + $log.debug('Selection callback', x, d);
145 + }
57 146
58 // invoke code to register with the overlay service 147 // invoke code to register with the overlay service
59 angular.module('ovSample') 148 angular.module('ovSample')
60 .run(['$log', 'TopoOverlayService', 149 .run(['$log', 'TopoOverlayService',
61 150
62 - function (_$log_, tov) { 151 + function (_$log_, _tov_) {
63 $log = _$log_; 152 $log = _$log_;
153 + tov = _tov_;
64 tov.register(overlay); 154 tov.register(overlay);
65 }]); 155 }]);
66 156
......
...@@ -45,6 +45,10 @@ ...@@ -45,6 +45,10 @@
45 "C429.9,285.5,426.7,293.2,427.7,300.4z" 45 "C429.9,285.5,426.7,293.2,427.7,300.4z"
46 }, 46 },
47 47
48 + // TODO: ONOS-2566 glyphs for device types:
49 + // otn, roadm_otn, firewall, balancer, ips, ids,
50 + // controller, virtual, fiber_switch, other
51 +
48 glyphDataSet = { 52 glyphDataSet = {
49 _viewbox: "0 0 110 110", 53 _viewbox: "0 0 110 110",
50 54
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
18 ONOS GUI -- Widget -- Toolbar Service 18 ONOS GUI -- Widget -- Toolbar Service
19 */ 19 */
20 // TODO: Augment service to allow toolbars to exist on right edge of screen 20 // TODO: Augment service to allow toolbars to exist on right edge of screen
21 +// TODO: also - make toolbar more object aware (rows etc.)
21 22
22 23
23 (function () { 24 (function () {
...@@ -80,6 +81,7 @@ ...@@ -80,6 +81,7 @@
80 panel = ps.createPanel(tbid, settings), 81 panel = ps.createPanel(tbid, settings),
81 arrowDiv = createArrow(panel), 82 arrowDiv = createArrow(panel),
82 currentRow = panel.append('div').classed('tbar-row', true), 83 currentRow = panel.append('div').classed('tbar-row', true),
84 + rowButtonIds = [], // for removable buttons
83 tbWidth = arrowSize + 2, // empty toolbar width 85 tbWidth = arrowSize + 2, // empty toolbar width
84 maxWidth = panel.width(); 86 maxWidth = panel.width();
85 87
...@@ -162,7 +164,41 @@ ...@@ -162,7 +164,41 @@
162 } else { 164 } else {
163 panel.append('br'); 165 panel.append('br');
164 currentRow = panel.append('div').classed('tbar-row', true); 166 currentRow = panel.append('div').classed('tbar-row', true);
167 +
168 + // return API to allow caller more access to the row
169 + return {
170 + clear: rowClear,
171 + setText: rowSetText,
172 + addButton: rowAddButton,
173 + classed: rowClassed
174 + };
175 + }
176 + }
177 +
178 + function rowClear() {
179 + currentRow.selectAll('*').remove();
180 + rowButtonIds.forEach(function (bid) {
181 + delete items[bid];
182 + });
183 + rowButtonIds = [];
165 } 184 }
185 +
186 + // installs a div with text into the button row
187 + function rowSetText(text) {
188 + rowClear();
189 + currentRow.append('div').classed('tbar-row-text', true)
190 + .html(text);
191 + }
192 +
193 + function rowAddButton(id, gid, cb, tooltip) {
194 + var b = addButton(id, gid, cb, tooltip);
195 + if (b) {
196 + rowButtonIds.push(id);
197 + }
198 + }
199 +
200 + function rowClassed(classes, bool) {
201 + currentRow.classed(classes, bool);
166 } 202 }
167 203
168 function show(cb) { 204 function show(cb) {
......
...@@ -305,6 +305,21 @@ html[data-platform='iPad'] #topo-p-detail { ...@@ -305,6 +305,21 @@ html[data-platform='iPad'] #topo-p-detail {
305 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"); 305 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");
306 } 306 }
307 307
308 +
309 +/* --- Toolbar --- */
310 +
311 +#toolbar-topo-tbar .tbar-row.right {
312 + width: 100%;
313 +}
314 +
315 +#toolbar-topo-tbar .tbar-row-text {
316 + height: 21px;
317 + text-align: right;
318 + padding: 8px 60px 0 0;
319 + font-style: italic;
320 +}
321 +
322 +
308 /* --- Topo Nodes --- */ 323 /* --- Topo Nodes --- */
309 324
310 #ov-topo svg .suppressed { 325 #ov-topo svg .suppressed {
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
40 40
41 // --- Short Cut Keys ------------------------------------------------ 41 // --- Short Cut Keys ------------------------------------------------
42 42
43 - function setUpKeys() { 43 + function setUpKeys(overlayKeys) {
44 // key bindings need to be made after the services have been injected 44 // key bindings need to be made after the services have been injected
45 // thus, deferred to here... 45 // thus, deferred to here...
46 actionMap = { 46 actionMap = {
...@@ -63,14 +63,6 @@ ...@@ -63,14 +63,6 @@
63 R: [resetZoom, 'Reset pan / zoom'], 63 R: [resetZoom, 'Reset pan / zoom'],
64 dot: [ttbs.toggleToolbar, 'Toggle Toolbar'], 64 dot: [ttbs.toggleToolbar, 'Toggle Toolbar'],
65 65
66 - V: [tts.showRelatedIntentsAction, 'Show all related intents'],
67 - rightArrow: [tts.showNextIntentAction, 'Show next related intent'],
68 - leftArrow: [tts.showPrevIntentAction, 'Show previous related intent'],
69 - W: [tts.showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
70 - A: [tts.showAllFlowTrafficAction, 'Monitor all traffic using flow stats'],
71 - Q: [tts.showAllPortTrafficAction, 'Monitor all traffic using port stats'],
72 - F: [tts.showDeviceLinkFlowsAction, 'Show device link flows'],
73 -
74 E: [equalizeMasters, 'Equalize mastership roles'], 66 E: [equalizeMasters, 'Equalize mastership roles'],
75 67
76 esc: handleEscape, 68 esc: handleEscape,
...@@ -78,12 +70,16 @@ ...@@ -78,12 +70,16 @@
78 _keyListener: ttbs.keyListener, 70 _keyListener: ttbs.keyListener,
79 71
80 _helpFormat: [ 72 _helpFormat: [
81 - ['I', 'O', 'D', '-', 'H', 'M', 'P', 'dash', 'B' ], 73 + ['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B', 'S' ],
82 - ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'dot'], 74 + ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'E', '-', 'dot'],
83 - ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ] 75 + [] // this column reserved for overlay actions
84 ] 76 ]
85 }; 77 };
86 78
79 + if (fs.isO(overlayKeys)) {
80 + mergeKeys(overlayKeys);
81 + }
82 +
87 ks.keyBindings(actionMap); 83 ks.keyBindings(actionMap);
88 84
89 ks.gestureNotes([ 85 ks.gestureNotes([
...@@ -95,6 +91,22 @@ ...@@ -95,6 +91,22 @@
95 ]); 91 ]);
96 } 92 }
97 93
94 + // when a topology overlay is activated, we need to bind their keystrokes
95 + // and include them in the quick-help panel
96 + function mergeKeys(extra) {
97 + var _hf = actionMap._helpFormat[2];
98 + extra._keyOrder.forEach(function (k) {
99 + var d = extra[k],
100 + cb = d && d.cb,
101 + tt = d && d.tt;
102 + // NOTE: ignore keys that are already defined
103 + if (d && !actionMap[k]) {
104 + actionMap[k] = [cb, tt];
105 + _hf.push(k);
106 + }
107 + });
108 + }
109 +
98 // --- Keystroke functions ------------------------------------------- 110 // --- Keystroke functions -------------------------------------------
99 111
100 function toggleInstances(x) { 112 function toggleInstances(x) {
...@@ -153,6 +165,10 @@ ...@@ -153,6 +165,10 @@
153 // if an instance is selected, cancel the affinity mapping 165 // if an instance is selected, cancel the affinity mapping
154 tis.cancelAffinity() 166 tis.cancelAffinity()
155 167
168 + } else if (tov.hooks.escape()) {
169 + // else if the overlay consumed the ESC event...
170 + // (work already done)
171 +
156 } else if (tss.deselectAll()) { 172 } else if (tss.deselectAll()) {
157 // else if we have node selections, deselect them all 173 // else if we have node selections, deselect them all
158 // (work already done) 174 // (work already done)
...@@ -169,19 +185,15 @@ ...@@ -169,19 +185,15 @@
169 } else if (tps.summaryVisible()) { 185 } else if (tps.summaryVisible()) {
170 // else if the Summary Panel is visible, hide it 186 // else if the Summary Panel is visible, hide it
171 tps.hideSummaryPanel(); 187 tps.hideSummaryPanel();
172 -
173 - } else {
174 - // TODO: set hover mode to hoverModeNone
175 - // talk to Thomas about this: shouldn't it be done
176 - // when we deselect the node (if tss.haveDetails()...)
177 } 188 }
178 } 189 }
179 190
180 // --- Toolbar Functions --------------------------------------------- 191 // --- Toolbar Functions ---------------------------------------------
181 192
182 function notValid(what) { 193 function notValid(what) {
183 - $log.warn('Topo.js getActionEntry(): Not a valid ' + what); 194 + $log.warn('topo.js getActionEntry(): Not a valid ' + what);
184 } 195 }
196 +
185 function getActionEntry(key) { 197 function getActionEntry(key) {
186 var entry; 198 var entry;
187 199
...@@ -201,7 +213,8 @@ ...@@ -201,7 +213,8 @@
201 213
202 function setUpToolbar() { 214 function setUpToolbar() {
203 ttbs.init({ 215 ttbs.init({
204 - getActionEntry: getActionEntry 216 + getActionEntry: getActionEntry,
217 + setUpKeys: setUpKeys
205 }); 218 });
206 ttbs.createToolbar(); 219 ttbs.createToolbar();
207 } 220 }
...@@ -503,7 +516,6 @@ ...@@ -503,7 +516,6 @@
503 restoreConfigFromPrefs(); 516 restoreConfigFromPrefs();
504 517
505 $log.debug('registered overlays...', tov.list()); 518 $log.debug('registered overlays...', tov.list());
506 -
507 $log.log('OvTopoCtrl has been created'); 519 $log.log('OvTopoCtrl has been created');
508 }]); 520 }]);
509 }()); 521 }());
......
...@@ -27,14 +27,14 @@ ...@@ -27,14 +27,14 @@
27 'use strict'; 27 'use strict';
28 28
29 // injected refs 29 // injected refs
30 - var $log, $interval, wss, tps, tis, tfs, tss, tts, tspr; 30 + var $log, $interval, wss, tps, tis, tfs, tss, tov, tspr;
31 31
32 // internal state 32 // internal state
33 var handlerMap, 33 var handlerMap,
34 openListener, 34 openListener,
35 heartbeatTimer; 35 heartbeatTimer;
36 36
37 - var heartbeatPeriod = 5000; // 5 seconds 37 + var heartbeatPeriod = 9000; // 9 seconds
38 38
39 // ========================== 39 // ==========================
40 40
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
44 44
45 showDetails: tss, 45 showDetails: tss,
46 46
47 - showTraffic: tts, 47 + showHighlights: tov,
48 48
49 addInstance: tis, 49 addInstance: tis,
50 updateInstance: tis, 50 updateInstance: tis,
...@@ -90,10 +90,10 @@ ...@@ -90,10 +90,10 @@
90 .factory('TopoEventService', 90 .factory('TopoEventService',
91 ['$log', '$interval', 'WebSocketService', 91 ['$log', '$interval', 'WebSocketService',
92 'TopoPanelService', 'TopoInstService', 'TopoForceService', 92 'TopoPanelService', 'TopoInstService', 'TopoForceService',
93 - 'TopoSelectService', 'TopoTrafficService', 'TopoSpriteService', 93 + 'TopoSelectService', 'TopoOverlayService', 'TopoSpriteService',
94 94
95 function (_$log_, _$interval_, _wss_, 95 function (_$log_, _$interval_, _wss_,
96 - _tps_, _tis_, _tfs_, _tss_, _tts_, _tspr_) { 96 + _tps_, _tis_, _tfs_, _tss_, _tov_, _tspr_) {
97 $log = _$log_; 97 $log = _$log_;
98 $interval = _$interval_; 98 $interval = _$interval_;
99 wss = _wss_; 99 wss = _wss_;
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
101 tis = _tis_; 101 tis = _tis_;
102 tfs = _tfs_; 102 tfs = _tfs_;
103 tss = _tss_; 103 tss = _tss_;
104 - tts = _tts_; 104 + tov = _tov_;
105 tspr = _tspr_; 105 tspr = _tspr_;
106 106
107 createHandlerMap(); 107 createHandlerMap();
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, $timeout, fs, sus, is, ts, flash, wss, 26 + var $log, $timeout, fs, sus, ts, flash, wss, tov,
27 tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg; 27 tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg;
28 28
29 // configuration 29 // configuration
...@@ -797,9 +797,10 @@ ...@@ -797,9 +797,10 @@
797 return true; 797 return true;
798 } 798 }
799 799
800 - // ========================== 800 + // =============================================
801 - // function entry points for traffic module 801 + // function entry points for overlay module
802 802
803 + // TODO: find an automatic way of tracking via the "showHighlights" events
803 var allTrafficClasses = 'primary secondary optical animated ' + 804 var allTrafficClasses = 'primary secondary optical animated ' +
804 'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' + 805 'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' +
805 'port-traffic-Gbps-choked'; 806 'port-traffic-Gbps-choked';
...@@ -845,7 +846,7 @@ ...@@ -845,7 +846,7 @@
845 }; 846 };
846 } 847 }
847 848
848 - function mkD3Api(uplink) { 849 + function mkD3Api() {
849 return { 850 return {
850 node: function () { return node; }, 851 node: function () { return node; },
851 link: function () { return link; }, 852 link: function () { return link; },
...@@ -859,7 +860,7 @@ ...@@ -859,7 +860,7 @@
859 }; 860 };
860 } 861 }
861 862
862 - function mkSelectApi(uplink) { 863 + function mkSelectApi() {
863 return { 864 return {
864 node: function () { return node; }, 865 node: function () { return node; },
865 zoomingOrPanning: zoomingOrPanning, 866 zoomingOrPanning: zoomingOrPanning,
...@@ -868,15 +869,20 @@ ...@@ -868,15 +869,20 @@
868 }; 869 };
869 } 870 }
870 871
871 - function mkTrafficApi(uplink) { 872 + function mkTrafficApi() {
873 + return {
874 + hovered: tss.hovered,
875 + somethingSelected: tss.somethingSelected,
876 + selectOrder: tss.selectOrder
877 + };
878 + }
879 +
880 + function mkOverlayApi() {
872 return { 881 return {
873 clearLinkTrafficStyle: clearLinkTrafficStyle, 882 clearLinkTrafficStyle: clearLinkTrafficStyle,
874 removeLinkLabels: removeLinkLabels, 883 removeLinkLabels: removeLinkLabels,
875 updateLinks: updateLinks, 884 updateLinks: updateLinks,
876 - findLinkById: tms.findLinkById, 885 + findLinkById: tms.findLinkById
877 - hovered: tss.hovered,
878 - validateSelectionContext: tss.validateSelectionContext,
879 - selectOrder: tss.selectOrder
880 }; 886 };
881 } 887 }
882 888
...@@ -904,7 +910,7 @@ ...@@ -904,7 +910,7 @@
904 }; 910 };
905 } 911 }
906 912
907 - function mkFilterApi(uplink) { 913 + function mkFilterApi() {
908 return { 914 return {
909 node: function () { return node; }, 915 node: function () { return node; },
910 link: function () { return link; } 916 link: function () { return link; }
...@@ -925,11 +931,11 @@ ...@@ -925,11 +931,11 @@
925 .factory('TopoForceService', 931 .factory('TopoForceService',
926 ['$log', '$timeout', 'FnService', 'SvgUtilService', 932 ['$log', '$timeout', 'FnService', 'SvgUtilService',
927 'ThemeService', 'FlashService', 'WebSocketService', 933 'ThemeService', 'FlashService', 'WebSocketService',
928 - 'TopoInstService', 'TopoModelService', 934 + 'TopoOverlayService', 'TopoInstService', 'TopoModelService',
929 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService', 935 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
930 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService', 936 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
931 937
932 - function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, 938 + function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_,
933 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) { 939 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
934 $log = _$log_; 940 $log = _$log_;
935 $timeout = _$timeout_; 941 $timeout = _$timeout_;
...@@ -938,6 +944,7 @@ ...@@ -938,6 +944,7 @@
938 ts = _ts_; 944 ts = _ts_;
939 flash = _flash_; 945 flash = _flash_;
940 wss = _wss_; 946 wss = _wss_;
947 + tov = _tov_;
941 tis = _tis_; 948 tis = _tis_;
942 tms = _tms_; 949 tms = _tms_;
943 td3 = _td3_; 950 td3 = _td3_;
...@@ -966,12 +973,13 @@ ...@@ -966,12 +973,13 @@
966 973
967 $log.debug('initForce().. dim = ' + dim); 974 $log.debug('initForce().. dim = ' + dim);
968 975
976 + tov.setApi(mkOverlayApi(), tss);
969 tms.initModel(mkModelApi(uplink), dim); 977 tms.initModel(mkModelApi(uplink), dim);
970 - td3.initD3(mkD3Api(uplink)); 978 + td3.initD3(mkD3Api());
971 - tss.initSelect(mkSelectApi(uplink)); 979 + tss.initSelect(mkSelectApi());
972 - tts.initTraffic(mkTrafficApi(uplink)); 980 + tts.initTraffic(mkTrafficApi());
973 tos.initOblique(mkObliqueApi(uplink, fltr)); 981 tos.initOblique(mkObliqueApi(uplink, fltr));
974 - fltr.initFilter(mkFilterApi(uplink)); 982 + fltr.initFilter(mkFilterApi());
975 tls.initLink(mkLinkApi(svg, uplink), td3); 983 tls.initLink(mkLinkApi(svg, uplink), td3);
976 984
977 settings = angular.extend({}, defaultSettings, opts); 985 settings = angular.extend({}, defaultSettings, opts);
...@@ -1016,6 +1024,7 @@ ...@@ -1016,6 +1024,7 @@
1016 tss.destroySelect(); 1024 tss.destroySelect();
1017 td3.destroyD3(); 1025 td3.destroyD3();
1018 tms.destroyModel(); 1026 tms.destroyModel();
1027 + // note: no need to destroy overlay service
1019 ts.removeListener(themeListener); 1028 ts.removeListener(themeListener);
1020 themeListener = null; 1029 themeListener = null;
1021 1030
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
30 var tos = 'TopoOverlayService: '; 30 var tos = 'TopoOverlayService: ';
31 31
32 // injected refs 32 // injected refs
33 - var $log, fs, gs, wss, ns; 33 + var $log, fs, gs, wss, ns, tss, tps, api;
34 34
35 // internal state 35 // internal state
36 var overlays = {}, 36 var overlays = {},
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
80 function register(overlay) { 80 function register(overlay) {
81 var r = 'register', 81 var r = 'register',
82 over = fs.isO(overlay), 82 over = fs.isO(overlay),
83 + kb = over ? fs.isO(overlay.keyBindings) : null,
83 id = over ? over.overlayId : ''; 84 id = over ? over.overlayId : '';
84 85
85 if (!id) { 86 if (!id) {
...@@ -90,11 +91,26 @@ ...@@ -90,11 +91,26 @@
90 } 91 }
91 overlays[id] = overlay; 92 overlays[id] = overlay;
92 handleGlyphs(overlay); 93 handleGlyphs(overlay);
94 +
95 + if (kb) {
96 + if (!fs.isA(kb._keyOrder)) {
97 + warn(r, 'no _keyOrder array defined on keyBindings');
98 + } else {
99 + kb._keyOrder.forEach(function (k) {
100 + if (k !== '-' && !kb[k]) {
101 + warn(r, 'no "' + k + '" property defined on keyBindings');
102 + }
103 + });
104 + }
105 + }
106 +
93 $log.debug(tos + 'registered overlay: ' + id, overlay); 107 $log.debug(tos + 'registered overlay: ' + id, overlay);
94 } 108 }
95 109
110 + // TODO: remove this redundant code.......
96 // NOTE: unregister needs to be called if an app is ever 111 // NOTE: unregister needs to be called if an app is ever
97 // deactivated/uninstalled via the applications view 112 // deactivated/uninstalled via the applications view
113 +/*
98 function unregister(overlay) { 114 function unregister(overlay) {
99 var u = 'unregister', 115 var u = 'unregister',
100 over = fs.isO(overlay), 116 over = fs.isO(overlay),
...@@ -108,21 +124,33 @@ ...@@ -108,21 +124,33 @@
108 } 124 }
109 delete overlays[id]; 125 delete overlays[id];
110 $log.debug(tos + 'unregistered overlay: ' + id); 126 $log.debug(tos + 'unregistered overlay: ' + id);
111 - // TODO: rebuild the toolbar overlay radio button set
112 } 127 }
128 +*/
113 129
130 +
131 + // returns the list of overlay identifiers
114 function list() { 132 function list() {
115 return d3.map(overlays).keys(); 133 return d3.map(overlays).keys();
116 } 134 }
117 135
118 - function overlay(id) { 136 + // add a radio button for each registered overlay
119 - return overlays[id]; 137 + function augmentRbset(rset, switchFn) {
138 + angular.forEach(overlays, function (ov) {
139 + rset.push({
140 + gid: ov._glyphId,
141 + tooltip: (ov.tooltip || '(no tooltip)'),
142 + cb: function () {
143 + tbSelection(ov.overlayId, switchFn);
144 + }
145 + });
146 + });
120 } 147 }
121 148
122 // an overlay was selected via toolbar radio button press from user 149 // an overlay was selected via toolbar radio button press from user
123 - function tbSelection(id) { 150 + function tbSelection(id, switchFn) {
124 var same = current && current.overlayId === id, 151 var same = current && current.overlayId === id,
125 - payload = {}; 152 + payload = {},
153 + actions;
126 154
127 function doop(op) { 155 function doop(op) {
128 var oid = current.overlayId; 156 var oid = current.overlayId;
...@@ -133,70 +161,211 @@ ...@@ -133,70 +161,211 @@
133 161
134 if (!same) { 162 if (!same) {
135 current && doop('deactivate'); 163 current && doop('deactivate');
136 - current = overlay(id); 164 + current = overlays[id];
137 current && doop('activate'); 165 current && doop('activate');
166 + actions = current && fs.isO(current.keyBindings);
167 + switchFn(id, actions);
168 +
138 wss.sendEvent('topoSelectOverlay', payload); 169 wss.sendEvent('topoSelectOverlay', payload);
139 170
140 - // TODO: refactor to emit "flush on overlay change" messages 171 + // Ensure summary and details panels are updated immediately..
141 wss.sendEvent('requestSummary'); 172 wss.sendEvent('requestSummary');
173 + tss.updateDetail();
142 } 174 }
143 } 175 }
144 176
145 - var coreButtonPath = { 177 + var coreButtons = {
146 - showDeviceView: 'device', 178 + showDeviceView: {
147 - showFlowView: 'flow', 179 + gid: 'switch',
148 - showPortView: 'port', 180 + tt: 'Show Device View',
149 - showGroupView: 'group' 181 + path: 'device'
182 + },
183 + showFlowView: {
184 + gid: 'flowTable',
185 + tt: 'Show Flow View for this Device',
186 + path: 'flow'
187 + },
188 + showPortView: {
189 + gid: 'portTable',
190 + tt: 'Show Port View for this Device',
191 + path: 'port'
192 + },
193 + showGroupView: {
194 + gid: 'groupTable',
195 + tt: 'Show Group View for this Device',
196 + path: 'group'
197 + }
150 }; 198 };
151 199
200 + // retrieves a button definition from the current overlay and generates
201 + // a button descriptor to be added to the panel, with the data baked in
202 + function _getButtonDef(id, data) {
203 + var btns = current && current.buttons,
204 + b = btns && btns[id],
205 + cb = fs.isF(b.cb),
206 + f = cb ? function () { cb(data); } : function () {};
207 +
208 + return b ? {
209 + id: current.mkId(id),
210 + gid: current.mkGid(b.gid),
211 + tt: b.tt,
212 + cb: f
213 + } : null;
214 + }
215 +
152 // install core buttons, and include any additional from the current overlay 216 // install core buttons, and include any additional from the current overlay
153 - function installButtons(buttons, addFn, data, devId) { 217 + function installButtons(buttons, data, devId) {
154 - 218 + buttons.forEach(function (id) {
155 - angular.forEach(buttons, function (btn) { 219 + var btn = coreButtons[id],
156 - var path = coreButtonPath[btn.id], 220 + gid = btn && btn.gid,
157 - _id, 221 + tt = btn && btn.tt,
158 - _gid, 222 + path = btn && btn.path;
159 - _cb, 223 +
160 - action; 224 + if (btn) {
161 - 225 + tps.addAction({
162 - if (path) { 226 + id: 'core-' + id,
163 - // core callback function 227 + gid: gid,
164 - _id = btn.id; 228 + tt: tt,
165 - _gid = btn.gid; 229 + cb: function () { ns.navTo(path, {devId: devId }); }
166 - action = function () { 230 + });
167 - ns.navTo(path, { devId: devId }); 231 + } else if (btn = _getButtonDef(id, data)) {
168 - }; 232 + tps.addAction(btn);
169 - } else if (current) { 233 + }
170 - _id = current.mkId(btn.id); 234 + });
171 - _gid = current.mkGid(btn.gid);
172 - action = current.buttonActions[btn.id] || function () {};
173 } 235 }
174 236
175 - _cb = function () { action(data); }; 237 + function addDetailButton(id) {
238 + var b = _getButtonDef(id);
239 + if (b) {
240 + tps.addAction({
241 + id: current.mkId(id),
242 + gid: current.mkGid(b.gid),
243 + cb: b.cb,
244 + tt: b.tt
245 + });
246 + }
247 + }
248 +
249 +
250 + // === -----------------------------------------------------
251 + // Hooks for overlays
252 +
253 + function _hook(x) {
254 + var h = current && current.hooks;
255 + return h && fs.isF(h[x]);
256 + }
176 257
177 - addFn({ id: _id, gid: _gid, cb: _cb, tt: btn.tt}); 258 + function escapeHook() {
259 + var eh = _hook('escape');
260 + return eh ? eh() : false;
261 + }
262 +
263 + function emptySelectHook() {
264 + var cb = _hook('empty');
265 + cb && cb();
266 + }
267 +
268 + function singleSelectHook(data) {
269 + var cb = _hook('single');
270 + cb && cb(data);
271 + }
272 +
273 + function multiSelectHook(selectOrder) {
274 + var cb = _hook('multi');
275 + cb && cb(selectOrder);
276 + }
277 +
278 + // === -----------------------------------------------------
279 + // Event (from server) Handlers
280 +
281 + function setApi(_api_, _tss_) {
282 + api = _api_;
283 + tss = _tss_;
284 + }
285 +
286 + // TODO: refactor this (currently using showTraffic data structure)
287 + function showHighlights(data) {
288 + /*
289 + API to topoForce
290 + clearLinkTrafficStyle()
291 + removeLinkLabels()
292 + updateLinks()
293 + findLinkById( id )
294 + */
295 +
296 + var paths = data.paths;
297 +
298 + api.clearLinkTrafficStyle();
299 + api.removeLinkLabels();
300 +
301 + // Now highlight all links in the paths payload, and attach
302 + // labels to them, if they are defined.
303 + paths.forEach(function (p) {
304 + var n = p.links.length,
305 + i, ldata, lab, units, magnitude, portcls;
306 +
307 + for (i=0; i<n; i++) {
308 + ldata = api.findLinkById(p.links[i]);
309 + lab = p.labels[i];
310 +
311 + if (ldata && !ldata.el.empty()) {
312 + ldata.el.classed(p.class, true);
313 + ldata.label = lab;
314 +
315 + if (fs.endsWith(lab, 'bps')) {
316 + // inject additional styling for port-based traffic
317 + units = lab.substring(lab.length-4);
318 + portcls = 'port-traffic-' + units;
319 +
320 + // for GBps
321 + if (units.substring(0,1) === 'G') {
322 + magnitude = fs.parseBitRate(lab);
323 + if (magnitude >= 9) {
324 + portcls += '-choked'
325 + }
326 + }
327 + ldata.el.classed(portcls, true);
328 + }
329 + }
330 + }
178 }); 331 });
179 332
333 + api.updateLinks();
180 } 334 }
181 335
336 + // ========================================================================
337 +
182 angular.module('ovTopo') 338 angular.module('ovTopo')
183 .factory('TopoOverlayService', 339 .factory('TopoOverlayService',
184 ['$log', 'FnService', 'GlyphService', 'WebSocketService', 'NavService', 340 ['$log', 'FnService', 'GlyphService', 'WebSocketService', 'NavService',
341 + 'TopoPanelService',
185 342
186 - function (_$log_, _fs_, _gs_, _wss_, _ns_) { 343 + function (_$log_, _fs_, _gs_, _wss_, _ns_, _tps_) {
187 $log = _$log_; 344 $log = _$log_;
188 fs = _fs_; 345 fs = _fs_;
189 gs = _gs_; 346 gs = _gs_;
190 wss = _wss_; 347 wss = _wss_;
191 ns = _ns_; 348 ns = _ns_;
349 + tps = _tps_;
192 350
193 return { 351 return {
194 register: register, 352 register: register,
195 - unregister: unregister, 353 + //unregister: unregister,
354 + setApi: setApi,
196 list: list, 355 list: list,
197 - overlay: overlay, 356 + augmentRbset: augmentRbset,
357 + mkGlyphId: mkGlyphId,
198 tbSelection: tbSelection, 358 tbSelection: tbSelection,
199 - installButtons: installButtons 359 + installButtons: installButtons,
360 + addDetailButton: addDetailButton,
361 + hooks: {
362 + escape: escapeHook,
363 + emptySelect: emptySelectHook,
364 + singleSelect: singleSelectHook,
365 + multiSelect: multiSelectHook
366 + },
367 +
368 + showHighlights: showHighlights
200 } 369 }
201 }]); 370 }]);
202 371
......
...@@ -40,12 +40,6 @@ ...@@ -40,12 +40,6 @@
40 selectOrder = [], // the order in which we made selections 40 selectOrder = [], // the order in which we made selections
41 consumeClick = false; // used to coordinate with SVG click handler 41 consumeClick = false; // used to coordinate with SVG click handler
42 42
43 - // constants
44 - var devPath = 'device',
45 - flowPath = 'flow',
46 - portPath ='port',
47 - groupPath = 'group';
48 -
49 // ========================== 43 // ==========================
50 44
51 function nSel() { 45 function nSel() {
...@@ -157,8 +151,7 @@ ...@@ -157,8 +151,7 @@
157 151
158 // === ----------------------------------------------------- 152 // === -----------------------------------------------------
159 153
160 - function requestDetails() { 154 + function requestDetails(data) {
161 - var data = getSel(0).obj;
162 wss.sendEvent('requestDetails', { 155 wss.sendEvent('requestDetails', {
163 id: data.id, 156 id: data.id,
164 class: data.class 157 class: data.class
...@@ -179,91 +172,62 @@ ...@@ -179,91 +172,62 @@
179 } 172 }
180 173
181 function emptySelect() { 174 function emptySelect() {
182 - tts.cancelTraffic(); 175 + tov.hooks.emptySelect();
183 tps.displayNothing(); 176 tps.displayNothing();
184 } 177 }
185 178
186 function singleSelect() { 179 function singleSelect() {
187 - // NOTE: detail is shown from 'showDetails' event callback 180 + var data = getSel(0).obj;
188 - requestDetails(); 181 + requestDetails(data);
189 - tts.cancelTraffic(); 182 + // NOTE: detail panel is shown as a response to receiving
190 - tts.requestTrafficForMode(); 183 + // a 'showDetails' event from the server. See 'showDetails'
184 + // callback function below...
191 } 185 }
192 186
193 function multiSelect() { 187 function multiSelect() {
194 // display the selected nodes in the detail panel 188 // display the selected nodes in the detail panel
195 tps.displayMulti(selectOrder); 189 tps.displayMulti(selectOrder);
190 + addHostSelectionActions();
191 + tov.hooks.multiSelect(selectOrder);
192 + tps.displaySomething();
193 + }
196 194
197 - // always add the 'show traffic' action 195 + function addHostSelectionActions() {
198 - tps.addAction({ 196 + if (allSelectionsClass('host')) {
199 - id: '-mult-rel-traf-btn', 197 + if (nSel() === 2) {
200 - gid: 'allTraffic',
201 - cb: tts.showRelatedIntentsAction,
202 - tt: 'Show Related Traffic'
203 - });
204 -
205 - // add other actions, based on what is selected...
206 - if (nSel() === 2 && allSelectionsClass('host')) {
207 tps.addAction({ 198 tps.addAction({
208 id: 'host-flow-btn', 199 id: 'host-flow-btn',
209 gid: 'endstation', 200 gid: 'endstation',
210 - cb: tts.addHostIntentAction, 201 + cb: tts.addHostIntent,
211 tt: 'Create Host-to-Host Flow' 202 tt: 'Create Host-to-Host Flow'
212 }); 203 });
213 - } else if (nSel() >= 2 && allSelectionsClass('host')) { 204 + } else if (nSel() >= 2) {
214 tps.addAction({ 205 tps.addAction({
215 id: 'mult-src-flow-btn', 206 id: 'mult-src-flow-btn',
216 gid: 'flows', 207 gid: 'flows',
217 - cb: tts.addMultiSourceIntentAction, 208 + cb: tts.addMultiSourceIntent,
218 tt: 'Create Multi-Source Flow' 209 tt: 'Create Multi-Source Flow'
219 }); 210 });
220 } 211 }
221 - 212 + }
222 - tts.cancelTraffic();
223 - tts.requestTrafficForMode();
224 - tps.displaySomething();
225 } 213 }
226 214
227 215
228 // === ----------------------------------------------------- 216 // === -----------------------------------------------------
229 // Event Handlers 217 // Event Handlers
230 218
219 + // display the data for the single selected node
231 function showDetails(data) { 220 function showDetails(data) {
232 var buttons = fs.isA(data.buttons) || []; 221 var buttons = fs.isA(data.buttons) || [];
233 -
234 - // display the data for the single selected node
235 tps.displaySingle(data); 222 tps.displaySingle(data);
236 - 223 + tov.installButtons(buttons, data, data.props['URI']);
237 - tov.installButtons(buttons, tps.addAction, data, data.props['URI']); 224 + tov.hooks.singleSelect(data);
238 -
239 - // TODO: MOVE traffic buttons to the traffic overlay
240 - // always add the 'show traffic' action
241 - tps.addAction({
242 - id: '-sin-rel-traf-btn',
243 - gid: 'intentTraffic',
244 - cb: tts.showRelatedIntentsAction,
245 - tt: 'Show Related Traffic'
246 - });
247 -
248 - // add other actions, based on what is selected...
249 - if (data.type === 'switch') {
250 - tps.addAction({
251 - id: 'sin-dev-flows-btn',
252 - gid: 'flows',
253 - cb: tts.showDeviceLinkFlowsAction,
254 - tt: 'Show Device Flows'
255 - });
256 - }
257 -
258 tps.displaySomething(); 225 tps.displaySomething();
259 } 226 }
260 227
261 - function validateSelectionContext() { 228 + // returns true if we are hovering over a node, or any nodes are selected
262 - if (!hovered && !nSel()) { 229 + function somethingSelected() {
263 - tts.cancelTraffic(); 230 + return hovered || nSel();
264 - return false;
265 - }
266 - return true;
267 } 231 }
268 232
269 function clickConsumed(x) { 233 function clickConsumed(x) {
...@@ -306,10 +270,11 @@ ...@@ -306,10 +270,11 @@
306 selectObject: selectObject, 270 selectObject: selectObject,
307 deselectObject: deselectObject, 271 deselectObject: deselectObject,
308 deselectAll: deselectAll, 272 deselectAll: deselectAll,
273 + updateDetail: updateDetail,
309 274
310 hovered: function () { return hovered; }, 275 hovered: function () { return hovered; },
311 selectOrder: function () { return selectOrder; }, 276 selectOrder: function () { return selectOrder; },
312 - validateSelectionContext: validateSelectionContext, 277 + somethingSelected: somethingSelected,
313 278
314 clickConsumed: clickConsumed 279 clickConsumed: clickConsumed
315 }; 280 };
......
...@@ -25,19 +25,25 @@ ...@@ -25,19 +25,25 @@
25 // injected references 25 // injected references
26 var $log, fs, tbs, ps, tov, api; 26 var $log, fs, tbs, ps, tov, api;
27 27
28 + // API:
29 + // getActionEntry
30 + // setUpKeys
31 +
28 // internal state 32 // internal state
29 - var toolbar, keyData, cachedState; 33 + var toolbar, keyData, cachedState, thirdRow;
30 34
31 // constants 35 // constants
32 var name = 'topo-tbar', 36 var name = 'topo-tbar',
33 - cooktag = 'topo_prefs'; 37 + cooktag = 'topo_prefs',
38 + soa = 'switchOverlayActions: ',
39 + selOver = 'Select overlay here &#x21e7;';
40 +
34 41
35 // key to button mapping data 42 // key to button mapping data
36 var k2b = { 43 var k2b = {
37 O: { id: 'summary-tog', gid: 'summary', isel: true}, 44 O: { id: 'summary-tog', gid: 'summary', isel: true},
38 I: { id: 'instance-tog', gid: 'uiAttached', isel: true }, 45 I: { id: 'instance-tog', gid: 'uiAttached', isel: true },
39 D: { id: 'details-tog', gid: 'details', isel: true }, 46 D: { id: 'details-tog', gid: 'details', isel: true },
40 -
41 H: { id: 'hosts-tog', gid: 'endstation', isel: false }, 47 H: { id: 'hosts-tog', gid: 'endstation', isel: false },
42 M: { id: 'offline-tog', gid: 'switch', isel: true }, 48 M: { id: 'offline-tog', gid: 'switch', isel: true },
43 P: { id: 'ports-tog', gid: 'ports', isel: true }, 49 P: { id: 'ports-tog', gid: 'ports', isel: true },
...@@ -50,16 +56,16 @@ ...@@ -50,16 +56,16 @@
50 L: { id: 'cycleLabels-btn', gid: 'cycleLabels' }, 56 L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
51 R: { id: 'resetZoom-btn', gid: 'resetZoom' }, 57 R: { id: 'resetZoom-btn', gid: 'resetZoom' },
52 58
53 - E: { id: 'eqMaster-btn', gid: 'eqMaster' }, 59 + E: { id: 'eqMaster-btn', gid: 'eqMaster' }
54 -
55 - V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
56 - leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
57 - rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
58 - W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
59 - A: { id: 'allTraffic-btn', gid: 'allTraffic' },
60 - F: { id: 'flows-btn', gid: 'flows' }
61 }; 60 };
62 61
62 + var prohibited = [
63 + 'T', 'backSlash', 'slash',
64 + 'X' // needed until we re-instate X above.
65 + ];
66 + prohibited = prohibited.concat(d3.map(k2b).keys());
67 +
68 +
63 // initial toggle state: default settings and tag to key mapping 69 // initial toggle state: default settings and tag to key mapping
64 var defaultPrefsState = { 70 var defaultPrefsState = {
65 summary: 1, 71 summary: 1,
...@@ -112,6 +118,7 @@ ...@@ -112,6 +118,7 @@
112 } 118 }
113 119
114 function initKeyData() { 120 function initKeyData() {
121 + // TODO: use angular forEach instead of d3.map
115 keyData = d3.map(k2b); 122 keyData = d3.map(k2b);
116 keyData.forEach(function(key, value) { 123 keyData.forEach(function(key, value) {
117 var data = api.getActionEntry(key); 124 var data = api.getActionEntry(key);
...@@ -124,6 +131,7 @@ ...@@ -124,6 +131,7 @@
124 var v = keyData.get(key); 131 var v = keyData.get(key);
125 v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt); 132 v.btn = toolbar.addButton(v.id, v.gid, v.cb, v.tt);
126 } 133 }
134 +
127 function addToggle(key, suppressIfMobile) { 135 function addToggle(key, suppressIfMobile) {
128 var v = keyData.get(key); 136 var v = keyData.get(key);
129 if (suppressIfMobile && fs.isMobile()) { return; } 137 if (suppressIfMobile && fs.isMobile()) { return; }
...@@ -158,36 +166,60 @@ ...@@ -158,36 +166,60 @@
158 166
159 // generate radio button set for overlays; start with 'none' 167 // generate radio button set for overlays; start with 'none'
160 var rset = [{ 168 var rset = [{
161 - gid: 'unknown', 169 + gid: 'topo',
162 tooltip: 'No Overlay', 170 tooltip: 'No Overlay',
163 cb: function () { 171 cb: function () {
164 - tov.tbSelection(null); 172 + tov.tbSelection(null, switchOverlayActions);
165 } 173 }
166 }]; 174 }];
167 - 175 + tov.augmentRbset(rset, switchOverlayActions);
168 - tov.list().forEach(function (key) { 176 + toolbar.addRadioSet('topo-overlays', rset);
169 - var ov = tov.overlay(key);
170 - rset.push({
171 - gid: ov._glyphId,
172 - tooltip: (ov.tooltip || '(no tooltip)'),
173 - cb: function () {
174 - tov.tbSelection(ov.overlayId);
175 } 177 }
178 +
179 + // invoked by overlay service to switch out old buttons and switch in new
180 + function switchOverlayActions(oid, keyBindings) {
181 + var prohibits = [],
182 + kb = fs.isO(keyBindings) || {},
183 + order = fs.isA(kb._keyOrder) || [];
184 +
185 + if (keyBindings && !keyBindings._keyOrder) {
186 + $log.warn(soa + 'no _keyOrder property defined');
187 + } else {
188 + // sanity removal of reserved property names
189 + ['esc', '_keyListener', '_helpFormat'].forEach(function (k) {
190 + fs.removeFromArray(k, order);
176 }); 191 });
177 - }); 192 + }
178 193
179 - toolbar.addRadioSet('topo-overlays', rset); 194 + thirdRow.clear();
195 +
196 + if (!order.length) {
197 + thirdRow.setText(selOver);
198 + thirdRow.classed('right', true);
199 + api.setUpKeys(); // clear previous overlay key bindings
200 +
201 + } else {
202 + thirdRow.classed('right', false);
203 + angular.forEach(order, function (key) {
204 + var value, bid, gid, tt;
205 +
206 + if (prohibited.indexOf(key) > -1) {
207 + prohibits.push(key);
208 +
209 + } else {
210 + value = keyBindings[key];
211 + bid = oid + '-' + key;
212 + gid = tov.mkGlyphId(oid, value.gid);
213 + tt = value.tt + ' (' + key + ')';
214 + thirdRow.addButton(bid, gid, value.cb, tt);
215 + }
216 + });
217 + api.setUpKeys(keyBindings); // add overlay key bindings
180 } 218 }
181 219
182 - // TODO: 3rd row needs to be swapped in/out based on selected overlay 220 + if (prohibits.length) {
183 - // NOTE: This particular row of buttons is for the traffic overlay 221 + $log.warn(soa + 'Prohibited key bindings ignored:', prohibits);
184 - function addThirdRow() { 222 + }
185 - addButton('V');
186 - addButton('leftArrow');
187 - addButton('rightArrow');
188 - addButton('W');
189 - addButton('A');
190 - addButton('F');
191 } 223 }
192 224
193 function createToolbar() { 225 function createToolbar() {
...@@ -197,8 +229,9 @@ ...@@ -197,8 +229,9 @@
197 toolbar.addRow(); 229 toolbar.addRow();
198 addSecondRow(); 230 addSecondRow();
199 addOverlays(); 231 addOverlays();
200 - toolbar.addRow(); 232 + thirdRow = toolbar.addRow();
201 - addThirdRow(); 233 + thirdRow.setText(selOver);
234 + thirdRow.classed('right', true);
202 235
203 if (cachedState.toolbar) { 236 if (cachedState.toolbar) {
204 toolbar.show(); 237 toolbar.show();
......
...@@ -23,85 +23,44 @@ ...@@ -23,85 +23,44 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, fs, flash, wss; 26 + var $log, fs, flash, wss, api;
27 27
28 - // api to topoForce
29 - var api;
30 /* 28 /*
31 - clearLinkTrafficStyle() 29 + API to topoForce
32 - removeLinkLabels()
33 - updateLinks()
34 - findLinkById( id )
35 hovered() 30 hovered()
36 - validateSelectionContext() 31 + somethingSelected()
32 + selectOrder()
37 */ 33 */
38 34
39 - // constants
40 - var hoverModeNone = 0,
41 - hoverModeAll = 1,
42 - hoverModeFlows = 2,
43 - hoverModeIntents = 3;
44 -
45 // internal state 35 // internal state
46 - var hoverMode = hoverModeNone; 36 + var trafficMode = null,
37 + hoverMode = null;
47 38
48 39
49 // === ----------------------------------------------------- 40 // === -----------------------------------------------------
50 - // Event Handlers 41 + // Helper functions
51 -
52 - function showTraffic(data) {
53 - var paths = data.paths;
54 -
55 - api.clearLinkTrafficStyle();
56 - api.removeLinkLabels();
57 -
58 - // Now highlight all links in the paths payload, and attach
59 - // labels to them, if they are defined.
60 - paths.forEach(function (p) {
61 - var n = p.links.length,
62 - i, ldata, lab, units, magnitude, portcls;
63 -
64 - for (i=0; i<n; i++) {
65 - ldata = api.findLinkById(p.links[i]);
66 - lab = p.labels[i];
67 -
68 - if (ldata && !ldata.el.empty()) {
69 - ldata.el.classed(p.class, true);
70 - ldata.label = lab;
71 -
72 - if (fs.endsWith(lab, 'bps')) {
73 - // inject additional styling for port-based traffic
74 - units = lab.substring(lab.length-4);
75 - portcls = 'port-traffic-' + units;
76 -
77 - // for GBps
78 - if (units.substring(0,1) === 'G') {
79 - magnitude = fs.parseBitRate(lab);
80 - if (magnitude >= 9) {
81 - portcls += '-choked'
82 - }
83 - }
84 - ldata.el.classed(portcls, true);
85 - }
86 - }
87 - }
88 - });
89 42
90 - api.updateLinks(); 43 + // invoked in response to change in selection and/or mouseover/out:
44 + function requestTrafficForMode() {
45 + if (hoverMode === 'flows') {
46 + requestDeviceLinkFlows();
47 + } else if (hoverMode === 'intents') {
48 + requestRelatedIntents();
49 + } else {
50 + cancelTraffic();
51 + }
91 } 52 }
92 -
93 - // === -----------------------------------------------------
94 - // Helper functions
95 53
96 function requestDeviceLinkFlows() { 54 function requestDeviceLinkFlows() {
55 + // generates payload based on current hover-state
97 var hov = api.hovered(); 56 var hov = api.hovered();
98 57
99 function hoverValid() { 58 function hoverValid() {
100 - return hoverMode === hoverModeFlows && 59 + return hoverMode === 'flows' &&
101 hov && (hov.class === 'device'); 60 hov && (hov.class === 'device');
102 } 61 }
103 62
104 - if (api.validateSelectionContext()) { 63 + if (api.somethingSelected()) {
105 wss.sendEvent('requestDeviceLinkFlows', { 64 wss.sendEvent('requestDeviceLinkFlows', {
106 ids: api.selectOrder(), 65 ids: api.selectOrder(),
107 hover: hoverValid() ? hov.id : '' 66 hover: hoverValid() ? hov.id : ''
...@@ -110,14 +69,15 @@ ...@@ -110,14 +69,15 @@
110 } 69 }
111 70
112 function requestRelatedIntents() { 71 function requestRelatedIntents() {
72 + // generates payload based on current hover-state
113 var hov = api.hovered(); 73 var hov = api.hovered();
114 74
115 function hoverValid() { 75 function hoverValid() {
116 - return hoverMode === hoverModeIntents && 76 + return hoverMode === 'intents' &&
117 hov && (hov.class === 'host' || hov.class === 'device'); 77 hov && (hov.class === 'host' || hov.class === 'device');
118 } 78 }
119 79
120 - if (api.validateSelectionContext()) { 80 + if (api.somethingSelected()) {
121 wss.sendEvent('requestRelatedIntents', { 81 wss.sendEvent('requestRelatedIntents', {
122 ids: api.selectOrder(), 82 ids: api.selectOrder(),
123 hover: hoverValid() ? hov.id : '' 83 hover: hoverValid() ? hov.id : ''
...@@ -126,71 +86,75 @@ ...@@ -126,71 +86,75 @@
126 } 86 }
127 87
128 88
129 - // === ----------------------------------------------------- 89 + // === -------------------------------------------------------------
130 - // Traffic requests 90 + // Traffic requests invoked from keystrokes or toolbar buttons...
131 91
132 function cancelTraffic() { 92 function cancelTraffic() {
93 + if (!trafficMode) {
94 + return false;
95 + }
96 +
97 + trafficMode = hoverMode = null;
133 wss.sendEvent('cancelTraffic'); 98 wss.sendEvent('cancelTraffic');
99 + flash.flash('Traffic monitoring canceled');
100 + return true;
134 } 101 }
135 102
136 - // invoked in response to change in selection and/or mouseover/out: 103 + function showAllFlowTraffic() {
137 - function requestTrafficForMode() { 104 + trafficMode = 'allFlow';
138 - if (hoverMode === hoverModeFlows) { 105 + hoverMode = 'all';
139 - requestDeviceLinkFlows(); 106 + wss.sendEvent('requestAllFlowTraffic');
140 - } else if (hoverMode === hoverModeIntents) { 107 + flash.flash('All Flow Traffic');
141 - requestRelatedIntents();
142 } 108 }
109 +
110 + function showAllPortTraffic() {
111 + trafficMode = 'allPort';
112 + hoverMode = 'all';
113 + wss.sendEvent('requestAllPortTraffic');
114 + flash.flash('All Port Traffic');
143 } 115 }
144 116
145 - // === ----------------------------- 117 + function showDeviceLinkFlows () {
146 - // keystroke commands 118 + trafficMode = hoverMode = 'flows';
119 + requestDeviceLinkFlows();
120 + flash.flash('Device Flows');
121 + }
147 122
148 - // keystroke-right-arrow (see topo.js) 123 + function showRelatedIntents () {
149 - function showNextIntentAction() { 124 + trafficMode = hoverMode = 'intents';
150 - hoverMode = hoverModeNone; 125 + requestRelatedIntents();
151 - wss.sendEvent('requestNextRelatedIntent'); 126 + flash.flash('Related Paths');
152 - flash.flash('Next related intent');
153 } 127 }
154 128
155 - // keystroke-left-arrow (see topo.js) 129 + function showPrevIntent() {
156 - function showPrevIntentAction() { 130 + if (trafficMode === 'intents') {
157 - hoverMode = hoverModeNone; 131 + hoverMode = null;
158 wss.sendEvent('requestPrevRelatedIntent'); 132 wss.sendEvent('requestPrevRelatedIntent');
159 flash.flash('Previous related intent'); 133 flash.flash('Previous related intent');
160 } 134 }
161 -
162 - // keystroke-W (see topo.js)
163 - function showSelectedIntentTrafficAction() {
164 - hoverMode = hoverModeNone;
165 - wss.sendEvent('requestSelectedIntentTraffic');
166 - flash.flash('Traffic on Selected Path');
167 } 135 }
168 136
169 - // keystroke-A (see topo.js) 137 + function showNextIntent() {
170 - function showAllFlowTrafficAction() { 138 + if (trafficMode === 'intents') {
171 - hoverMode = hoverModeAll; 139 + hoverMode = null;
172 - wss.sendEvent('requestAllFlowTraffic'); 140 + wss.sendEvent('requestNextRelatedIntent');
173 - flash.flash('All Flow Traffic'); 141 + flash.flash('Next related intent');
142 + }
174 } 143 }
175 144
176 - // keystroke-A (see topo.js) 145 + function showSelectedIntentTraffic() {
177 - function showAllPortTrafficAction() { 146 + if (trafficMode === 'intents') {
178 - hoverMode = hoverModeAll; 147 + hoverMode = null;
179 - wss.sendEvent('requestAllPortTraffic'); 148 + wss.sendEvent('requestSelectedIntentTraffic');
180 - flash.flash('All Port Traffic'); 149 + flash.flash('Traffic on Selected Path');
150 + }
181 } 151 }
182 152
183 - // === -----------------------------
184 - // action buttons on detail panel
185 153
186 - // also, keystroke-V (see topo.js) 154 + // === ------------------------------------------------------
187 - function showRelatedIntentsAction () { 155 + // action buttons on detail panel (multiple selection)
188 - hoverMode = hoverModeIntents;
189 - requestRelatedIntents();
190 - flash.flash('Related Paths');
191 - }
192 156
193 - function addHostIntentAction () { 157 + function addHostIntent () {
194 var so = api.selectOrder(); 158 var so = api.selectOrder();
195 wss.sendEvent('addHostIntent', { 159 wss.sendEvent('addHostIntent', {
196 one: so[0], 160 one: so[0],
...@@ -200,7 +164,7 @@ ...@@ -200,7 +164,7 @@
200 flash.flash('Host-to-Host flow added'); 164 flash.flash('Host-to-Host flow added');
201 } 165 }
202 166
203 - function addMultiSourceIntentAction () { 167 + function addMultiSourceIntent () {
204 var so = api.selectOrder(); 168 var so = api.selectOrder();
205 wss.sendEvent('addMultiSourceIntent', { 169 wss.sendEvent('addMultiSourceIntent', {
206 src: so.slice(0, so.length - 1), 170 src: so.slice(0, so.length - 1),
...@@ -210,12 +174,6 @@ ...@@ -210,12 +174,6 @@
210 flash.flash('Multi-Source flow added'); 174 flash.flash('Multi-Source flow added');
211 } 175 }
212 176
213 - // also, keystroke-F (see topo.js)
214 - function showDeviceLinkFlowsAction () {
215 - hoverMode = hoverModeFlows;
216 - requestDeviceLinkFlows();
217 - flash.flash('Device Flows');
218 - }
219 177
220 178
221 // === ----------------------------------------------------- 179 // === -----------------------------------------------------
...@@ -231,29 +189,26 @@ ...@@ -231,29 +189,26 @@
231 flash = _flash_; 189 flash = _flash_;
232 wss = _wss_; 190 wss = _wss_;
233 191
234 - function initTraffic(_api_) {
235 - api = _api_;
236 - }
237 -
238 - function destroyTraffic() { }
239 -
240 return { 192 return {
241 - initTraffic: initTraffic, 193 + initTraffic: function (_api_) { api = _api_; },
242 - destroyTraffic: destroyTraffic, 194 + destroyTraffic: function () { },
243 -
244 - showTraffic: showTraffic,
245 195
196 + // invoked from toolbar overlay buttons or keystrokes
246 cancelTraffic: cancelTraffic, 197 cancelTraffic: cancelTraffic,
198 + showAllFlowTraffic: showAllFlowTraffic,
199 + showAllPortTraffic: showAllPortTraffic,
200 + showDeviceLinkFlows: showDeviceLinkFlows,
201 + showRelatedIntents: showRelatedIntents,
202 + showPrevIntent: showPrevIntent,
203 + showNextIntent: showNextIntent,
204 + showSelectedIntentTraffic: showSelectedIntentTraffic,
205 +
206 + // invoked from mouseover/mouseout and selection change
247 requestTrafficForMode: requestTrafficForMode, 207 requestTrafficForMode: requestTrafficForMode,
248 - showRelatedIntentsAction: showRelatedIntentsAction, 208 +
249 - addHostIntentAction: addHostIntentAction, 209 + // invoked from buttons on detail (multi-select) panel
250 - addMultiSourceIntentAction: addMultiSourceIntentAction, 210 + addHostIntent: addHostIntent,
251 - showDeviceLinkFlowsAction: showDeviceLinkFlowsAction, 211 + addMultiSourceIntent: addMultiSourceIntent
252 - showNextIntentAction: showNextIntentAction,
253 - showPrevIntentAction: showPrevIntentAction,
254 - showSelectedIntentTrafficAction: showSelectedIntentTrafficAction,
255 - showAllFlowTrafficAction: showAllFlowTrafficAction,
256 - showAllPortTrafficAction: showAllPortTrafficAction
257 }; 212 };
258 }]); 213 }]);
259 }()); 214 }());
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
16 */ 16 */
17 17
18 /* 18 /*
19 - ONOS GUI -- Topology Traffic Module. 19 + ONOS GUI -- Topology Traffic Overlay Module.
20 Defines behavior for viewing different traffic modes. 20 Defines behavior for viewing different traffic modes.
21 Installed as a Topology Overlay. 21 Installed as a Topology Overlay.
22 */ 22 */
...@@ -24,7 +24,13 @@ ...@@ -24,7 +24,13 @@
24 'use strict'; 24 'use strict';
25 25
26 // injected refs 26 // injected refs
27 - var $log; 27 + var $log, tov, tts;
28 +
29 + // NOTE: no internal state here -- see TopoTrafficService for that
30 +
31 + // NOTE: providing button disabling requires too big a refactoring of
32 + // the button factory etc. Will have to be done another time.
33 +
28 34
29 // traffic overlay definition 35 // traffic overlay definition
30 var overlay = { 36 var overlay = {
...@@ -32,26 +38,112 @@ ...@@ -32,26 +38,112 @@
32 glyphId: 'allTraffic', 38 glyphId: 'allTraffic',
33 tooltip: 'Traffic Overlay', 39 tooltip: 'Traffic Overlay',
34 40
35 - activate: activateTraffic, 41 + // NOTE: Traffic glyphs already installed as part of the base ONOS set.
36 - deactivate: deactivateTraffic
37 - };
38 42
39 - // === implementation of overlay API (essentially callbacks) 43 + activate: function () {
40 - function activateTraffic() { 44 + $log.debug("Traffic overlay ACTIVATED");
41 - $log.debug("Topology traffic overlay ACTIVATED"); 45 + },
42 - } 46 +
47 + deactivate: function () {
48 + tts.cancelTraffic();
49 + $log.debug("Traffic overlay DEACTIVATED");
50 + },
51 +
52 + // detail panel button definitions
53 + // (keys match button identifiers, also defined in TrafficOverlay.java)
54 + buttons: {
55 + showDeviceFlows: {
56 + gid: 'flows',
57 + tt: 'Show Device Flows',
58 + cb: function (data) { tts.showDeviceLinkFlows(); }
59 + },
43 60
44 - function deactivateTraffic() { 61 + showRelatedTraffic: {
45 - $log.debug("Topology traffic overlay DEACTIVATED"); 62 + gid: 'relatedIntents',
63 + tt: 'Show Related Traffic',
64 + cb: function (data) { tts.showRelatedIntents(); }
46 } 65 }
66 + },
47 67
68 + // key bindings for traffic overlay toolbar buttons
69 + // NOTE: fully qual. button ID is derived from overlay-id and key-name
70 + keyBindings: {
71 + 0: {
72 + cb: function () { tts.cancelTraffic(); },
73 + tt: 'Cancel traffic monitoring',
74 + gid: 'xMark'
75 + },
76 +
77 + A: {
78 + cb: function () { tts.showAllFlowTraffic(); },
79 + tt: 'Monitor all traffic using flow stats',
80 + gid: 'allTraffic'
81 + },
82 + Q: {
83 + cb: function () { tts.showAllPortTraffic(); },
84 + tt: 'Monitor all traffic using port stats',
85 + gid: 'allTraffic'
86 + },
87 + F: {
88 + cb: function () { tts.showDeviceLinkFlows(); },
89 + tt: 'Show device link flows',
90 + gid: 'flows'
91 + },
92 + V: {
93 + cb: function () { tts.showRelatedIntents(); },
94 + tt: 'Show all related intents',
95 + gid: 'relatedIntents'
96 + },
97 + leftArrow: {
98 + cb: function () { tts.showPrevIntent(); },
99 + tt: 'Show previous related intent',
100 + gid: 'prevIntent'
101 + },
102 + rightArrow: {
103 + cb: function () { tts.showNextIntent(); },
104 + tt: 'Show next related intent',
105 + gid: 'nextIntent'
106 + },
107 + W: {
108 + cb: function () { tts.showSelectedIntentTraffic(); },
109 + tt: 'Monitor traffic of selected intent',
110 + gid: 'intentTraffic'
111 + },
112 +
113 + _keyOrder: [
114 + '0', 'A', 'Q', 'F', 'V', 'leftArrow', 'rightArrow', 'W'
115 + ]
116 + },
117 +
118 + hooks: {
119 + // hook for handling escape key
120 + escape: function () {
121 + // Must return true to consume ESC, false otherwise.
122 + return tts.cancelTraffic();
123 + },
124 +
125 + // hooks for when the selection changes...
126 + empty: function () {
127 + tts.cancelTraffic();
128 + },
129 + single: function (data) {
130 + tts.requestTrafficForMode();
131 + },
132 + multi: function (selectOrder) {
133 + tts.requestTrafficForMode();
134 + tov.addDetailButton('showRelatedTraffic');
135 + }
136 + }
137 + };
48 138
49 // invoke code to register with the overlay service 139 // invoke code to register with the overlay service
50 angular.module('ovTopo') 140 angular.module('ovTopo')
51 - .run(['$log', 'TopoOverlayService', 141 + .run(['$log', 'TopoOverlayService', 'TopoTrafficService',
52 142
53 - function (_$log_, tov) { 143 + function (_$log_, _tov_, _tts_) {
54 $log = _$log_; 144 $log = _$log_;
145 + tov = _tov_;
146 + tts = _tts_;
55 tov.register(overlay); 147 tov.register(overlay);
56 }]); 148 }]);
57 149
......