ONOS-303 Added ability to add mult-source intent from GUI.
Fixed treatment of selection & hover modes. Change-Id: Idf47b6a15b56ea96b9edaeeb034fad0f205af6e3
Showing
4 changed files
with
163 additions
and
88 deletions
... | @@ -15,6 +15,7 @@ | ... | @@ -15,6 +15,7 @@ |
15 | */ | 15 | */ |
16 | package org.onlab.onos.gui; | 16 | package org.onlab.onos.gui; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
18 | import com.fasterxml.jackson.databind.node.ArrayNode; | 19 | import com.fasterxml.jackson.databind.node.ArrayNode; |
19 | import com.fasterxml.jackson.databind.node.ObjectNode; | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
20 | import org.eclipse.jetty.websocket.WebSocket; | 21 | import org.eclipse.jetty.websocket.WebSocket; |
... | @@ -23,20 +24,25 @@ import org.onlab.onos.cluster.ClusterEventListener; | ... | @@ -23,20 +24,25 @@ import org.onlab.onos.cluster.ClusterEventListener; |
23 | import org.onlab.onos.cluster.ControllerNode; | 24 | import org.onlab.onos.cluster.ControllerNode; |
24 | import org.onlab.onos.core.ApplicationId; | 25 | import org.onlab.onos.core.ApplicationId; |
25 | import org.onlab.onos.core.CoreService; | 26 | import org.onlab.onos.core.CoreService; |
27 | +import org.onlab.onos.net.ConnectPoint; | ||
26 | import org.onlab.onos.net.Device; | 28 | import org.onlab.onos.net.Device; |
27 | import org.onlab.onos.net.Host; | 29 | import org.onlab.onos.net.Host; |
28 | import org.onlab.onos.net.HostId; | 30 | import org.onlab.onos.net.HostId; |
31 | +import org.onlab.onos.net.HostLocation; | ||
29 | import org.onlab.onos.net.Link; | 32 | import org.onlab.onos.net.Link; |
30 | import org.onlab.onos.net.device.DeviceEvent; | 33 | import org.onlab.onos.net.device.DeviceEvent; |
31 | import org.onlab.onos.net.device.DeviceListener; | 34 | import org.onlab.onos.net.device.DeviceListener; |
32 | import org.onlab.onos.net.flow.DefaultTrafficSelector; | 35 | import org.onlab.onos.net.flow.DefaultTrafficSelector; |
33 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; | 36 | import org.onlab.onos.net.flow.DefaultTrafficTreatment; |
37 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
38 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
34 | import org.onlab.onos.net.host.HostEvent; | 39 | import org.onlab.onos.net.host.HostEvent; |
35 | import org.onlab.onos.net.host.HostListener; | 40 | import org.onlab.onos.net.host.HostListener; |
36 | import org.onlab.onos.net.intent.HostToHostIntent; | 41 | import org.onlab.onos.net.intent.HostToHostIntent; |
37 | import org.onlab.onos.net.intent.Intent; | 42 | import org.onlab.onos.net.intent.Intent; |
38 | import org.onlab.onos.net.intent.IntentEvent; | 43 | import org.onlab.onos.net.intent.IntentEvent; |
39 | import org.onlab.onos.net.intent.IntentListener; | 44 | import org.onlab.onos.net.intent.IntentListener; |
45 | +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; | ||
40 | import org.onlab.onos.net.link.LinkEvent; | 46 | import org.onlab.onos.net.link.LinkEvent; |
41 | import org.onlab.onos.net.link.LinkListener; | 47 | import org.onlab.onos.net.link.LinkListener; |
42 | import org.onlab.osgi.ServiceDirectory; | 48 | import org.onlab.osgi.ServiceDirectory; |
... | @@ -119,7 +125,7 @@ public class TopologyViewWebSocket | ... | @@ -119,7 +125,7 @@ public class TopologyViewWebSocket |
119 | super(directory); | 125 | super(directory); |
120 | 126 | ||
121 | intentFilter = new TopologyViewIntentFilter(intentService, deviceService, | 127 | intentFilter = new TopologyViewIntentFilter(intentService, deviceService, |
122 | - hostService, linkService); | 128 | + hostService, linkService); |
123 | appId = directory.get(CoreService.class).registerApplication(APP_ID); | 129 | appId = directory.get(CoreService.class).registerApplication(APP_ID); |
124 | } | 130 | } |
125 | 131 | ||
... | @@ -195,8 +201,11 @@ public class TopologyViewWebSocket | ... | @@ -195,8 +201,11 @@ public class TopologyViewWebSocket |
195 | requestDetails(event); | 201 | requestDetails(event); |
196 | } else if (type.equals("updateMeta")) { | 202 | } else if (type.equals("updateMeta")) { |
197 | updateMetaUi(event); | 203 | updateMetaUi(event); |
204 | + | ||
198 | } else if (type.equals("addHostIntent")) { | 205 | } else if (type.equals("addHostIntent")) { |
199 | createHostIntent(event); | 206 | createHostIntent(event); |
207 | + } else if (type.equals("addMultiSourceIntent")) { | ||
208 | + createMultiSourceIntent(event); | ||
200 | 209 | ||
201 | } else if (type.equals("requestTraffic")) { | 210 | } else if (type.equals("requestTraffic")) { |
202 | requestTraffic(event); | 211 | requestTraffic(event); |
... | @@ -268,6 +277,7 @@ public class TopologyViewWebSocket | ... | @@ -268,6 +277,7 @@ public class TopologyViewWebSocket |
268 | } | 277 | } |
269 | } | 278 | } |
270 | 279 | ||
280 | + | ||
271 | // Creates host-to-host intent. | 281 | // Creates host-to-host intent. |
272 | private void createHostIntent(ObjectNode event) { | 282 | private void createHostIntent(ObjectNode event) { |
273 | ObjectNode payload = payload(event); | 283 | ObjectNode payload = payload(event); |
... | @@ -276,19 +286,66 @@ public class TopologyViewWebSocket | ... | @@ -276,19 +286,66 @@ public class TopologyViewWebSocket |
276 | HostId one = hostId(string(payload, "one")); | 286 | HostId one = hostId(string(payload, "one")); |
277 | HostId two = hostId(string(payload, "two")); | 287 | HostId two = hostId(string(payload, "two")); |
278 | 288 | ||
279 | - HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two, | 289 | + HostToHostIntent intent = |
280 | - DefaultTrafficSelector.builder().build(), | 290 | + new HostToHostIntent(appId, one, two, |
281 | - DefaultTrafficTreatment.builder().build()); | 291 | + DefaultTrafficSelector.builder().build(), |
292 | + DefaultTrafficTreatment.builder().build()); | ||
293 | + startMonitoring(event); | ||
294 | + intentService.submit(intent); | ||
295 | + } | ||
296 | + | ||
297 | + // Creates multi-source-to-single-dest intent. | ||
298 | + private void createMultiSourceIntent(ObjectNode event) { | ||
299 | + ObjectNode payload = payload(event); | ||
300 | + long id = number(event, "sid"); | ||
301 | + // TODO: add protection against device ids and non-existent hosts. | ||
302 | + Set<HostId> src = getHostIds((ArrayNode) payload.path("src")); | ||
303 | + HostId dst = hostId(string(payload, "dst")); | ||
304 | + Host dstHost = hostService.getHost(dst); | ||
305 | + | ||
306 | + Set<ConnectPoint> ingressPoints = getHostLocations(src); | ||
307 | + | ||
308 | + // FIXME: clearly, this is not enough | ||
309 | + TrafficSelector selector = DefaultTrafficSelector.builder() | ||
310 | + .matchEthDst(dstHost.mac()).build(); | ||
311 | + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); | ||
312 | + | ||
313 | + MultiPointToSinglePointIntent intent = | ||
314 | + new MultiPointToSinglePointIntent(appId, selector, treatment, | ||
315 | + ingressPoints, dstHost.location()); | ||
282 | trafficEvent = event; | 316 | trafficEvent = event; |
283 | - intentService.submit(hostIntent); | 317 | + intentService.submit(intent); |
284 | } | 318 | } |
285 | 319 | ||
320 | + private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) { | ||
321 | + Set<ConnectPoint> points = new HashSet<>(); | ||
322 | + for (HostId hostId : hostIds) { | ||
323 | + points.add(getHostLocation(hostId)); | ||
324 | + } | ||
325 | + return points; | ||
326 | + } | ||
327 | + | ||
328 | + private HostLocation getHostLocation(HostId hostId) { | ||
329 | + return hostService.getHost(hostId).location(); | ||
330 | + } | ||
331 | + | ||
332 | + // Produces a list of host ids from the specified JSON array. | ||
333 | + private Set<HostId> getHostIds(ArrayNode ids) { | ||
334 | + Set<HostId> hostIds = new HashSet<>(); | ||
335 | + for (JsonNode id : ids) { | ||
336 | + hostIds.add(hostId(id.asText())); | ||
337 | + } | ||
338 | + return hostIds; | ||
339 | + } | ||
340 | + | ||
341 | + | ||
286 | private synchronized long startMonitoring(ObjectNode event) { | 342 | private synchronized long startMonitoring(ObjectNode event) { |
287 | - if (trafficTask == null) { | 343 | + if (trafficTask != null) { |
288 | - trafficEvent = event; | 344 | + stopMonitoring(); |
289 | - trafficTask = new TrafficMonitor(); | ||
290 | - timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC); | ||
291 | } | 345 | } |
346 | + trafficEvent = event; | ||
347 | + trafficTask = new TrafficMonitor(); | ||
348 | + timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC); | ||
292 | return number(event, "sid"); | 349 | return number(event, "sid"); |
293 | } | 350 | } |
294 | 351 | ... | ... |
... | @@ -132,11 +132,11 @@ svg .node.host circle { | ... | @@ -132,11 +132,11 @@ svg .node.host circle { |
132 | /* LINKS */ | 132 | /* LINKS */ |
133 | 133 | ||
134 | #topo svg .link { | 134 | #topo svg .link { |
135 | - opacity: .7; | 135 | + opacity: .9; |
136 | } | 136 | } |
137 | 137 | ||
138 | #topo svg .link.inactive { | 138 | #topo svg .link.inactive { |
139 | - opacity: .2; | 139 | + opacity: .5; |
140 | stroke-dasharray: 8 4; | 140 | stroke-dasharray: 8 4; |
141 | } | 141 | } |
142 | 142 | ||
... | @@ -285,7 +285,7 @@ svg .node.host circle { | ... | @@ -285,7 +285,7 @@ svg .node.host circle { |
285 | padding: 2px 6px; | 285 | padding: 2px 6px; |
286 | font-size: 9pt; | 286 | font-size: 9pt; |
287 | cursor: pointer; | 287 | cursor: pointer; |
288 | - width: 50%; | 288 | + width: 200px; |
289 | text-align: center; | 289 | text-align: center; |
290 | 290 | ||
291 | /* theme specific... */ | 291 | /* theme specific... */ | ... | ... |
... | @@ -72,9 +72,9 @@ | ... | @@ -72,9 +72,9 @@ |
72 | topo: { | 72 | topo: { |
73 | linkBaseColor: '#666', | 73 | linkBaseColor: '#666', |
74 | linkInColor: '#66f', | 74 | linkInColor: '#66f', |
75 | - linkInWidth: 14, | 75 | + linkInWidth: 12, |
76 | linkOutColor: '#f00', | 76 | linkOutColor: '#f00', |
77 | - linkOutWidth: 14 | 77 | + linkOutWidth: 10 |
78 | }, | 78 | }, |
79 | icons: { | 79 | icons: { |
80 | w: 30, | 80 | w: 30, |
... | @@ -148,8 +148,7 @@ | ... | @@ -148,8 +148,7 @@ |
148 | P: togglePorts, | 148 | P: togglePorts, |
149 | U: [unpin, 'Unpin node'], | 149 | U: [unpin, 'Unpin node'], |
150 | R: [resetZoomPan, 'Reset zoom/pan'], | 150 | R: [resetZoomPan, 'Reset zoom/pan'], |
151 | - H: [cycleHoverMode, 'Cycle hover mode'], | 151 | + V: [showTrafficAction, 'Show related traffic'], |
152 | - V: [showTrafficAction, 'Show traffic'], | ||
153 | A: [showAllTrafficAction, 'Show all traffic'], | 152 | A: [showAllTrafficAction, 'Show all traffic'], |
154 | F: [showDeviceLinkFlowsAction, 'Show device link flows'], | 153 | F: [showDeviceLinkFlowsAction, 'Show device link flows'], |
155 | esc: handleEscape | 154 | esc: handleEscape |
... | @@ -191,10 +190,13 @@ | ... | @@ -191,10 +190,13 @@ |
191 | onosOrder = [], | 190 | onosOrder = [], |
192 | oiBox, | 191 | oiBox, |
193 | oiShowMaster = false, | 192 | oiShowMaster = false, |
194 | - hoverModes = [ 'none', 'intents', 'flows'], | ||
195 | - hoverMode = 0, | ||
196 | portLabelsOn = false; | 193 | portLabelsOn = false; |
197 | 194 | ||
195 | + var hoverModeAll = 1, | ||
196 | + hoverModeFlows = 2, | ||
197 | + hoverModeIntents = 3, | ||
198 | + hoverMode = hoverModeFlows; | ||
199 | + | ||
198 | // D3 selections | 200 | // D3 selections |
199 | var svg, | 201 | var svg, |
200 | zoomPanContainer, | 202 | zoomPanContainer, |
... | @@ -327,14 +329,6 @@ | ... | @@ -327,14 +329,6 @@ |
327 | }); | 329 | }); |
328 | } | 330 | } |
329 | 331 | ||
330 | - function cycleHoverMode(view) { | ||
331 | - hoverMode++; | ||
332 | - if (hoverMode === hoverModes.length) { | ||
333 | - hoverMode = 0; | ||
334 | - } | ||
335 | - view.flash('Mode: ' + hoverModes[hoverMode]); | ||
336 | - } | ||
337 | - | ||
338 | function togglePorts(view) { | 332 | function togglePorts(view) { |
339 | view.alert('togglePorts() callback') | 333 | view.alert('togglePorts() callback') |
340 | } | 334 | } |
... | @@ -829,6 +823,14 @@ | ... | @@ -829,6 +823,14 @@ |
829 | function getSelId(idx) { | 823 | function getSelId(idx) { |
830 | return getSel(idx).obj.id; | 824 | return getSel(idx).obj.id; |
831 | } | 825 | } |
826 | + function getSelIds(start, endOffset) { | ||
827 | + var end = selectOrder.length - endOffset; | ||
828 | + var ids = []; | ||
829 | + selectOrder.slice(start, end).forEach(function (d) { | ||
830 | + ids.push(getSelId(d)); | ||
831 | + }); | ||
832 | + return ids; | ||
833 | + } | ||
832 | function allSelectionsClass(cls) { | 834 | function allSelectionsClass(cls) { |
833 | for (var i=0, n=nSel(); i<n; i++) { | 835 | for (var i=0, n=nSel(); i<n; i++) { |
834 | if (getSel(i).obj.class !== cls) { | 836 | if (getSel(i).obj.class !== cls) { |
... | @@ -876,69 +878,92 @@ | ... | @@ -876,69 +878,92 @@ |
876 | sendMessage('requestDetails', payload); | 878 | sendMessage('requestDetails', payload); |
877 | } | 879 | } |
878 | 880 | ||
879 | - function addIntentAction() { | 881 | + function addHostIntentAction() { |
880 | sendMessage('addHostIntent', { | 882 | sendMessage('addHostIntent', { |
881 | - one: getSelId(0), | 883 | + one: selectOrder[0], |
882 | - two: getSelId(1), | 884 | + two: selectOrder[1], |
883 | - ids: [ getSelId(0), getSelId(1) ] | 885 | + ids: selectOrder |
884 | }); | 886 | }); |
885 | - network.view.flash('Host-to-Host connectivity added'); | 887 | + network.view.flash('Host-to-Host flow added'); |
886 | } | 888 | } |
887 | 889 | ||
888 | - function showTrafficAction() { | 890 | + function addMultiSourceIntentAction() { |
889 | - cancelTraffic(); | 891 | + sendMessage('addMultiSourceIntent', { |
890 | - hoverMode = 1; | 892 | + src: selectOrder.slice(0, selectOrder.length - 1), |
891 | - showSelectTraffic(); | 893 | + dst: selectOrder[selectOrder.length - 1], |
892 | - network.view.flash('Related Traffic'); | 894 | + ids: selectOrder |
895 | + }); | ||
896 | + network.view.flash('Multi-Source flow added'); | ||
893 | } | 897 | } |
894 | 898 | ||
899 | + | ||
895 | function cancelTraffic() { | 900 | function cancelTraffic() { |
896 | sendMessage('cancelTraffic', {}); | 901 | sendMessage('cancelTraffic', {}); |
897 | } | 902 | } |
898 | 903 | ||
899 | - function showSelectTraffic() { | 904 | + function requestTrafficForMode() { |
900 | - // if nothing is hovered over, and nothing selected, send cancel request | 905 | + if (hoverMode === hoverModeAll) { |
901 | - if (!hovered && nSel() === 0) { | 906 | + requestAllTraffic(); |
902 | - cancelTraffic(); | 907 | + } else if (hoverMode === hoverModeFlows) { |
903 | - return; | 908 | + requestDeviceLinkFlows(); |
909 | + } else if (hoverMode === hoverModeIntents) { | ||
910 | + requestSelectTraffic(); | ||
904 | } | 911 | } |
912 | + } | ||
905 | 913 | ||
906 | - // NOTE: hover is only populated if "show traffic on hover" is | 914 | + function showTrafficAction() { |
907 | - // toggled on, and the item hovered is a host or a device... | 915 | + hoverMode = hoverModeIntents; |
908 | - var hoverId = (trafficHover() && hovered && | 916 | + requestSelectTraffic(); |
909 | - (hovered.class === 'host' || hovered.class === 'device')) | 917 | + network.view.flash('Related Traffic'); |
918 | + } | ||
919 | + | ||
920 | + function requestSelectTraffic() { | ||
921 | + if (validateSelectionContext()) { | ||
922 | + var hoverId = (hoverMode === hoverModeIntents && hovered && | ||
923 | + (hovered.class === 'host' || hovered.class === 'device')) | ||
910 | ? hovered.id : ''; | 924 | ? hovered.id : ''; |
911 | - sendMessage('requestTraffic', { | 925 | + sendMessage('requestTraffic', { |
912 | - ids: selectOrder, | 926 | + ids: selectOrder, |
913 | - hover: hoverId | 927 | + hover: hoverId |
914 | - }); | 928 | + }); |
929 | + } | ||
915 | } | 930 | } |
916 | 931 | ||
932 | + | ||
933 | + function showDeviceLinkFlowsAction() { | ||
934 | + hoverMode = hoverModeFlows; | ||
935 | + requestDeviceLinkFlows(); | ||
936 | + network.view.flash('Device Flows'); | ||
937 | + } | ||
938 | + | ||
939 | + function requestDeviceLinkFlows() { | ||
940 | + if (validateSelectionContext()) { | ||
941 | + var hoverId = (hoverMode === hoverModeFlows && hovered && | ||
942 | + (hovered.class === 'device')) ? hovered.id : ''; | ||
943 | + sendMessage('requestDeviceLinkFlows', { | ||
944 | + ids: selectOrder, | ||
945 | + hover: hoverId | ||
946 | + }); | ||
947 | + } | ||
948 | + } | ||
949 | + | ||
950 | + | ||
917 | function showAllTrafficAction() { | 951 | function showAllTrafficAction() { |
918 | - cancelTraffic(); | 952 | + hoverMode = hoverModeAll; |
919 | - sendMessage('requestAllTraffic', {}); | 953 | + requestAllTraffic(); |
920 | network.view.flash('All Traffic'); | 954 | network.view.flash('All Traffic'); |
921 | } | 955 | } |
922 | 956 | ||
923 | - function showDeviceLinkFlowsAction() { | 957 | + function requestAllTraffic() { |
924 | - cancelTraffic(); | 958 | + sendMessage('requestAllTraffic', {}); |
925 | - hoverMode = 2; | ||
926 | - showDeviceLinkFlows(); | ||
927 | - network.view.flash('Device Flows'); | ||
928 | } | 959 | } |
929 | 960 | ||
930 | - function showDeviceLinkFlows() { | 961 | + function validateSelectionContext() { |
931 | - // if nothing is hovered over, and nothing selected, send cancel request | ||
932 | if (!hovered && nSel() === 0) { | 962 | if (!hovered && nSel() === 0) { |
933 | cancelTraffic(); | 963 | cancelTraffic(); |
934 | - return; | 964 | + return false; |
935 | } | 965 | } |
936 | - var hoverId = (flowsHover() && hovered && hovered.class === 'device') ? | 966 | + return true; |
937 | - hovered.id : ''; | ||
938 | - sendMessage('requestDeviceLinkFlows', { | ||
939 | - ids: selectOrder, | ||
940 | - hover: hoverId | ||
941 | - }); | ||
942 | } | 967 | } |
943 | 968 | ||
944 | // TODO: these should be moved out to utility module. | 969 | // TODO: these should be moved out to utility module. |
... | @@ -1547,20 +1572,12 @@ | ... | @@ -1547,20 +1572,12 @@ |
1547 | 1572 | ||
1548 | function nodeMouseOver(d) { | 1573 | function nodeMouseOver(d) { |
1549 | hovered = d; | 1574 | hovered = d; |
1550 | - if (trafficHover() && (d.class === 'host' || d.class === 'device')) { | 1575 | + requestTrafficForMode(); |
1551 | - showSelectTraffic(); | ||
1552 | - } else if (flowsHover() && (d.class === 'device')) { | ||
1553 | - showDeviceLinkFlows(); | ||
1554 | - } | ||
1555 | } | 1576 | } |
1556 | 1577 | ||
1557 | function nodeMouseOut(d) { | 1578 | function nodeMouseOut(d) { |
1558 | hovered = null; | 1579 | hovered = null; |
1559 | - if (trafficHover() && (d.class === 'host' || d.class === 'device')) { | 1580 | + requestTrafficForMode(); |
1560 | - showSelectTraffic(); | ||
1561 | - } else if (flowsHover() && (d.class === 'device')) { | ||
1562 | - showDeviceLinkFlows(); | ||
1563 | - } | ||
1564 | } | 1581 | } |
1565 | 1582 | ||
1566 | function addHostIcon(node, radius, iid) { | 1583 | function addHostIcon(node, radius, iid) { |
... | @@ -2002,22 +2019,29 @@ | ... | @@ -2002,22 +2019,29 @@ |
2002 | function updateDetailPane() { | 2019 | function updateDetailPane() { |
2003 | var nSel = selectOrder.length; | 2020 | var nSel = selectOrder.length; |
2004 | if (!nSel) { | 2021 | if (!nSel) { |
2005 | - detailPane.hide(); | 2022 | + emptySelect(); |
2006 | - cancelTraffic(); | ||
2007 | } else if (nSel === 1) { | 2023 | } else if (nSel === 1) { |
2008 | singleSelect(); | 2024 | singleSelect(); |
2025 | + requestTrafficForMode(); | ||
2009 | } else { | 2026 | } else { |
2010 | multiSelect(); | 2027 | multiSelect(); |
2011 | } | 2028 | } |
2012 | } | 2029 | } |
2013 | 2030 | ||
2031 | + function emptySelect() { | ||
2032 | + detailPane.hide(); | ||
2033 | + cancelTraffic(); | ||
2034 | + } | ||
2035 | + | ||
2014 | function singleSelect() { | 2036 | function singleSelect() { |
2037 | + // NOTE: detail is shown from showDetails event callback | ||
2015 | requestDetails(); | 2038 | requestDetails(); |
2016 | - // NOTE: detail pane will be shown from showDetails event callback | 2039 | + requestTrafficForMode(); |
2017 | } | 2040 | } |
2018 | 2041 | ||
2019 | function multiSelect() { | 2042 | function multiSelect() { |
2020 | populateMultiSelect(); | 2043 | populateMultiSelect(); |
2044 | + requestTrafficForMode(); | ||
2021 | } | 2045 | } |
2022 | 2046 | ||
2023 | function addSep(tbody) { | 2047 | function addSep(tbody) { |
... | @@ -2127,7 +2151,9 @@ | ... | @@ -2127,7 +2151,9 @@ |
2127 | addAction(detailPane, 'Show Related Traffic', showTrafficAction); | 2151 | addAction(detailPane, 'Show Related Traffic', showTrafficAction); |
2128 | // if exactly two hosts are selected, also want 'add host intent' | 2152 | // if exactly two hosts are selected, also want 'add host intent' |
2129 | if (nSel() === 2 && allSelectionsClass('host')) { | 2153 | if (nSel() === 2 && allSelectionsClass('host')) { |
2130 | - addAction(detailPane, 'Add Host-to-Host Intent', addIntentAction); | 2154 | + addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction); |
2155 | + } else if (nSel() >= 2 && allSelectionsClass('host')) { | ||
2156 | + addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction); | ||
2131 | } | 2157 | } |
2132 | } | 2158 | } |
2133 | 2159 | ||
... | @@ -2239,14 +2265,6 @@ | ... | @@ -2239,14 +2265,6 @@ |
2239 | return false; | 2265 | return false; |
2240 | } | 2266 | } |
2241 | 2267 | ||
2242 | - function trafficHover() { | ||
2243 | - return hoverModes[hoverMode] === 'intents'; | ||
2244 | - } | ||
2245 | - | ||
2246 | - function flowsHover() { | ||
2247 | - return hoverModes[hoverMode] === 'flows'; | ||
2248 | - } | ||
2249 | - | ||
2250 | function loadGlyphs(svg) { | 2268 | function loadGlyphs(svg) { |
2251 | var defs = svg.append('defs'); | 2269 | var defs = svg.append('defs'); |
2252 | gly.defBird(defs); | 2270 | gly.defBird(defs); | ... | ... |
-
Please register or login to post a comment