Committed by
Gerrit Code Review
ONOS-1479 -- GUI - augmenting topology view for extensibility: WIP.
Change-Id: I11820a9ff8f446c0d10a0311cee5ce448c15f402
Showing
22 changed files
with
832 additions
and
467 deletions
| ... | @@ -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 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + // detail panel button definitions | ||
| 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); | ||
| 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 | + }, | ||
| 61 | + | ||
| 62 | + // Key bindings for traffic overlay buttons | ||
| 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 | + }, | ||
| 32 | 86 | ||
| 33 | - // button callbacks matching button identifiers | 87 | + R: { |
| 34 | - buttonActions: { | 88 | + cb: buttonCallback, |
| 35 | - foo: fooCb, | 89 | + tt: 'Uses the R key', |
| 36 | - bar: barCb | 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 | + } | ||
| 37 | } | 125 | } |
| 38 | - }; | ||
| 39 | 126 | ||
| 40 | - function fooCb(data) { | 127 | + }; |
| 41 | - $log.debug('FOO callback with data:', data); | ||
| 42 | - } | ||
| 43 | 128 | ||
| 44 | - function barCb(data) { | 129 | + // invoked when the escape key is pressed |
| 45 | - $log.debug('BAR callback with data:', data); | 130 | + function cancelState() { |
| 131 | + if (someStateValue) { | ||
| 132 | + someStateValue = false; | ||
| 133 | + // we consumed the ESC event | ||
| 134 | + return true; | ||
| 135 | + } | ||
| 136 | + return false; | ||
| 46 | } | 137 | } |
| 47 | 138 | ||
| 48 | - // === implementation of overlay API (essentially callbacks) | 139 | + function buttonCallback(x) { |
| 49 | - function activateOverlay() { | 140 | + $log.debug('Toolbar-button callback', x); |
| 50 | - $log.debug("sample topology overlay ACTIVATED"); | ||
| 51 | } | 141 | } |
| 52 | 142 | ||
| 53 | - function deactivateOverlay() { | 143 | + function selectionCallback(x, d) { |
| 54 | - $log.debug("sample topology overlay DEACTIVATED"); | 144 | + $log.debug('Selection callback', x, d); |
| 55 | } | 145 | } |
| 56 | 146 | ||
| 57 | - | ||
| 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,9 +164,43 @@ | ... | @@ -162,9 +164,43 @@ |
| 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 = []; | ||
| 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); | ||
| 165 | } | 197 | } |
| 166 | } | 198 | } |
| 167 | 199 | ||
| 200 | + function rowClassed(classes, bool) { | ||
| 201 | + currentRow.classed(classes, bool); | ||
| 202 | + } | ||
| 203 | + | ||
| 168 | function show(cb) { | 204 | function show(cb) { |
| 169 | rotateArrowLeft(arrowDiv); | 205 | rotateArrowLeft(arrowDiv); |
| 170 | panel.show(cb); | 206 | panel.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) { | ||
| 170 | - _id = current.mkId(btn.id); | ||
| 171 | - _gid = current.mkGid(btn.gid); | ||
| 172 | - action = current.buttonActions[btn.id] || function () {}; | ||
| 173 | } | 233 | } |
| 234 | + }); | ||
| 235 | + } | ||
| 236 | + | ||
| 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 | + | ||
| 174 | 249 | ||
| 175 | - _cb = function () { action(data); }; | 250 | + // === ----------------------------------------------------- |
| 251 | + // Hooks for overlays | ||
| 176 | 252 | ||
| 177 | - addFn({ id: _id, gid: _gid, cb: _cb, tt: btn.tt}); | 253 | + function _hook(x) { |
| 254 | + var h = current && current.hooks; | ||
| 255 | + return h && fs.isF(h[x]); | ||
| 256 | + } | ||
| 257 | + | ||
| 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', | 198 | + tps.addAction({ |
| 201 | - cb: tts.showRelatedIntentsAction, | 199 | + id: 'host-flow-btn', |
| 202 | - tt: 'Show Related Traffic' | 200 | + gid: 'endstation', |
| 203 | - }); | 201 | + cb: tts.addHostIntent, |
| 204 | - | 202 | + tt: 'Create Host-to-Host Flow' |
| 205 | - // add other actions, based on what is selected... | 203 | + }); |
| 206 | - if (nSel() === 2 && allSelectionsClass('host')) { | 204 | + } else if (nSel() >= 2) { |
| 207 | - tps.addAction({ | 205 | + tps.addAction({ |
| 208 | - id: 'host-flow-btn', | 206 | + id: 'mult-src-flow-btn', |
| 209 | - gid: 'endstation', | 207 | + gid: 'flows', |
| 210 | - cb: tts.addHostIntentAction, | 208 | + cb: tts.addMultiSourceIntent, |
| 211 | - tt: 'Create Host-to-Host Flow' | 209 | + tt: 'Create Multi-Source Flow' |
| 212 | - }); | 210 | + }); |
| 213 | - } else if (nSel() >= 2 && allSelectionsClass('host')) { | 211 | + } |
| 214 | - tps.addAction({ | ||
| 215 | - id: 'mult-src-flow-btn', | ||
| 216 | - gid: 'flows', | ||
| 217 | - cb: tts.addMultiSourceIntentAction, | ||
| 218 | - tt: 'Create Multi-Source Flow' | ||
| 219 | - }); | ||
| 220 | } | 212 | } |
| 221 | - | ||
| 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 ⇧'; | ||
| 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 | }]; |
| 175 | + tov.augmentRbset(rset, switchOverlayActions); | ||
| 176 | + toolbar.addRadioSet('topo-overlays', rset); | ||
| 177 | + } | ||
| 167 | 178 | ||
| 168 | - tov.list().forEach(function (key) { | 179 | + // invoked by overlay service to switch out old buttons and switch in new |
| 169 | - var ov = tov.overlay(key); | 180 | + function switchOverlayActions(oid, keyBindings) { |
| 170 | - rset.push({ | 181 | + var prohibits = [], |
| 171 | - gid: ov._glyphId, | 182 | + kb = fs.isO(keyBindings) || {}, |
| 172 | - tooltip: (ov.tooltip || '(no tooltip)'), | 183 | + order = fs.isA(kb._keyOrder) || []; |
| 173 | - cb: function () { | 184 | + |
| 174 | - tov.tbSelection(ov.overlayId); | 185 | + if (keyBindings && !keyBindings._keyOrder) { |
| 175 | - } | 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(); |
| 180 | - } | 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 | ||
| 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() | 30 | + hovered() |
| 33 | - updateLinks() | 31 | + somethingSelected() |
| 34 | - findLinkById( id ) | 32 | + selectOrder() |
| 35 | - hovered() | ||
| 36 | - validateSelectionContext() | ||
| 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 | 53 | ||
| 93 | - // === ----------------------------------------------------- | ||
| 94 | - // Helper functions | ||
| 95 | - | ||
| 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 | - } | ||
| 143 | } | 108 | } |
| 144 | 109 | ||
| 145 | - // === ----------------------------- | 110 | + function showAllPortTraffic() { |
| 146 | - // keystroke commands | 111 | + trafficMode = 'allPort'; |
| 112 | + hoverMode = 'all'; | ||
| 113 | + wss.sendEvent('requestAllPortTraffic'); | ||
| 114 | + flash.flash('All Port Traffic'); | ||
| 115 | + } | ||
| 147 | 116 | ||
| 148 | - // keystroke-right-arrow (see topo.js) | 117 | + function showDeviceLinkFlows () { |
| 149 | - function showNextIntentAction() { | 118 | + trafficMode = hoverMode = 'flows'; |
| 150 | - hoverMode = hoverModeNone; | 119 | + requestDeviceLinkFlows(); |
| 151 | - wss.sendEvent('requestNextRelatedIntent'); | 120 | + flash.flash('Device Flows'); |
| 152 | - flash.flash('Next related intent'); | ||
| 153 | } | 121 | } |
| 154 | 122 | ||
| 155 | - // keystroke-left-arrow (see topo.js) | 123 | + function showRelatedIntents () { |
| 156 | - function showPrevIntentAction() { | 124 | + trafficMode = hoverMode = 'intents'; |
| 157 | - hoverMode = hoverModeNone; | 125 | + requestRelatedIntents(); |
| 158 | - wss.sendEvent('requestPrevRelatedIntent'); | 126 | + flash.flash('Related Paths'); |
| 159 | - flash.flash('Previous related intent'); | ||
| 160 | } | 127 | } |
| 161 | 128 | ||
| 162 | - // keystroke-W (see topo.js) | 129 | + function showPrevIntent() { |
| 163 | - function showSelectedIntentTrafficAction() { | 130 | + if (trafficMode === 'intents') { |
| 164 | - hoverMode = hoverModeNone; | 131 | + hoverMode = null; |
| 165 | - wss.sendEvent('requestSelectedIntentTraffic'); | 132 | + wss.sendEvent('requestPrevRelatedIntent'); |
| 166 | - flash.flash('Traffic on Selected Path'); | 133 | + flash.flash('Previous related intent'); |
| 134 | + } | ||
| 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 | 42 | + |
| 37 | - }; | 43 | + activate: function () { |
| 44 | + $log.debug("Traffic overlay ACTIVATED"); | ||
| 45 | + }, | ||
| 46 | + | ||
| 47 | + deactivate: function () { | ||
| 48 | + tts.cancelTraffic(); | ||
| 49 | + $log.debug("Traffic overlay DEACTIVATED"); | ||
| 50 | + }, | ||
| 38 | 51 | ||
| 39 | - // === implementation of overlay API (essentially callbacks) | 52 | + // detail panel button definitions |
| 40 | - function activateTraffic() { | 53 | + // (keys match button identifiers, also defined in TrafficOverlay.java) |
| 41 | - $log.debug("Topology traffic overlay ACTIVATED"); | 54 | + buttons: { |
| 42 | - } | 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', |
| 46 | - } | 63 | + tt: 'Show Related Traffic', |
| 64 | + cb: function (data) { tts.showRelatedIntents(); } | ||
| 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 | ... | ... |
-
Please register or login to post a comment