Thomas Vachuska
Committed by Gerrit Code Review

GUI fixes/breaks.

Change-Id: Ic5c8b087cc32506162153b2756a677c7d9e3bdd7
......@@ -66,7 +66,7 @@ import static org.slf4j.LoggerFactory.getLogger;
public class DefaultTopologyProvider extends AbstractProvider
implements TopologyProvider {
private static final int MAX_THREADS = 8;
private static final int MAX_THREADS = 32;
private static final int DEFAULT_MAX_EVENTS = 1000;
private static final int DEFAULT_MAX_IDLE_MS = 10;
private static final int DEFAULT_MAX_BATCH_MS = 50;
......
from mininet.topo import Topo
class MyTopo( Topo ):
"10 'floating' switch topology"
def __init__( self ):
# Initialize topology
Topo.__init__( self )
sw_list = []
swC = self.addSwitch('sc', dpid = 'ffffffff00000001')
for i in range(1, 201):
switch=self.addSwitch('s'+str(i), dpid = str(i).zfill(16))
self.addLink(switch,swC)
sw_list.append(switch)
topos = { 'mytopo': ( lambda: MyTopo() ) }
......@@ -33,6 +33,7 @@ import org.onlab.onos.net.intent.PathIntent;
import org.onlab.onos.net.intent.PointToPointIntent;
import org.onlab.onos.net.link.LinkService;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -71,17 +72,19 @@ public class TopologyViewIntentFilter {
* Finds all path (host-to-host or point-to-point) intents that pertains
* to the given hosts.
*
* @param hosts set of hosts to query by
* @param devices set of devices to query by
* @param hosts set of hosts to query by
* @param devices set of devices to query by
* @param sourceIntents collection of intents to search
* @return set of intents that 'match' all hosts and devices given
*/
Set<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
Iterable<Intent> sourceIntents) {
// Derive from this the set of edge connect points.
Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
// Iterate over all intents and produce a set that contains only those
// intents that target all selected hosts or derived edge connect points.
return getIntents(hosts, devices, edgePoints);
return getIntents(hosts, devices, edgePoints, sourceIntents);
}
......@@ -94,10 +97,11 @@ public class TopologyViewIntentFilter {
return edgePoints;
}
// Produces a set of intents that target all selected hosts, devices or connect points.
private Set<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
Set<ConnectPoint> edgePoints) {
Set<Intent> intents = new HashSet<>();
// Produces a list of intents that target all selected hosts, devices or connect points.
private List<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
Set<ConnectPoint> edgePoints,
Iterable<Intent> sourceIntents) {
List<Intent> intents = new ArrayList<>();
if (hosts.isEmpty() && devices.isEmpty()) {
return intents;
}
......@@ -105,7 +109,7 @@ public class TopologyViewIntentFilter {
Set<OpticalConnectivityIntent> opticalIntents = new HashSet<>();
// Search through all intents and see if they are relevant to our search.
for (Intent intent : intentService.getIntents()) {
for (Intent intent : sourceIntents) {
if (intentService.getIntentState(intent.id()) == INSTALLED) {
boolean isRelevant = false;
if (intent instanceof HostToHostIntent) {
......@@ -140,7 +144,7 @@ public class TopologyViewIntentFilter {
}
// Indicates whether the specified intent involves all of the given hosts.
private boolean isIntentRelevantToHosts(HostToHostIntent intent, Set<Host> hosts) {
private boolean isIntentRelevantToHosts(HostToHostIntent intent, Iterable<Host> hosts) {
for (Host host : hosts) {
HostId id = host.id();
// Bail if intent does not involve this host.
......@@ -152,7 +156,7 @@ public class TopologyViewIntentFilter {
}
// Indicates whether the specified intent involves all of the given devices.
private boolean isIntentRelevantToDevices(Intent intent, Set<Device> devices) {
private boolean isIntentRelevantToDevices(Intent intent, Iterable<Device> devices) {
List<Intent> installables = intentService.getInstallableIntents(intent.id());
for (Device device : devices) {
if (!isIntentRelevantToDevice(installables, device)) {
......@@ -192,7 +196,8 @@ public class TopologyViewIntentFilter {
return false;
}
private boolean isIntentRelevant(PointToPointIntent intent, Set<ConnectPoint> edgePoints) {
private boolean isIntentRelevant(PointToPointIntent intent,
Iterable<ConnectPoint> edgePoints) {
for (ConnectPoint point : edgePoints) {
// Bail if intent does not involve this edge point.
if (!point.equals(intent.egressPoint()) &&
......@@ -205,7 +210,7 @@ public class TopologyViewIntentFilter {
// Indicates whether the specified intent involves all of the given edge points.
private boolean isIntentRelevant(MultiPointToSinglePointIntent intent,
Set<ConnectPoint> edgePoints) {
Iterable<ConnectPoint> edgePoints) {
for (ConnectPoint point : edgePoints) {
// Bail if intent does not involve this edge point.
if (!point.equals(intent.egressPoint()) &&
......@@ -218,7 +223,7 @@ public class TopologyViewIntentFilter {
// Indicates whether the specified intent involves all of the given edge points.
private boolean isIntentRelevant(OpticalConnectivityIntent opticalIntent,
Set<Intent> intents) {
Iterable<Intent> intents) {
Link ccSrc = getFirstLink(opticalIntent.getSrc(), false);
Link ccDst = getFirstLink(opticalIntent.getDst(), true);
......
......@@ -676,13 +676,16 @@ public abstract class TopologyViewMessages {
List<Intent> installables = intentService.getInstallableIntents(intent.id());
if (installables != null) {
for (Intent installable : installables) {
String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
if (installable instanceof PathIntent) {
classifyLinks(cls, biLinks, ((PathIntent) installable).path().links());
classifyLinks(type, biLinks, trafficClass.showTraffic,
((PathIntent) installable).path().links());
} else if (installable instanceof LinkCollectionIntent) {
classifyLinks(cls, biLinks, ((LinkCollectionIntent) installable).links());
classifyLinks(type, biLinks, trafficClass.showTraffic,
((LinkCollectionIntent) installable).links());
} else if (installable instanceof OpticalPathIntent) {
classifyLinks(cls, biLinks, ((OpticalPathIntent) installable).path().links());
classifyLinks(type, biLinks, trafficClass.showTraffic,
((OpticalPathIntent) installable).path().links());
}
}
}
......@@ -695,12 +698,14 @@ public abstract class TopologyViewMessages {
// Adds the link segments (path or tree) associated with the specified
// connectivity intent
private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
Iterable<Link> links) {
boolean showTraffic, Iterable<Link> links) {
if (links != null) {
for (Link link : links) {
BiLink biLink = addLink(biLinks, link);
if (isInfrastructureEgress(link)) {
biLink.addLoad(statService.load(link));
if (showTraffic) {
biLink.addLoad(statService.load(link));
}
biLink.addClass(type);
}
}
......@@ -862,12 +867,18 @@ public abstract class TopologyViewMessages {
// Auxiliary carrier of data for requesting traffic message.
protected class TrafficClass {
public final boolean showTraffic;
public final String type;
public final Set<Intent> intents;
public final Iterable<Intent> intents;
TrafficClass(String type, Iterable<Intent> intents) {
this(type, intents, false);
}
TrafficClass(String type, Set<Intent> intents) {
TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
this.type = type;
this.intents = intents;
this.showTraffic = showTraffic;
}
}
......
......@@ -90,7 +90,8 @@ public class TopologyViewWebSocket
private static final String APP_ID = "org.onlab.onos.gui";
private static final long TRAFFIC_FREQUENCY_SEC = 2000;
private static final long TRAFFIC_FREQUENCY = 2000;
private static final long SUMMARY_FREQUENCY = 30000;
private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
new Comparator<ControllerNode>() {
......@@ -103,9 +104,9 @@ public class TopologyViewWebSocket
private final Timer timer = new Timer("topology-view");
private static final int MAX_EVENTS = 500;
private static final int MAX_BATCH_MS = 1000;
private static final int MAX_IDLE_MS = 500;
private static final int MAX_EVENTS = 1000;
private static final int MAX_BATCH_MS = 5000;
private static final int MAX_IDLE_MS = 1000;
private final ApplicationId appId;
......@@ -122,15 +123,23 @@ public class TopologyViewWebSocket
private final EventAccumulator eventAccummulator = new InternalEventAccummulator();
private boolean summaryEnabled = true;
private TimerTask trafficTask;
private ObjectNode trafficEvent;
private TimerTask summaryTask;
private ObjectNode summaryEvent;
private long lastActive = System.currentTimeMillis();
private boolean listenersRemoved = false;
private TopologyViewIntentFilter intentFilter;
// Current selection context
private Set<Host> selectedHosts;
private Set<Device> selectedDevices;
private List<Intent> selectedIntents;
private int currentIntentIndex = -1;
/**
* Creates a new web-socket for serving data to GUI topology view.
*
......@@ -204,7 +213,6 @@ public class TopologyViewWebSocket
processMessage((ObjectNode) mapper.reader().readTree(data));
} catch (Exception e) {
log.warn("Unable to parse GUI request {} due to {}", data, e);
log.warn("Boom!!!!", e);
}
}
......@@ -221,19 +229,29 @@ public class TopologyViewWebSocket
} else if (type.equals("addMultiSourceIntent")) {
createMultiSourceIntent(event);
} else if (type.equals("requestTraffic")) {
requestTraffic(event);
} else if (type.equals("requestRelatedIntents")) {
requestRelatedIntents(event);
} else if (type.equals("requestNextRelatedIntent")) {
requestNextRelatedIntent(event);
} else if (type.equals("requestSelectedIntentTraffic")) {
requestSelectedIntentTraffic(event);
} else if (type.equals("requestAllTraffic")) {
requestAllTraffic(event);
startTrafficMonitoring(event);
} else if (type.equals("requestDeviceLinkFlows")) {
requestDeviceLinkFlows(event);
startTrafficMonitoring(event);
} else if (type.equals("cancelTraffic")) {
cancelTraffic(event);
} else if (type.equals("requestSummary")) {
requestSummary(event);
startSummaryMonitoring(event);
} else if (type.equals("cancelSummary")) {
cancelSummary(event);
stopSummaryMonitoring();
} else if (type.equals("equalizeMasters")) {
equalizeMasters(event);
......@@ -324,8 +342,9 @@ public class TopologyViewWebSocket
new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
startMonitoring(event);
intentService.submit(intent);
startMonitoringIntent(event, intent);
}
// Creates multi-source-to-single-dest intent.
......@@ -348,10 +367,24 @@ public class TopologyViewWebSocket
MultiPointToSinglePointIntent intent =
new MultiPointToSinglePointIntent(appId, selector, treatment,
ingressPoints, dstHost.location());
startMonitoring(event);
intentService.submit(intent);
startMonitoringIntent(event, intent);
}
private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) {
selectedHosts = new HashSet<>();
selectedDevices = new HashSet<>();
selectedIntents = new ArrayList<>();
selectedIntents.add(intent);
currentIntentIndex = -1;
requestNextRelatedIntent(event);
requestSelectedIntentTraffic(event);
}
private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
Set<ConnectPoint> points = new HashSet<>();
for (HostId hostId : hostIds) {
......@@ -374,17 +407,15 @@ public class TopologyViewWebSocket
}
private synchronized long startMonitoring(ObjectNode event) {
if (trafficTask != null) {
stopMonitoring();
}
private synchronized long startTrafficMonitoring(ObjectNode event) {
stopTrafficMonitoring();
trafficEvent = event;
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
return number(event, "sid");
}
private synchronized void stopMonitoring() {
private synchronized void stopTrafficMonitoring() {
if (trafficTask != null) {
trafficTask.cancel();
trafficTask = null;
......@@ -394,13 +425,13 @@ public class TopologyViewWebSocket
// Subscribes for host traffic messages.
private synchronized void requestAllTraffic(ObjectNode event) {
long sid = startMonitoring(event);
long sid = startTrafficMonitoring(event);
sendMessage(trafficSummaryMessage(sid));
}
private void requestDeviceLinkFlows(ObjectNode event) {
ObjectNode payload = payload(event);
long sid = startMonitoring(event);
long sid = startTrafficMonitoring(event);
// Get the set of selected hosts and their intents.
ArrayNode ids = (ArrayNode) payload.path("ids");
......@@ -416,58 +447,122 @@ public class TopologyViewWebSocket
}
// Subscribes for host traffic messages.
private synchronized void requestTraffic(ObjectNode event) {
// Requests related intents message.
private synchronized void requestRelatedIntents(ObjectNode event) {
ObjectNode payload = payload(event);
if (!payload.has("ids")) {
return;
}
long sid = startMonitoring(event);
long sid = number(event, "sid");
// Get the set of selected hosts and their intents.
ArrayNode ids = (ArrayNode) payload.path("ids");
Set<Host> hosts = getHosts(ids);
Set<Device> devices = getDevices(ids);
Set<Intent> intents = intentFilter.findPathIntents(hosts, devices);
// Cancel any other traffic monitoring mode.
stopTrafficMonitoring();
// If there is a hover node, include it in the hosts and find intents.
String hover = string(payload, "hover");
Set<Intent> hoverIntents;
if (haveSelectedIntents()) {
// Get the set of selected hosts and their intents.
ArrayNode ids = (ArrayNode) payload.path("ids");
selectedHosts = getHosts(ids);
selectedDevices = getDevices(ids);
selectedIntents = intentFilter.findPathIntents(selectedHosts, selectedDevices,
intentService.getIntents());
currentIntentIndex = -1;
// Send a message to highlight all links of all monitored intents.
sendMessage(trafficMessage(sid, new TrafficClass("primary", selectedIntents)));
}
if (!isNullOrEmpty(hover)) {
addHover(hosts, devices, hover);
hoverIntents = intentFilter.findPathIntents(hosts, devices);
intents.removeAll(hoverIntents);
// If there is a hover node, include it in the selection and find intents.
processExtendedSelection(sid, hover);
}
}
private boolean haveSelectedIntents() {
return selectedIntents != null && !selectedIntents.isEmpty();
}
private void processExtendedSelection(long sid, String hover) {
Set<Host> hoverSelHosts = new HashSet<>(selectedHosts);
Set<Device> hoverSelDevices = new HashSet<>(selectedDevices);
addHover(hoverSelHosts, hoverSelDevices, hover);
// Send an initial message to highlight all links of all monitored intents.
sendMessage(trafficMessage(sid,
new TrafficClass("primary", hoverIntents),
new TrafficClass("secondary", intents)));
List<Intent> primary =
intentFilter.findPathIntents(hoverSelHosts, hoverSelDevices,
selectedIntents);
Set<Intent> secondary = new HashSet<>(selectedIntents);
secondary.removeAll(primary);
} else {
// Send an initial message to highlight all links of all monitored intents.
sendMessage(trafficMessage(sid, new TrafficClass("primary", intents)));
// Send a message to highlight all links of all monitored intents.
sendMessage(trafficMessage(sid, new TrafficClass("primary", primary),
new TrafficClass("secondary", secondary)));
}
// Requests next of the related intents.
private void requestNextRelatedIntent(ObjectNode event) {
if (haveSelectedIntents()) {
currentIntentIndex = (currentIntentIndex + 1) % selectedIntents.size();
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.info("Requested next intent {}", selectedIntent.id());
Set<Intent> primary = new HashSet<>();
primary.add(selectedIntent);
Set<Intent> secondary = new HashSet<>(selectedIntents);
secondary.remove(selectedIntent);
// Send a message to highlight all links of the selected intent.
sendMessage(trafficMessage(number(event, "sid"),
new TrafficClass("primary", primary),
new TrafficClass("secondary", secondary)));
}
}
// Requests monitoring of traffic for the selected intent.
private void requestSelectedIntentTraffic(ObjectNode event) {
if (haveSelectedIntents()) {
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.info("Requested traffic for selected {}", selectedIntent.id());
Set<Intent> primary = new HashSet<>();
primary.add(selectedIntent);
// Send a message to highlight all links of the selected intent.
sendMessage(trafficMessage(number(event, "sid"),
new TrafficClass("primary", primary, true)));
}
}
// Cancels sending traffic messages.
private void cancelTraffic(ObjectNode event) {
selectedIntents = null;
sendMessage(trafficMessage(number(event, "sid")));
stopMonitoring();
stopTrafficMonitoring();
}
private synchronized long startSummaryMonitoring(ObjectNode event) {
stopSummaryMonitoring();
summaryEvent = event;
summaryTask = new SummaryMonitor();
timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
return number(event, "sid");
}
private synchronized void stopSummaryMonitoring() {
if (summaryEvent != null) {
summaryTask.cancel();
summaryTask = null;
summaryEvent = null;
}
}
// Subscribes for summary messages.
private synchronized void requestSummary(ObjectNode event) {
summaryEnabled = true;
sendMessage(summmaryMessage(number(event, "sid")));
}
// Cancels sending summary messages.
private synchronized void cancelSummary(ObjectNode event) {
summaryEnabled = false;
}
// Forces mastership role rebalancing.
private void equalizeMasters(ObjectNode event) {
......@@ -550,7 +645,7 @@ public class TopologyViewWebSocket
@Override
public void event(IntentEvent event) {
if (trafficEvent != null) {
requestTraffic(trafficEvent);
requestSelectedIntentTraffic(trafficEvent);
}
eventAccummulator.add(event);
}
......@@ -564,6 +659,7 @@ public class TopologyViewWebSocket
}
}
// Periodic update of the traffic information
private class TrafficMonitor extends TimerTask {
@Override
public void run() {
......@@ -574,8 +670,8 @@ public class TopologyViewWebSocket
requestAllTraffic(trafficEvent);
} else if (type.equals("requestDeviceLinkFlows")) {
requestDeviceLinkFlows(trafficEvent);
} else {
requestTraffic(trafficEvent);
} else if (type.equals("requestSelectedIntentTraffic")) {
requestSelectedIntentTraffic(trafficEvent);
}
}
} catch (Exception e) {
......@@ -585,6 +681,20 @@ public class TopologyViewWebSocket
}
}
// Periodic update of the summary information
private class SummaryMonitor extends TimerTask {
@Override
public void run() {
try {
if (summaryEvent != null) {
requestSummary(summaryEvent);
}
} catch (Exception e) {
log.warn("Unable to handle summary request due to {}", e.getMessage());
}
}
}
// Accumulates events to drive methodic update of the summary pane.
private class InternalEventAccummulator extends AbstractEventAccumulator {
protected InternalEventAccummulator() {
......@@ -594,7 +704,7 @@ public class TopologyViewWebSocket
@Override
public void processEvents(List<Event> events) {
try {
if (summaryEnabled) {
if (summaryEvent != null) {
sendMessage(summmaryMessage(0));
}
} catch (Exception e) {
......
......@@ -145,8 +145,10 @@
P: togglePorts,
U: [unpin, 'Unpin node (hover mouse over)'],
R: [resetPanZoom, 'Reset pan / zoom'],
V: [showTrafficAction, 'Show related traffic'],
A: [showAllTrafficAction, 'Show all traffic'],
V: [showRelatedIntentsAction, 'Show all related intents'],
N: [showNextIntentAction, 'Show next related intent'],
W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
A: [showAllTrafficAction, 'Monitor all traffic'],
F: [showDeviceLinkFlowsAction, 'Show device link flows'],
X: [toggleNodeLock, 'Lock / unlock node positions'],
Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
......@@ -209,10 +211,11 @@
oblique = false;
// constants
var hoverModeAll = 1,
var hoverModeNone = 0,
hoverModeAll = 1,
hoverModeFlows = 2,
hoverModeIntents = 3,
hoverMode = hoverModeFlows;
hoverMode = hoverModeNone;
// D3 selections
var svg,
......@@ -394,7 +397,7 @@
cancelSummary();
stopAntTimer();
} else {
hoverMode = hoverModeFlows;
hoverMode = hoverModeNone;
}
}
......@@ -1219,22 +1222,20 @@
}
function requestTrafficForMode() {
if (hoverMode === hoverModeAll) {
requestAllTraffic();
} else if (hoverMode === hoverModeFlows) {
if (hoverMode === hoverModeFlows) {
requestDeviceLinkFlows();
} else if (hoverMode === hoverModeIntents) {
requestSelectTraffic();
requestRelatedIntents();
}
}
function showTrafficAction() {
function showRelatedIntentsAction() {
hoverMode = hoverModeIntents;
requestSelectTraffic();
flash('Related Traffic');
requestRelatedIntents();
flash('Related intents');
}
function requestSelectTraffic() {
function requestRelatedIntents() {
function hoverValid() {
return hoverMode === hoverModeIntents &&
hovered &&
......@@ -1242,13 +1243,24 @@
}
if (validateSelectionContext()) {
sendMessage('requestTraffic', {
sendMessage('requestRelatedIntents', {
ids: selectOrder,
hover: hoverValid() ? hovered.id : ''
});
}
}
function showNextIntentAction() {
hoverMode = hoverModeNone;
sendMessage('requestNextRelatedIntent', {});
flash('Next related intent');
}
function showSelectedIntentTrafficAction() {
hoverMode = hoverModeNone;
sendMessage('requestSelectedIntentTraffic', {});
flash('Monitoring selected intent');
}
function showDeviceLinkFlowsAction() {
hoverMode = hoverModeFlows;
......@@ -2010,13 +2022,17 @@
}
function nodeMouseOver(d) {
hovered = d;
requestTrafficForMode();
if (hovered != d) {
hovered = d;
requestTrafficForMode();
}
}
function nodeMouseOut(d) {
hovered = null;
requestTrafficForMode();
if (hovered != null) {
hovered = null;
requestTrafficForMode();
}
}
function addHostIcon(node, radius, iid) {
......@@ -2498,7 +2514,7 @@
wsTrace('rx', msg);
}
function wsTrace(rxtx, msg) {
console.log('[' + rxtx + '] ' + msg);
// console.log('[' + rxtx + '] ' + msg);
}
// NOTE: Temporary hardcoded example for showing detail pane
......@@ -2620,7 +2636,6 @@
emptySelect();
} else if (nSel === 1) {
singleSelect();
requestTrafficForMode();
} else {
multiSelect();
}
......@@ -2635,12 +2650,14 @@
function singleSelect() {
// NOTE: detail is shown from showDetails event callback
requestDetails();
cancelTraffic();
requestTrafficForMode();
}
function multiSelect() {
haveDetails = true;
populateMultiSelect();
cancelTraffic();
requestTrafficForMode();
}
......@@ -2738,7 +2755,7 @@
function addSingleSelectActions(data) {
detailPane.append('hr');
// always want to allow 'show traffic'
addAction(detailPane, 'Show Related Traffic', showTrafficAction);
addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
if (data.type === 'switch') {
addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
......@@ -2748,7 +2765,7 @@
function addMultiSelectActions() {
detailPane.append('hr');
// always want to allow 'show traffic'
addAction(detailPane, 'Show Related Traffic', showTrafficAction);
addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
// if exactly two hosts are selected, also want 'add host intent'
if (nSel() === 2 && allSelectionsClass('host')) {
addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
......